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  }