Skip to content

Commit

Permalink
Add new command buildkite-agent exec-job, and deprecate bootstrap
Browse files Browse the repository at this point in the history
`exec-job` does the exact same thing as bootstrap, it just has a much better name
  • Loading branch information
moskyb committed Feb 16, 2023
1 parent 94b179d commit 69f0510
Show file tree
Hide file tree
Showing 20 changed files with 629 additions and 570 deletions.
2 changes: 1 addition & 1 deletion agent/agent_configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ package agent
// has been loaded from the config file and command-line params
type AgentConfiguration struct {
ConfigPath string
BootstrapScript string
JobExecutorScript string
BuildPath string
HooksPath string
GitMirrorsPath string
Expand Down
16 changes: 8 additions & 8 deletions agent/integration/job_runner_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func TestJobRunner_WhenJobHasToken_ItOverridesAccessToken(t *testing.T) {
})
}

func TestJobRunnerPassesAccessTokenToBootstrap(t *testing.T) {
func TestJobRunnerPassesAccessTokenToJobExecute(t *testing.T) {
ag := &api.AgentRegisterResponse{
AccessToken: "llamasrock",
}
Expand Down Expand Up @@ -91,29 +91,29 @@ func TestJobRunnerIgnoresPipelineChangesToProtectedVars(t *testing.T) {

}

func runJob(t *testing.T, ag *api.AgentRegisterResponse, j *api.Job, cfg agent.AgentConfiguration, bootstrap func(c *bintest.Call)) {
func runJob(t *testing.T, ag *api.AgentRegisterResponse, j *api.Job, cfg agent.AgentConfiguration, executor func(c *bintest.Call)) {
// create a mock agent API
server := createTestAgentEndpoint(t, "my-job-id")
defer server.Close()

// set up a mock bootstrap that the runner will call
bs, err := bintest.NewMock("buildkite-agent-bootstrap")
// set up a mock executor that the runner will call
bs, err := bintest.NewMock("buildkite-agent-job-execute")
if err != nil {
t.Fatalf("bintest.NewMock() error = %v", err)
}
defer bs.CheckAndClose(t)

// execute the callback we have inside the bootstrap mock
bs.Expect().Once().AndExitWith(0).AndCallFunc(bootstrap)
// execute the callback we have inside the executor mock
bs.Expect().Once().AndExitWith(0).AndCallFunc(executor)

l := logger.Discard

// minimal metrics, this could be cleaner
m := metrics.NewCollector(l, metrics.CollectorConfig{})
scope := m.Scope(metrics.Tags{})

// set the bootstrap into the config
cfg.BootstrapScript = bs.Path
// set the executor into the config
cfg.JobExecutorScript = bs.Path

client := api.NewClient(l, api.Config{
Endpoint: server.URL,
Expand Down
12 changes: 6 additions & 6 deletions agent/job_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import (

const (
// BuildkiteMessageMax is the maximum length of "BUILDKITE_MESSAGE=...\0"
// environment entry passed to bootstrap, beyond which it will be truncated
// environment entry passed to job executor, beyond which it will be truncated
// to avoid exceeding the system limit. Note that it includes the variable
// name, equals sign, and null terminator.
//
Expand Down Expand Up @@ -151,11 +151,11 @@ func NewJobRunner(l logger.Logger, scope *metrics.Scope, ag *api.AgentRegisterRe
return nil, err
}

// The bootstrap-script gets parsed based on the operating system
cmd, err := shellwords.Split(conf.AgentConfiguration.BootstrapScript)
// The job executor script gets parsed based on the operating system
cmd, err := shellwords.Split(conf.AgentConfiguration.JobExecutorScript)
if err != nil {
return nil, fmt.Errorf("Failed to split bootstrap-script (%q) into tokens: %v",
conf.AgentConfiguration.BootstrapScript, err)
return nil, fmt.Errorf("Failed to split job executor script (%q) into tokens: %v",
conf.AgentConfiguration.JobExecutorScript, err)
}

// Our log streamer works off a buffer of output
Expand Down Expand Up @@ -237,7 +237,7 @@ func NewJobRunner(l logger.Logger, scope *metrics.Scope, ag *api.AgentRegisterRe
// take precedence over the agent
processEnv := append(os.Environ(), env...)

// The process that will run the bootstrap script
// The process that will run the job executor script
runner.process = process.New(l, process.Config{
Path: cmd[0],
Args: cmd[1:],
Expand Down
33 changes: 20 additions & 13 deletions clicommand/agent_start.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const startDescription = `Usage:
Description:
When a job is ready to run it will call the "bootstrap-script"
When a job is ready to run it will call the "job-executor-script"
and pass it all the environment variables required for the job to run.
This script is responsible for checking out the code, and running the
actual build script defined in the pipeline.
Expand All @@ -54,8 +54,8 @@ Example:
// Adding config requires changes in a few different spots
// - The AgentStartConfig struct with a cli parameter
// - As a flag in the AgentStartCommand (with matching env)
// - Into an env to be passed to the bootstrap in agent/job_runner.go, createEnvironment()
// - Into clicommand/bootstrap.go to read it from the env into the bootstrap config
// - Into an env to be passed to the job executor in agent/job_runner.go, createEnvironment()
// - Into clicommand/exec-job.go to read it from the env into the job executor config

type AgentStartConfig struct {
Config string `cli:"config"`
Expand All @@ -64,7 +64,7 @@ type AgentStartConfig struct {
AcquireJob string `cli:"acquire-job"`
DisconnectAfterJob bool `cli:"disconnect-after-job"`
DisconnectAfterIdleTimeout int `cli:"disconnect-after-idle-timeout"`
BootstrapScript string `cli:"bootstrap-script" normalize:"commandpath"`
JobExecutorScript string `cli:"job-executor-script" normalize:"commandpath"`
CancelGracePeriod int `cli:"cancel-grace-period"`
EnableJobLogTmpfile bool `cli:"enable-job-log-tmpfile"`
BuildPath string `cli:"build-path" normalize:"filepath" validate:"required"`
Expand Down Expand Up @@ -134,6 +134,7 @@ type AgentStartConfig struct {
MetaDataGCP bool `cli:"meta-data-gcp" deprecated-and-renamed-to:"TagsFromGCP"`
TagsFromEC2 bool `cli:"tags-from-ec2" deprecated-and-renamed-to:"TagsFromEC2MetaData"`
TagsFromGCP bool `cli:"tags-from-gcp" deprecated-and-renamed-to:"TagsFromGCPMetaData"`
BootstrapScript string `cli:"bootstrap-script" deprecated-and-renamed-to:"JobExecutorScript" normalize:"commandpath"`
DisconnectAfterJobTimeout int `cli:"disconnect-after-job-timeout" deprecated:"Use disconnect-after-idle-timeout instead"`
}

Expand Down Expand Up @@ -417,9 +418,15 @@ var AgentStartCommand = cli.Command{
cli.StringFlag{
Name: "bootstrap-script",
Value: "",
Usage: "The command that is executed for bootstrapping a job, defaults to the bootstrap sub-command of this binary",
Usage: "The command that is executed for bootstrapping a job, defaults to the exec-job sub-command of this binary",
EnvVar: "BUILDKITE_BOOTSTRAP_SCRIPT_PATH",
},
cli.StringFlag{
Name: "job-executor-script",
Value: "",
Usage: "The command that is executed for running a job, defaults to the exec-job sub-command of this binary",
EnvVar: "BUILDKITE_JOB_EXECUTOR_SCRIPT_PATH",
},
cli.StringFlag{
Name: "build-path",
Value: "",
Expand Down Expand Up @@ -630,7 +637,7 @@ var AgentStartCommand = cli.Command{
done := HandleGlobalFlags(l, cfg)
defer done()

// Remove any config env from the environment to prevent them propagating to bootstrap
// Remove any config env from the environment to prevent them propagating to job execution
err = UnsetConfigFromEnvironment(c)
if err != nil {
fmt.Printf("%s", err)
Expand All @@ -650,12 +657,12 @@ var AgentStartCommand = cli.Command{
}

// Set a useful default for the bootstrap script
if cfg.BootstrapScript == "" {
if cfg.JobExecutorScript == "" {
exePath, err := os.Executable()
if err != nil {
l.Fatal("Unable to find executable path for bootstrap")
l.Fatal("Unable to find our executable path to construct the job executor script: %v", err)
}
cfg.BootstrapScript = fmt.Sprintf("%s bootstrap", shellwords.Quote(exePath))
cfg.JobExecutorScript = fmt.Sprintf("%s exec-job", shellwords.Quote(exePath))
}

isSetNoPlugins := c.IsSet("no-plugins")
Expand Down Expand Up @@ -738,14 +745,14 @@ var AgentStartCommand = cli.Command{
DatadogDistributions: cfg.MetricsDatadogDistributions,
})

// Sense check supported tracing backends, we don't want bootstrapped jobs to silently have no tracing
// Sense check supported tracing backends, we don't want jobs to silently have no tracing
if _, has := tracetools.ValidTracingBackends[cfg.TracingBackend]; !has {
l.Fatal("The given tracing backend %q is not supported. Valid backends are: %q", cfg.TracingBackend, maps.Keys(tracetools.ValidTracingBackends))
}

// AgentConfiguration is the runtime configuration for an agent
agentConf := agent.AgentConfiguration{
BootstrapScript: cfg.BootstrapScript,
JobExecutorScript: cfg.JobExecutorScript,
BuildPath: cfg.BuildPath,
GitMirrorsPath: cfg.GitMirrorsPath,
GitMirrorsLockTimeout: cfg.GitMirrorsLockTimeout,
Expand Down Expand Up @@ -807,7 +814,7 @@ var AgentStartCommand = cli.Command{
l.WithFields(logger.StringField(`path`, agentConf.ConfigPath)).Info("Configuration loaded")
}

l.Debug("Bootstrap command: %s", agentConf.BootstrapScript)
l.Debug("Job Exec command: %s", agentConf.JobExecutorScript)
l.Debug("Build path: %s", agentConf.BuildPath)
l.Debug("Hooks directory: %s", agentConf.HooksPath)
l.Debug("Plugins directory: %s", agentConf.PluginsPath)
Expand Down Expand Up @@ -841,7 +848,7 @@ var AgentStartCommand = cli.Command{
l.Fatal("Failed to parse cancel-signal: %v", err)
}

// confirm the BuildPath is exists. The bootstrap is going to write to it when a job executes,
// confirm the BuildPath is exists. The job executor is going to write to it when a job executes,
// so we may as well check that'll work now and fail early if it's a problem
if !utils.FileExists(agentConf.BuildPath) {
l.Info("Build Path doesn't exist, creating it (%s)", agentConf.BuildPath)
Expand Down
Loading

0 comments on commit 69f0510

Please sign in to comment.