github.com/chenbh/concourse/v6@v6.4.2/worker/workercmd/worker_linux.go (about) 1 package workercmd 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "io/ioutil" 8 "os" 9 "os/user" 10 "path/filepath" 11 "strings" 12 "time" 13 14 "code.cloudfoundry.org/lager" 15 "github.com/chenbh/concourse/v6/atc" 16 concourseCmd "github.com/chenbh/concourse/v6/cmd" 17 "github.com/concourse/flag" 18 "github.com/jessevdk/go-flags" 19 "github.com/tedsuo/ifrit" 20 ) 21 22 type Certs struct { 23 Dir string `long:"certs-dir" description:"Directory to use when creating the resource certificates volume."` 24 } 25 26 type RuntimeConfiguration struct { 27 Runtime string `long:"runtime" default:"guardian" choice:"guardian" choice:"containerd" choice:"houdini" description:"Runtime to use with the worker. Please note that Houdini is insecure and doesn't run 'tasks' in containers."` 28 } 29 30 type GuardianRuntime struct { 31 Bin string `long:"bin" description:"Path to a garden server executable (non-absolute names get resolved from $PATH)."` 32 Config flag.File `long:"config" description:"Path to a config file to use for the Garden backend. Guardian flags as env vars, e.g. 'CONCOURSE_GARDEN_FOO_BAR=a,b' for '--foo-bar a --foo-bar b'."` 33 DNS DNSConfig `group:"DNS Proxy Configuration" namespace:"dns-proxy"` 34 RequestTimeout time.Duration `long:"request-timeout" default:"5m" description:"How long to wait for requests to the Garden server to complete. 0 means no timeout."` 35 } 36 37 type ContainerdRuntime struct { 38 Config flag.File `long:"config" description:"Path to a config file to use for the Containerd daemon."` 39 Bin string `long:"bin" description:"Path to a containerd executable (non-absolute names get resolved from $PATH)."` 40 RequestTimeout time.Duration `long:"request-timeout" default:"5m" description:"How long to wait for requests to Containerd to complete. 0 means no timeout."` 41 42 //TODO can DNSConfig be simplifed to just a bool rather than struct with a bool? 43 DNS DNSConfig `group:"DNS Proxy Configuration" namespace:"dns-proxy"` 44 DNSServers []string `long:"dns-server" description:"DNS server IP address to use instead of automatically determined servers. Can be specified multiple times."` 45 RestrictedNetworks []string `long:"restricted-network" description:"Network ranges to which traffic from containers will be restricted. Can be specified multiple times."` 46 MaxContainers int `long:"max-containers" default:"0" description:"Max container capacity. 0 means no limit."` 47 NetworkPool string `long:"network-pool" default:"10.80.0.0/16" description:"Network range to use for dynamically allocated container subnets."` 48 } 49 50 const containerdRuntime = "containerd" 51 const guardianRuntime = "guardian" 52 const houdiniRuntime = "houdini" 53 54 func (cmd WorkerCommand) LessenRequirements(prefix string, command *flags.Command) { 55 // configured as work-dir/volumes 56 command.FindOptionByLongName(prefix + "baggageclaim-volumes").Required = false 57 } 58 59 // Chooses the appropriate runtime based on CONCOURSE_RUNTIME_TYPE. 60 // The runtime is represented as a Ifrit runner that must include a Garden Server process. The Garden server exposes API 61 // endpoints that allow the ATC to make container related requests to the worker. 62 // The runner may also include additional processes such as the runtime's daemon or a DNS proxy server. 63 func (cmd *WorkerCommand) gardenServerRunner(logger lager.Logger) (atc.Worker, ifrit.Runner, error) { 64 err := cmd.checkRoot() 65 if err != nil { 66 return atc.Worker{}, nil, err 67 } 68 69 err = cmd.verifyRuntimeFlags() 70 if err != nil { 71 return atc.Worker{}, nil, err 72 } 73 74 worker := cmd.Worker.Worker() 75 worker.Platform = "linux" 76 77 if cmd.Certs.Dir != "" { 78 worker.CertsPath = &cmd.Certs.Dir 79 } 80 81 worker.ResourceTypes, err = cmd.loadResources(logger.Session("load-resources")) 82 if err != nil { 83 return atc.Worker{}, nil, err 84 } 85 86 worker.Name, err = cmd.workerName() 87 if err != nil { 88 return atc.Worker{}, nil, err 89 } 90 91 trySetConcourseDirInPATH() 92 93 var runner ifrit.Runner 94 95 switch { 96 case cmd.Runtime == houdiniRuntime: 97 runner, err = cmd.houdiniRunner(logger) 98 case cmd.Runtime == containerdRuntime: 99 runner, err = cmd.containerdRunner(logger) 100 case cmd.Runtime == guardianRuntime: 101 runner, err = cmd.guardianRunner(logger) 102 default: 103 err = fmt.Errorf("unsupported Runtime :%s", cmd.Runtime) 104 } 105 106 if err != nil { 107 return atc.Worker{}, nil, err 108 } 109 110 return worker, runner, nil 111 } 112 113 func trySetConcourseDirInPATH() { 114 binDir := concourseCmd.DiscoverAsset("bin") 115 if binDir == "" { 116 return 117 } 118 119 err := os.Setenv("PATH", binDir+":"+os.Getenv("PATH")) 120 if err != nil { 121 // programming mistake 122 panic(fmt.Errorf("failed to set PATH environment variable: %w", err)) 123 } 124 } 125 126 var ErrNotRoot = errors.New("worker must be run as root") 127 128 func (cmd *WorkerCommand) checkRoot() error { 129 currentUser, err := user.Current() 130 if err != nil { 131 return err 132 } 133 134 if currentUser.Uid != "0" { 135 return ErrNotRoot 136 } 137 138 return nil 139 } 140 141 func (cmd *WorkerCommand) dnsProxyRunner(logger lager.Logger) (ifrit.Runner, error) { 142 server, err := cmd.Guardian.DNS.Server() 143 if err != nil { 144 return nil, err 145 } 146 147 return ifrit.RunFunc(func(signals <-chan os.Signal, ready chan<- struct{}) error { 148 server.NotifyStartedFunc = func() { 149 close(ready) 150 logger.Info("started") 151 } 152 153 serveErr := make(chan error, 1) 154 155 go func() { 156 serveErr <- server.ListenAndServe() 157 }() 158 159 for { 160 select { 161 case err := <-serveErr: 162 return err 163 case <-signals: 164 server.Shutdown() 165 } 166 } 167 }), nil 168 } 169 170 func (cmd *WorkerCommand) loadResources(logger lager.Logger) ([]atc.WorkerResourceType, error) { 171 var types []atc.WorkerResourceType 172 173 if cmd.ResourceTypes != "" { 174 basePath := cmd.ResourceTypes.Path() 175 176 entries, err := ioutil.ReadDir(basePath) 177 if err != nil { 178 logger.Error("failed-to-read-resources-dir", err) 179 return nil, err 180 } 181 182 for _, e := range entries { 183 meta, err := ioutil.ReadFile(filepath.Join(basePath, e.Name(), "resource_metadata.json")) 184 if err != nil { 185 logger.Error("failed-to-read-resource-type-metadata", err) 186 return nil, err 187 } 188 189 var t atc.WorkerResourceType 190 err = json.Unmarshal(meta, &t) 191 if err != nil { 192 logger.Error("failed-to-unmarshal-resource-type-metadata", err) 193 return nil, err 194 } 195 196 t.Image = filepath.Join(basePath, e.Name(), "rootfs.tgz") 197 198 types = append(types, t) 199 } 200 } 201 202 return types, nil 203 } 204 205 206 func (cmd *WorkerCommand) hasFlags(prefix string) bool { 207 env := os.Environ() 208 209 for _, envVar := range env { 210 if strings.HasPrefix(envVar, prefix) { 211 return true 212 } 213 } 214 215 return false 216 } 217 218 const guardianEnvPrefix = "CONCOURSE_GARDEN_" 219 const containerdEnvPrefix = "CONCOURSE_CONTAINERD_" 220 221 // Checks if runtime specific flags provided match the selected runtime type 222 func (cmd *WorkerCommand) verifyRuntimeFlags() error { 223 switch { 224 case cmd.Runtime == houdiniRuntime: 225 if cmd.hasFlags(guardianEnvPrefix) || cmd.hasFlags(containerdEnvPrefix) { 226 return fmt.Errorf("cannot use %s or %s environment variables with Houdini", guardianEnvPrefix, containerdEnvPrefix) 227 } 228 case cmd.Runtime == containerdRuntime: 229 if cmd.hasFlags(guardianEnvPrefix) { 230 return fmt.Errorf("cannot use %s environment variables with Containerd", guardianEnvPrefix) 231 } 232 case cmd.Runtime == guardianRuntime: 233 if cmd.hasFlags(containerdEnvPrefix) { 234 return fmt.Errorf("cannot use %s environment variables with Guardian", containerdEnvPrefix) 235 } 236 default: 237 return fmt.Errorf("unsupported Runtime :%s", cmd.Runtime) 238 } 239 240 return nil 241 }