github.com/stevenmatthewt/agent@v3.5.4+incompatible/clicommand/agent_start.go (about)

     1  package clicommand
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"path/filepath"
     7  	"runtime"
     8  	"time"
     9  
    10  	"github.com/buildkite/agent/agent"
    11  	"github.com/buildkite/agent/cliconfig"
    12  	"github.com/buildkite/agent/logger"
    13  	"github.com/buildkite/shellwords"
    14  	"github.com/urfave/cli"
    15  )
    16  
    17  var StartDescription = `Usage:
    18  
    19     buildkite-agent start [arguments...]
    20  
    21  Description:
    22  
    23     When a job is ready to run it will call the "bootstrap-script"
    24     and pass it all the environment variables required for the job to run.
    25     This script is responsible for checking out the code, and running the
    26     actual build script defined in the pipeline.
    27  
    28     The agent will run any jobs within a PTY (pseudo terminal) if available.
    29  
    30  Example:
    31  
    32     $ buildkite-agent start --token xxx`
    33  
    34  // Adding config requires changes in a few different spots
    35  // - The AgentStartConfig struct with a cli parameter
    36  // - As a flag in the AgentStartCommand (with matching env)
    37  // - Into an env to be passed to the bootstrap in agent/job_runner.go, createEnvironment()
    38  // - Into clicommand/bootstrap.go to read it from the env into the bootstrap config
    39  
    40  type AgentStartConfig struct {
    41  	Config                    string   `cli:"config"`
    42  	Token                     string   `cli:"token" validate:"required"`
    43  	Name                      string   `cli:"name"`
    44  	Priority                  string   `cli:"priority"`
    45  	DisconnectAfterJob        bool     `cli:"disconnect-after-job"`
    46  	DisconnectAfterJobTimeout int      `cli:"disconnect-after-job-timeout"`
    47  	BootstrapScript           string   `cli:"bootstrap-script" normalize:"commandpath"`
    48  	BuildPath                 string   `cli:"build-path" normalize:"filepath" validate:"required"`
    49  	HooksPath                 string   `cli:"hooks-path" normalize:"filepath"`
    50  	PluginsPath               string   `cli:"plugins-path" normalize:"filepath"`
    51  	Shell                     string   `cli:"shell"`
    52  	Tags                      []string `cli:"tags" normalize:"list"`
    53  	TagsFromEC2               bool     `cli:"tags-from-ec2"`
    54  	TagsFromEC2Tags           bool     `cli:"tags-from-ec2-tags"`
    55  	TagsFromGCP               bool     `cli:"tags-from-gcp"`
    56  	TagsFromHost              bool     `cli:"tags-from-host"`
    57  	WaitForEC2TagsTimeout     string   `cli:"wait-for-ec2-tags-timeout"`
    58  	GitCloneFlags             string   `cli:"git-clone-flags"`
    59  	GitCleanFlags             string   `cli:"git-clean-flags"`
    60  	NoGitSubmodules           bool     `cli:"no-git-submodules"`
    61  	NoColor                   bool     `cli:"no-color"`
    62  	NoSSHKeyscan              bool     `cli:"no-ssh-keyscan"`
    63  	NoCommandEval             bool     `cli:"no-command-eval"`
    64  	NoLocalHooks              bool     `cli:"no-local-hooks"`
    65  	NoPlugins                 bool     `cli:"no-plugins"`
    66  	NoPluginValidation        bool     `cli:"no-plugin-validation"`
    67  	NoPTY                     bool     `cli:"no-pty"`
    68  	TimestampLines            bool     `cli:"timestamp-lines"`
    69  	Endpoint                  string   `cli:"endpoint" validate:"required"`
    70  	Debug                     bool     `cli:"debug"`
    71  	DebugHTTP                 bool     `cli:"debug-http"`
    72  	Experiments               []string `cli:"experiment" normalize:"list"`
    73  
    74  	/* Deprecated */
    75  	NoSSHFingerprintVerification bool     `cli:"no-automatic-ssh-fingerprint-verification" deprecated-and-renamed-to:"NoSSHKeyscan"`
    76  	MetaData                     []string `cli:"meta-data" deprecated-and-renamed-to:"Tags"`
    77  	MetaDataEC2                  bool     `cli:"meta-data-ec2" deprecated-and-renamed-to:"TagsFromEC2"`
    78  	MetaDataEC2Tags              bool     `cli:"meta-data-ec2-tags" deprecated-and-renamed-to:"TagsFromEC2Tags"`
    79  	MetaDataGCP                  bool     `cli:"meta-data-gcp" deprecated-and-renamed-to:"TagsFromGCP"`
    80  }
    81  
    82  func DefaultShell() string {
    83  	// https://github.com/golang/go/blob/master/src/go/build/syslist.go#L7
    84  	switch runtime.GOOS {
    85  	case "windows":
    86  		return `C:\Windows\System32\CMD.exe /S /C`
    87  	case "freebsd", "openbsd", "netbsd":
    88  		return `/usr/local/bin/bash -e -c`
    89  	default:
    90  		return `/bin/bash -e -c`
    91  	}
    92  }
    93  
    94  func DefaultConfigFilePaths() (paths []string) {
    95  	// Toggle beetwen windows an *nix paths
    96  	if runtime.GOOS == "windows" {
    97  		paths = []string{
    98  			"C:\\buildkite-agent\\buildkite-agent.cfg",
    99  			"$USERPROFILE\\AppData\\Local\\BuildkiteAgent\\buildkite-agent.cfg",
   100  		}
   101  	} else {
   102  		paths = []string{
   103  			"$HOME/.buildkite-agent/buildkite-agent.cfg",
   104  			"/usr/local/etc/buildkite-agent/buildkite-agent.cfg",
   105  			"/etc/buildkite-agent/buildkite-agent.cfg",
   106  		}
   107  	}
   108  
   109  	// Also check to see if there's a buildkite-agent.cfg in the folder
   110  	// that the binary is running in.
   111  	pathToBinary, err := filepath.Abs(filepath.Dir(os.Args[0]))
   112  	if err == nil {
   113  		pathToRelativeConfig := filepath.Join(pathToBinary, "buildkite-agent.cfg")
   114  		paths = append([]string{pathToRelativeConfig}, paths...)
   115  	}
   116  
   117  	return
   118  }
   119  
   120  var AgentStartCommand = cli.Command{
   121  	Name:        "start",
   122  	Usage:       "Starts a Buildkite agent",
   123  	Description: StartDescription,
   124  	Flags: []cli.Flag{
   125  		cli.StringFlag{
   126  			Name:   "config",
   127  			Value:  "",
   128  			Usage:  "Path to a configuration file",
   129  			EnvVar: "BUILDKITE_AGENT_CONFIG",
   130  		},
   131  		cli.StringFlag{
   132  			Name:   "token",
   133  			Value:  "",
   134  			Usage:  "Your account agent token",
   135  			EnvVar: "BUILDKITE_AGENT_TOKEN",
   136  		},
   137  		cli.StringFlag{
   138  			Name:   "name",
   139  			Value:  "",
   140  			Usage:  "The name of the agent",
   141  			EnvVar: "BUILDKITE_AGENT_NAME",
   142  		},
   143  		cli.StringFlag{
   144  			Name:   "priority",
   145  			Value:  "",
   146  			Usage:  "The priority of the agent (higher priorities are assigned work first)",
   147  			EnvVar: "BUILDKITE_AGENT_PRIORITY",
   148  		},
   149  		cli.BoolFlag{
   150  			Name:   "disconnect-after-job",
   151  			Usage:  "Disconnect the agent after running a job",
   152  			EnvVar: "BUILDKITE_AGENT_DISCONNECT_AFTER_JOB",
   153  		},
   154  		cli.IntFlag{
   155  			Name:   "disconnect-after-job-timeout",
   156  			Value:  120,
   157  			Usage:  "When --disconnect-after-job is specified, the number of seconds to wait for a job before shutting down",
   158  			EnvVar: "BUILDKITE_AGENT_DISCONNECT_AFTER_JOB_TIMEOUT",
   159  		},
   160  		cli.StringFlag{
   161  			Name:   "shell",
   162  			Value:  DefaultShell(),
   163  			Usage:  "The shell commamnd used to interpret build commands, e.g /bin/bash -e -c",
   164  			EnvVar: "BUILDKITE_SHELL",
   165  		},
   166  		cli.StringSliceFlag{
   167  			Name:   "tags",
   168  			Value:  &cli.StringSlice{},
   169  			Usage:  "A comma-separated list of tags for the agent (e.g. \"linux\" or \"mac,xcode=8\")",
   170  			EnvVar: "BUILDKITE_AGENT_TAGS",
   171  		},
   172  		cli.BoolFlag{
   173  			Name:   "tags-from-host",
   174  			Usage:  "Include tags from the host (hostname, machine-id, os)",
   175  			EnvVar: "BUILDKITE_AGENT_TAGS_FROM_HOST",
   176  		},
   177  		cli.BoolFlag{
   178  			Name:   "tags-from-ec2",
   179  			Usage:  "Include the host's EC2 meta-data as tags (instance-id, instance-type, and ami-id)",
   180  			EnvVar: "BUILDKITE_AGENT_TAGS_FROM_EC2",
   181  		},
   182  		cli.BoolFlag{
   183  			Name:   "tags-from-ec2-tags",
   184  			Usage:  "Include the host's EC2 tags as tags",
   185  			EnvVar: "BUILDKITE_AGENT_TAGS_FROM_EC2_TAGS",
   186  		},
   187  		cli.BoolFlag{
   188  			Name:   "tags-from-gcp",
   189  			Usage:  "Include the host's Google Cloud meta-data as tags (instance-id, machine-type, preemptible, project-id, region, and zone)",
   190  			EnvVar: "BUILDKITE_AGENT_TAGS_FROM_GCP",
   191  		},
   192  		cli.DurationFlag{
   193  			Name:   "wait-for-ec2-tags-timeout",
   194  			Usage:  "The amount of time to wait for tags from EC2 before proceeding",
   195  			EnvVar: "BUILDKITE_AGENT_WAIT_FOR_EC2_TAGS_TIMEOUT",
   196  			Value:  time.Second * 10,
   197  		},
   198  		cli.StringFlag{
   199  			Name:   "git-clone-flags",
   200  			Value:  "-v",
   201  			Usage:  "Flags to pass to the \"git clone\" command",
   202  			EnvVar: "BUILDKITE_GIT_CLONE_FLAGS",
   203  		},
   204  		cli.StringFlag{
   205  			Name:   "git-clean-flags",
   206  			Value:  "-fxdq",
   207  			Usage:  "Flags to pass to \"git clean\" command",
   208  			EnvVar: "BUILDKITE_GIT_CLEAN_FLAGS",
   209  		},
   210  		cli.StringFlag{
   211  			Name:   "bootstrap-script",
   212  			Value:  "",
   213  			Usage:  "The command that is executed for bootstrapping a job, defaults to the bootstrap sub-command of this binary",
   214  			EnvVar: "BUILDKITE_BOOTSTRAP_SCRIPT_PATH",
   215  		},
   216  		cli.StringFlag{
   217  			Name:   "build-path",
   218  			Value:  "",
   219  			Usage:  "Path to where the builds will run from",
   220  			EnvVar: "BUILDKITE_BUILD_PATH",
   221  		},
   222  		cli.StringFlag{
   223  			Name:   "hooks-path",
   224  			Value:  "",
   225  			Usage:  "Directory where the hook scripts are found",
   226  			EnvVar: "BUILDKITE_HOOKS_PATH",
   227  		},
   228  		cli.StringFlag{
   229  			Name:   "plugins-path",
   230  			Value:  "",
   231  			Usage:  "Directory where the plugins are saved to",
   232  			EnvVar: "BUILDKITE_PLUGINS_PATH",
   233  		},
   234  		cli.BoolFlag{
   235  			Name:   "timestamp-lines",
   236  			Usage:  "Prepend timestamps on each line of output.",
   237  			EnvVar: "BUILDKITE_TIMESTAMP_LINES",
   238  		},
   239  		cli.BoolFlag{
   240  			Name:   "no-pty",
   241  			Usage:  "Do not run jobs within a pseudo terminal",
   242  			EnvVar: "BUILDKITE_NO_PTY",
   243  		},
   244  		cli.BoolFlag{
   245  			Name:   "no-ssh-keyscan",
   246  			Usage:  "Don't automatically run ssh-keyscan before checkout",
   247  			EnvVar: "BUILDKITE_NO_SSH_KEYSCAN",
   248  		},
   249  		cli.BoolFlag{
   250  			Name:   "no-command-eval",
   251  			Usage:  "Don't allow this agent to run arbitrary console commands, including plugins",
   252  			EnvVar: "BUILDKITE_NO_COMMAND_EVAL",
   253  		},
   254  		cli.BoolFlag{
   255  			Name:   "no-plugins",
   256  			Usage:  "Don't allow this agent to load plugins",
   257  			EnvVar: "BUILDKITE_NO_PLUGINS",
   258  		},
   259  		cli.BoolTFlag{
   260  			Name:   "no-plugin-validation",
   261  			Usage:  "Don't validate plugin configuration and requirements",
   262  			EnvVar: "BUILDKITE_NO_PLUGIN_VALIDATION",
   263  		},
   264  		cli.BoolFlag{
   265  			Name:   "no-local-hooks",
   266  			Usage:  "Don't allow local hooks to be run from checked out repositories",
   267  			EnvVar: "BUILDKITE_NO_LOCAL_HOOKS",
   268  		},
   269  		cli.BoolFlag{
   270  			Name:   "no-git-submodules",
   271  			Usage:  "Don't automatically checkout git submodules",
   272  			EnvVar: "BUILDKITE_NO_GIT_SUBMODULES,BUILDKITE_DISABLE_GIT_SUBMODULES",
   273  		},
   274  		ExperimentsFlag,
   275  		EndpointFlag,
   276  		NoColorFlag,
   277  		DebugFlag,
   278  		DebugHTTPFlag,
   279  		/* Deprecated flags which will be removed in v4 */
   280  		cli.StringSliceFlag{
   281  			Name:   "meta-data",
   282  			Value:  &cli.StringSlice{},
   283  			Hidden: true,
   284  			EnvVar: "BUILDKITE_AGENT_META_DATA",
   285  		},
   286  		cli.BoolFlag{
   287  			Name:   "meta-data-ec2",
   288  			Hidden: true,
   289  			EnvVar: "BUILDKITE_AGENT_META_DATA_EC2",
   290  		},
   291  		cli.BoolFlag{
   292  			Name:   "meta-data-ec2-tags",
   293  			Hidden: true,
   294  			EnvVar: "BUILDKITE_AGENT_TAGS_FROM_EC2_TAGS",
   295  		},
   296  		cli.BoolFlag{
   297  			Name:   "meta-data-gcp",
   298  			Hidden: true,
   299  			EnvVar: "BUILDKITE_AGENT_META_DATA_GCP",
   300  		},
   301  		cli.BoolFlag{
   302  			Name:   "no-automatic-ssh-fingerprint-verification",
   303  			Hidden: true,
   304  			EnvVar: "BUILDKITE_NO_AUTOMATIC_SSH_FINGERPRINT_VERIFICATION",
   305  		},
   306  	},
   307  	Action: func(c *cli.Context) {
   308  		// The configuration will be loaded into this struct
   309  		cfg := AgentStartConfig{}
   310  
   311  		// Setup the config loader. You'll see that we also path paths to
   312  		// potential config files. The loader will use the first one it finds.
   313  		loader := cliconfig.Loader{
   314  			CLI:                    c,
   315  			Config:                 &cfg,
   316  			DefaultConfigFilePaths: DefaultConfigFilePaths(),
   317  		}
   318  
   319  		// Load the configuration
   320  		if err := loader.Load(); err != nil {
   321  			logger.Fatal("%s", err)
   322  		}
   323  
   324  		// Setup the any global configuration options
   325  		HandleGlobalFlags(cfg)
   326  
   327  		// Force some settings if on Windows (these aren't supported yet)
   328  		if runtime.GOOS == "windows" {
   329  			cfg.NoPTY = true
   330  		}
   331  
   332  		// Set a useful default for the bootstrap script
   333  		if cfg.BootstrapScript == "" {
   334  			cfg.BootstrapScript = fmt.Sprintf("%s bootstrap", shellwords.Quote(os.Args[0]))
   335  		}
   336  
   337  		// Show a warning if plugins are enabled by no-command-eval or no-local-hooks is set
   338  		if c.IsSet("no-plugins") && cfg.NoPlugins == false {
   339  			msg := `Plugins have been specifically enabled, despite %s being enabled. ` +
   340  				`Plugins can execute arbitrary hooks and commands, make sure you are ` +
   341  				`whitelisting your plugins in ` +
   342  				`your environment hook.`
   343  
   344  			switch {
   345  			case cfg.NoCommandEval:
   346  				logger.Warn(msg, `no-command-eval`)
   347  			case cfg.NoLocalHooks:
   348  				logger.Warn(msg, `no-local-hooks`)
   349  			}
   350  		}
   351  
   352  		// Turning off command eval or local hooks will also turn off plugins unless
   353  		// `--no-plugins=false` is provided specifically
   354  		if (cfg.NoCommandEval || cfg.NoLocalHooks) && !c.IsSet("no-plugins") {
   355  			cfg.NoPlugins = true
   356  		}
   357  
   358  		// Guess the shell if none is provided
   359  		if cfg.Shell == "" {
   360  			cfg.Shell = DefaultShell()
   361  		}
   362  
   363  		// Make sure the DisconnectAfterJobTimeout value is correct
   364  		if cfg.DisconnectAfterJob && cfg.DisconnectAfterJobTimeout < 120 {
   365  			logger.Fatal("The timeout for `disconnect-after-job` must be at least 120 seconds")
   366  		}
   367  
   368  		var ec2TagTimeout time.Duration
   369  		if t := cfg.WaitForEC2TagsTimeout; t != "" {
   370  			var err error
   371  			ec2TagTimeout, err = time.ParseDuration(t)
   372  			if err != nil {
   373  				logger.Fatal("Failed to parse ec2 tag timeout: %v", err)
   374  			}
   375  		}
   376  
   377  		// Setup the agent
   378  		pool := agent.AgentPool{
   379  			Token:                 cfg.Token,
   380  			Name:                  cfg.Name,
   381  			Priority:              cfg.Priority,
   382  			Tags:                  cfg.Tags,
   383  			TagsFromEC2:           cfg.TagsFromEC2,
   384  			TagsFromEC2Tags:       cfg.TagsFromEC2Tags,
   385  			TagsFromGCP:           cfg.TagsFromGCP,
   386  			TagsFromHost:          cfg.TagsFromHost,
   387  			WaitForEC2TagsTimeout: ec2TagTimeout,
   388  			Endpoint:              cfg.Endpoint,
   389  			AgentConfiguration: &agent.AgentConfiguration{
   390  				BootstrapScript:           cfg.BootstrapScript,
   391  				BuildPath:                 cfg.BuildPath,
   392  				HooksPath:                 cfg.HooksPath,
   393  				PluginsPath:               cfg.PluginsPath,
   394  				GitCloneFlags:             cfg.GitCloneFlags,
   395  				GitCleanFlags:             cfg.GitCleanFlags,
   396  				GitSubmodules:             !cfg.NoGitSubmodules,
   397  				SSHKeyscan:                !cfg.NoSSHKeyscan,
   398  				CommandEval:               !cfg.NoCommandEval,
   399  				PluginsEnabled:            !cfg.NoPlugins,
   400  				PluginValidation:          !cfg.NoPluginValidation,
   401  				LocalHooksEnabled:         !cfg.NoLocalHooks,
   402  				RunInPty:                  !cfg.NoPTY,
   403  				TimestampLines:            cfg.TimestampLines,
   404  				DisconnectAfterJob:        cfg.DisconnectAfterJob,
   405  				DisconnectAfterJobTimeout: cfg.DisconnectAfterJobTimeout,
   406  				Shell: cfg.Shell,
   407  			},
   408  		}
   409  
   410  		// Store the loaded config file path on the pool and agent config so we can
   411  		// show it when the agent starts and set an env
   412  		if loader.File != nil {
   413  			pool.ConfigFilePath = loader.File.Path
   414  			pool.AgentConfiguration.ConfigPath = loader.File.Path
   415  		}
   416  
   417  		// Start the agent pool
   418  		if err := pool.Start(); err != nil {
   419  			logger.Fatal("%s", err)
   420  		}
   421  	},
   422  }