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 }