Documentation ¶
Overview ¶
Package clicommand contains the definitions of buildkite-agent subcommands.
It is intended for internal use by buildkite-agent only.
Index ¶
- Constants
- Variables
- func AddToRedactor(ctx context.Context, l logger.Logger, client *jobapi.Client, secrets ...string) error
- func CreateLogger(cfg any) logger.Logger
- func DefaultShell() string
- func HandleGlobalFlags(ctx context.Context, l logger.Logger, cfg any) (context.Context, func())
- func HandleProfileFlag(l logger.Logger, cfg any) func()
- func ParseSecrets(l logger.Logger, cfg RedactorAddConfig, secretsReader io.Reader) ([]string, error)
- func PrintMessageAndReturnExitCode(err error) int
- func Profile(l logger.Logger, mode string) func()
- func UnsetConfigFromEnvironment(c *cli.Context) error
- type AcknowledgementsConfig
- type AgentStartConfig
- type AnnotateConfig
- type AnnotationRemoveConfig
- type ArtifactDownloadConfig
- type ArtifactSearchConfig
- type ArtifactShasumConfig
- type ArtifactUploadConfig
- type BootstrapConfig
- type BuildCancelConfig
- type EnvDumpConfig
- type EnvGetConfig
- type EnvSetConfig
- type EnvUnsetConfig
- type ExitError
- type GitCredentialsHelperConfig
- type LockAcquireConfig
- type LockDoConfig
- type LockDoneConfig
- type LockGetConfig
- type LockReleaseConfig
- type MetaDataExistsConfig
- type MetaDataGetConfig
- type MetaDataKeysConfig
- type MetaDataSetConfig
- type OIDCTokenConfig
- type PipelineUploadConfig
- type RedactorAddConfig
- type SecretGetConfig
- type SilentExitError
- type StepGetConfig
- type StepUpdateConfig
- type ToolKeygenConfig
- type ToolSignConfig
Constants ¶
const ( FormatStringJSON = "json" FormatStringNone = "none" )
Note: if you add a new format string, make sure to add it to `secretsFormats` and update the usage string in LogRedactCommand
const (
DefaultEndpoint = "https://agent.buildkite.com/v3"
)
Variables ¶
var ( AgentAccessTokenFlag = cli.StringFlag{ Name: "agent-access-token", Value: "", Usage: "The access token used to identify the agent", EnvVar: "BUILDKITE_AGENT_ACCESS_TOKEN", } AgentRegisterTokenFlag = cli.StringFlag{ Name: "token", Value: "", Usage: "Your account agent token", EnvVar: "BUILDKITE_AGENT_TOKEN", } EndpointFlag = cli.StringFlag{ Name: "endpoint", Value: DefaultEndpoint, Usage: "The Agent API endpoint", EnvVar: "BUILDKITE_AGENT_ENDPOINT", } NoHTTP2Flag = cli.BoolFlag{ Name: "no-http2", Usage: "Disable HTTP2 when communicating with the Agent API.", EnvVar: "BUILDKITE_NO_HTTP2", } DebugFlag = cli.BoolFlag{ Name: "debug", Usage: "Enable debug mode. Synonym for ′--log-level debug′. Takes precedence over ′--log-level′", EnvVar: "BUILDKITE_AGENT_DEBUG", } LogLevelFlag = cli.StringFlag{ Name: "log-level", Value: "notice", Usage: "Set the log level for the agent, making logging more or less verbose. Defaults to notice. Allowed values are: debug, info, error, warn, fatal", EnvVar: "BUILDKITE_AGENT_LOG_LEVEL", } ProfileFlag = cli.StringFlag{ Name: "profile", Usage: "Enable a profiling mode, either cpu, memory, mutex or block", EnvVar: "BUILDKITE_AGENT_PROFILE", } DebugHTTPFlag = cli.BoolFlag{ Name: "debug-http", Usage: "Enable HTTP debug mode, which dumps all request and response bodies to the log", EnvVar: "BUILDKITE_AGENT_DEBUG_HTTP", } TraceHTTPFlag = cli.BoolFlag{ Name: "trace-http", Usage: "Enable HTTP trace mode, which logs timings for each HTTP request. Timings are logged at the debug level unless a request fails at the network level in which case they are logged at the error level", EnvVar: "BUILDKITE_AGENT_TRACE_HTTP", } NoColorFlag = cli.BoolFlag{ Name: "no-color", Usage: "Don't show colors in logging", EnvVar: "BUILDKITE_AGENT_NO_COLOR", } StrictSingleHooksFlag = cli.BoolFlag{ Name: "strict-single-hooks", Usage: "Enforces that only one checkout hook, and only one command hook, can be run", EnvVar: "BUILDKITE_STRICT_SINGLE_HOOKS", } KubernetesExecFlag = cli.BoolFlag{ Name: "kubernetes-exec", Usage: "This is intended to be used only by the Buildkite k8s stack " + "(github.com/buildkite/agent-stack-k8s); it enables a Unix socket for transporting " + "logs and exit statuses between containers in a pod", EnvVar: "BUILDKITE_KUBERNETES_EXEC", } NoMultipartArtifactUploadFlag = cli.BoolFlag{ Name: "no-multipart-artifact-upload", Usage: "For Buildkite-hosted artifacts, disables the use of multipart uploads. Has no effect on uploads to other destinations such as custom cloud buckets", EnvVar: "BUILDKITE_NO_MULTIPART_ARTIFACT_UPLOAD", } ExperimentsFlag = cli.StringSliceFlag{ Name: "experiment", Value: &cli.StringSlice{}, Usage: "Enable experimental features within the buildkite-agent", EnvVar: "BUILDKITE_AGENT_EXPERIMENT", } RedactedVars = cli.StringSliceFlag{ Name: "redacted-vars", Usage: "Pattern of environment variable names containing sensitive values", EnvVar: "BUILDKITE_REDACTED_VARS", Value: &cli.StringSlice{ "*_PASSWORD", "*_SECRET", "*_TOKEN", "*_PRIVATE_KEY", "*_ACCESS_KEY", "*_SECRET_KEY", "*_CONNECTION_STRING", }, } TraceContextEncodingFlag = cli.StringFlag{ Name: "trace-context-encoding", Usage: "Sets the inner encoding for BUILDKITE_TRACE_CONTEXT. Must be either json or gob", Value: "gob", EnvVar: "BUILDKITE_TRACE_CONTEXT_ENCODING", } )
var ( ErrNoPipeline = errors.New("no pipeline file found") ErrUseGraphQL = errors.New( "either provide the pipeline YAML, and the repository URL, " + "or provide a GraphQL token to allow them to be retrieved from Buildkite", ) ErrNotFound = errors.New("pipeline not found") )
var AcknowledgementsCommand = cli.Command{ Name: "acknowledgements", Usage: "Prints the licenses and notices of open source software incorporated into this software.", Description: acknowledgementsHelpDescription, Action: func(c *cli.Context) error { ctx := context.Background() _, _, _, _, done := setupLoggerAndConfig[AcknowledgementsConfig](ctx, c) defer done() f, err := files.Open("ACKNOWLEDGEMENTS.md.gz") if err != nil { f, err = files.Open("dummy.md.gz") if err != nil { return fmt.Errorf("couldn't open any embedded acknowledgements files: %w", err) } } r, err := gzip.NewReader(f) if err != nil { return fmt.Errorf("couldn't create a gzip reader: %w", err) } if _, err := io.Copy(c.App.Writer, r); err != nil { return fmt.Errorf("couldn't copy acknowledgments to output: %w", err) } return nil }, }
var AgentStartCommand = cli.Command{ Name: "start", Usage: "Starts a Buildkite agent", Description: startDescription, Flags: []cli.Flag{ cli.StringFlag{ Name: "config", Value: "", Usage: "Path to a configuration file", EnvVar: "BUILDKITE_AGENT_CONFIG", }, cli.StringFlag{ Name: "name", Value: "", Usage: "The name of the agent", EnvVar: "BUILDKITE_AGENT_NAME", }, cli.StringFlag{ Name: "priority", Value: "", Usage: "The priority of the agent (higher priorities are assigned work first)", EnvVar: "BUILDKITE_AGENT_PRIORITY", }, cli.StringFlag{ Name: "acquire-job", Value: "", Usage: "Start this agent and only run the specified job, disconnecting after it's finished", EnvVar: "BUILDKITE_AGENT_ACQUIRE_JOB", }, cli.BoolFlag{ Name: "disconnect-after-job", Usage: "Disconnect the agent after running exactly one job. When used in conjunction with the ′--spawn′ flag, each worker booted will run exactly one job", EnvVar: "BUILDKITE_AGENT_DISCONNECT_AFTER_JOB", }, cli.IntFlag{ Name: "disconnect-after-idle-timeout", Value: 0, Usage: "The maximum idle time in seconds to wait for a job before disconnecting. The default of 0 means no timeout", EnvVar: "BUILDKITE_AGENT_DISCONNECT_AFTER_IDLE_TIMEOUT", }, cancelGracePeriodFlag, cli.BoolFlag{ Name: "enable-job-log-tmpfile", Usage: "Store the job logs in a temporary file ′BUILDKITE_JOB_LOG_TMPFILE′ that is accessible during the job and removed at the end of the job", EnvVar: "BUILDKITE_ENABLE_JOB_LOG_TMPFILE", }, cli.StringFlag{ Name: "job-log-path", Usage: "Location to store job logs created by configuring ′enable-job-log-tmpfile`, by default job log will be stored in TempDir", EnvVar: "BUILDKITE_JOB_LOG_PATH", }, cli.BoolFlag{ Name: "write-job-logs-to-stdout", Usage: "Writes job logs to the agent process' stdout. This simplifies log collection if running agents in Docker.", EnvVar: "BUILDKITE_WRITE_JOB_LOGS_TO_STDOUT", }, cli.StringFlag{ Name: "shell", Value: DefaultShell(), Usage: "The shell command used to interpret build commands, e.g /bin/bash -e -c", EnvVar: "BUILDKITE_SHELL", }, cli.StringFlag{ Name: "queue", Usage: "The queue the agent will listen to for jobs. If not set, the agent will use the default queue. Overwrites the queue tag in the agent's tags", EnvVar: "BUILDKITE_AGENT_QUEUE", }, cli.StringSliceFlag{ Name: "tags", Value: &cli.StringSlice{}, Usage: "A comma-separated list of tags for the agent (for example, \"linux\" or \"mac,xcode=8\")", EnvVar: "BUILDKITE_AGENT_TAGS", }, cli.BoolFlag{ Name: "tags-from-host", Usage: "Include tags from the host (hostname, machine-id, os)", EnvVar: "BUILDKITE_AGENT_TAGS_FROM_HOST", }, cli.StringSliceFlag{ Name: "tags-from-ec2-meta-data", Value: &cli.StringSlice{}, Usage: "Include the default set of host EC2 meta-data as tags (instance-id, instance-type, ami-id, and instance-life-cycle)", EnvVar: "BUILDKITE_AGENT_TAGS_FROM_EC2_META_DATA", }, cli.StringSliceFlag{ Name: "tags-from-ec2-meta-data-paths", Value: &cli.StringSlice{}, Usage: "Include additional tags fetched from EC2 meta-data using tag & path suffix pairs, e.g \"tag_name=path/to/value\"", EnvVar: "BUILDKITE_AGENT_TAGS_FROM_EC2_META_DATA_PATHS", }, cli.BoolFlag{ Name: "tags-from-ec2-tags", Usage: "Include the host's EC2 tags as tags", EnvVar: "BUILDKITE_AGENT_TAGS_FROM_EC2_TAGS", }, cli.BoolFlag{ Name: "tags-from-ecs-meta-data", Usage: "Include the host's ECS meta-data as tags (container-name, image, and task-arn)", EnvVar: "BUILDKITE_AGENT_TAGS_FROM_ECS_META_DATA", }, cli.StringSliceFlag{ Name: "tags-from-gcp-meta-data", Value: &cli.StringSlice{}, Usage: "Include the default set of host Google Cloud instance meta-data as tags (instance-id, machine-type, preemptible, project-id, region, and zone)", EnvVar: "BUILDKITE_AGENT_TAGS_FROM_GCP_META_DATA", }, cli.StringSliceFlag{ Name: "tags-from-gcp-meta-data-paths", Value: &cli.StringSlice{}, Usage: "Include additional tags fetched from Google Cloud instance meta-data using tag & path suffix pairs, e.g \"tag_name=path/to/value\"", EnvVar: "BUILDKITE_AGENT_TAGS_FROM_GCP_META_DATA_PATHS", }, cli.BoolFlag{ Name: "tags-from-gcp-labels", Usage: "Include the host's Google Cloud instance labels as tags", EnvVar: "BUILDKITE_AGENT_TAGS_FROM_GCP_LABELS", }, cli.DurationFlag{ Name: "wait-for-ec2-tags-timeout", Usage: "The amount of time to wait for tags from EC2 before proceeding", EnvVar: "BUILDKITE_AGENT_WAIT_FOR_EC2_TAGS_TIMEOUT", Value: time.Second * 10, }, cli.DurationFlag{ Name: "wait-for-ec2-meta-data-timeout", Usage: "The amount of time to wait for meta-data from EC2 before proceeding", EnvVar: "BUILDKITE_AGENT_WAIT_FOR_EC2_META_DATA_TIMEOUT", Value: time.Second * 10, }, cli.DurationFlag{ Name: "wait-for-ecs-meta-data-timeout", Usage: "The amount of time to wait for meta-data from ECS before proceeding", EnvVar: "BUILDKITE_AGENT_WAIT_FOR_ECS_META_DATA_TIMEOUT", Value: time.Second * 10, }, cli.DurationFlag{ Name: "wait-for-gcp-labels-timeout", Usage: "The amount of time to wait for labels from GCP before proceeding", EnvVar: "BUILDKITE_AGENT_WAIT_FOR_GCP_LABELS_TIMEOUT", Value: time.Second * 10, }, cli.StringFlag{ Name: "git-checkout-flags", Value: "-f", Usage: "Flags to pass to \"git checkout\" command", EnvVar: "BUILDKITE_GIT_CHECKOUT_FLAGS", }, cli.StringFlag{ Name: "git-clone-flags", Value: "-v", Usage: "Flags to pass to the \"git clone\" command", EnvVar: "BUILDKITE_GIT_CLONE_FLAGS", }, cli.StringFlag{ Name: "git-clean-flags", Value: "-ffxdq", Usage: "Flags to pass to \"git clean\" command", EnvVar: "BUILDKITE_GIT_CLEAN_FLAGS", }, cli.StringFlag{ Name: "git-fetch-flags", Value: "-v --prune", Usage: "Flags to pass to \"git fetch\" command", EnvVar: "BUILDKITE_GIT_FETCH_FLAGS", }, cli.StringFlag{ Name: "git-clone-mirror-flags", Value: "-v", Usage: "Flags to pass to the \"git clone\" command when used for mirroring", EnvVar: "BUILDKITE_GIT_CLONE_MIRROR_FLAGS", }, cli.StringFlag{ Name: "git-mirrors-path", Value: "", Usage: "Path to where mirrors of git repositories are stored", EnvVar: "BUILDKITE_GIT_MIRRORS_PATH", }, cli.IntFlag{ Name: "git-mirrors-lock-timeout", Value: 300, Usage: "Seconds to lock a git mirror during clone, should exceed your longest checkout", EnvVar: "BUILDKITE_GIT_MIRRORS_LOCK_TIMEOUT", }, cli.BoolFlag{ Name: "git-mirrors-skip-update", Usage: "Skip updating the Git mirror", EnvVar: "BUILDKITE_GIT_MIRRORS_SKIP_UPDATE", }, 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", EnvVar: "BUILDKITE_BOOTSTRAP_SCRIPT_PATH", }, cli.StringFlag{ Name: "build-path", Value: "", Usage: "Path to where the builds will run from", EnvVar: "BUILDKITE_BUILD_PATH", }, cli.StringFlag{ Name: "hooks-path", Value: "", Usage: "Directory where the hook scripts are found", EnvVar: "BUILDKITE_HOOKS_PATH", }, cli.StringFlag{ Name: "sockets-path", Value: defaultSocketsPath(), Usage: "Directory where the agent will place sockets", EnvVar: "BUILDKITE_SOCKETS_PATH", }, cli.StringFlag{ Name: "plugins-path", Value: "", Usage: "Directory where the plugins are saved to", EnvVar: "BUILDKITE_PLUGINS_PATH", }, cli.BoolFlag{ Name: "no-ansi-timestamps", Usage: "Do not insert ANSI timestamp codes at the start of each line of job output", EnvVar: "BUILDKITE_NO_ANSI_TIMESTAMPS", }, cli.BoolFlag{ Name: "timestamp-lines", Usage: "Prepend timestamps on each line of job output. Has no effect unless --no-ansi-timestamps is also used", EnvVar: "BUILDKITE_TIMESTAMP_LINES", }, cli.StringFlag{ Name: "health-check-addr", Usage: "Start an HTTP server on this addr:port that returns whether the agent is healthy, disabled by default", EnvVar: "BUILDKITE_AGENT_HEALTH_CHECK_ADDR", }, cli.BoolFlag{ Name: "no-pty", Usage: "Do not run jobs within a pseudo terminal", EnvVar: "BUILDKITE_NO_PTY", }, cli.BoolFlag{ Name: "no-ssh-keyscan", Usage: "Don't automatically run ssh-keyscan before checkout", EnvVar: "BUILDKITE_NO_SSH_KEYSCAN", }, cli.BoolFlag{ Name: "no-command-eval", Usage: "Don't allow this agent to run arbitrary console commands, including plugins", EnvVar: "BUILDKITE_NO_COMMAND_EVAL", }, cli.BoolFlag{ Name: "no-plugins", Usage: "Don't allow this agent to load plugins", EnvVar: "BUILDKITE_NO_PLUGINS", }, cli.BoolTFlag{ Name: "no-plugin-validation", Usage: "Don't validate plugin configuration and requirements", EnvVar: "BUILDKITE_NO_PLUGIN_VALIDATION", }, cli.BoolFlag{ Name: "no-local-hooks", Usage: "Don't allow local hooks to be run from checked out repositories", EnvVar: "BUILDKITE_NO_LOCAL_HOOKS", }, cli.BoolFlag{ Name: "no-git-submodules", Usage: "Don't automatically checkout git submodules", EnvVar: "BUILDKITE_NO_GIT_SUBMODULES,BUILDKITE_DISABLE_GIT_SUBMODULES", }, cli.BoolFlag{ Name: "no-feature-reporting", Usage: "Disables sending a list of enabled features back to the Buildkite mothership. We use this information to measure feature usage, but if you're not comfortable sharing that information then that's totally okay :)", EnvVar: "BUILDKITE_AGENT_NO_FEATURE_REPORTING", }, cli.StringSliceFlag{ Name: "allowed-repositories", Value: &cli.StringSlice{}, Usage: `A comma-separated list of regular expressions representing repositories the agent is allowed to clone (for example, "^git@github.com:buildkite/.*" or "^https://github.com/buildkite/.*")`, EnvVar: "BUILDKITE_ALLOWED_REPOSITORIES", }, cli.BoolFlag{ Name: "enable-environment-variable-allowlist", Usage: "Only run jobs where all environment variables are allowed by the allowed-environment-variables option, or have been set by Buildkite", EnvVar: "BUILDKITE_ENABLE_ENVIRONMENT_VARIABLE_ALLOWLIST", }, cli.StringSliceFlag{ Name: "allowed-environment-variables", Value: &cli.StringSlice{}, Usage: `A comma-separated list of regular expressions representing environment variables the agent will pass to jobs (for example, "^MYAPP_.*$"). Environment variables set by Buildkite will always be allowed. Requires --enable-environment-variable-allowlist to be set`, EnvVar: "BUILDKITE_ALLOWED_ENVIRONMENT_VARIABLES", }, cli.StringSliceFlag{ Name: "allowed-plugins", Value: &cli.StringSlice{}, Usage: `A comma-separated list of regular expressions representing plugins the agent is allowed to use (for example, "^buildkite-plugins/.*$" or "^/var/lib/buildkite-plugins/.*")`, EnvVar: "BUILDKITE_ALLOWED_PLUGINS", }, cli.BoolFlag{ Name: "metrics-datadog", Usage: "Send metrics to DogStatsD for Datadog", EnvVar: "BUILDKITE_METRICS_DATADOG", }, cli.StringFlag{ Name: "metrics-datadog-host", Usage: "The dogstatsd instance to send metrics to using udp", EnvVar: "BUILDKITE_METRICS_DATADOG_HOST", Value: "127.0.0.1:8125", }, cli.BoolFlag{ Name: "metrics-datadog-distributions", Usage: "Use Datadog Distributions for Timing metrics", EnvVar: "BUILDKITE_METRICS_DATADOG_DISTRIBUTIONS", }, cli.StringFlag{ Name: "log-format", Usage: "The format to use for the logger output", EnvVar: "BUILDKITE_LOG_FORMAT", Value: "text", }, cli.IntFlag{ Name: "spawn", Usage: "The number of agents to spawn in parallel (mutually exclusive with --spawn-per-cpu)", Value: 1, EnvVar: "BUILDKITE_AGENT_SPAWN", }, cli.IntFlag{ Name: "spawn-per-cpu", Usage: "The number of agents to spawn per cpu in parallel (mutually exclusive with --spawn)", Value: 0, EnvVar: "BUILDKITE_AGENT_SPAWN_PER_CPU", }, cli.BoolFlag{ Name: "spawn-with-priority", Usage: "Assign priorities to every spawned agent (when using --spawn or --spawn-per-cpu) equal to the agent's index", EnvVar: "BUILDKITE_AGENT_SPAWN_WITH_PRIORITY", }, cancelSignalFlag, signalGracePeriodSecondsFlag, cli.StringFlag{ Name: "tracing-backend", Usage: `Enable tracing for build jobs by specifying a backend, "datadog" or "opentelemetry"`, EnvVar: "BUILDKITE_TRACING_BACKEND", Value: "", }, cli.StringFlag{ Name: "tracing-service-name", Usage: "Service name to use when reporting traces.", EnvVar: "BUILDKITE_TRACING_SERVICE_NAME", Value: "buildkite-agent", }, cli.StringFlag{ Name: "verification-jwks-file", Usage: "Path to a file containing a JSON Web Key Set (JWKS), used to verify job signatures. ", EnvVar: "BUILDKITE_AGENT_VERIFICATION_JWKS_FILE", }, cli.StringFlag{ Name: "signing-jwks-file", Usage: `Path to a file containing a signing key. Passing this flag enables pipeline signing for all pipelines uploaded by this agent. For hmac-sha256, the raw file content is used as the shared key. When using Docker containers to upload pipeline steps dynamically, use environment variable propagation (for example, "docker run -e BUILDKITE_AGENT_JWKS_FILE") to allow all steps within the pipeline to be signed.`, EnvVar: "BUILDKITE_AGENT_SIGNING_JWKS_FILE", }, cli.StringFlag{ Name: "signing-jwks-key-id", Usage: "The JWKS key ID to use when signing the pipeline. If omitted, and the signing JWKS contains only one key, that key will be used.", EnvVar: "BUILDKITE_AGENT_SIGNING_JWKS_KEY_ID", }, cli.StringFlag{ Name: "signing-aws-kms-key", Usage: "The KMS KMS key ID, or key alias used when signing and verifying the pipeline.", EnvVar: "BUILDKITE_AGENT_SIGNING_AWS_KMS_KEY", }, cli.BoolFlag{ Name: "debug-signing", Usage: "Enable debug logging for pipeline signing. This can potentially leak secrets to the logs as it prints each step in full before signing. Requires debug logging to be enabled", EnvVar: "BUILDKITE_AGENT_DEBUG_SIGNING", }, cli.StringFlag{ Name: "verification-failure-behavior", Value: agent.VerificationBehaviourBlock, Usage: fmt.Sprintf("The behavior when a job is received without a valid verifiable signature (without a signature, with an invalid signature, or with a signature that fails verification). One of: %v. Defaults to %s", verificationFailureBehaviors, agent.VerificationBehaviourBlock), EnvVar: "BUILDKITE_AGENT_JOB_VERIFICATION_NO_SIGNATURE_BEHAVIOR", }, cli.StringSliceFlag{ Name: "disable-warnings-for", Usage: "A list of warning IDs to disable", EnvVar: "BUILDKITE_AGENT_DISABLE_WARNINGS_FOR", }, AgentRegisterTokenFlag, EndpointFlag, NoHTTP2Flag, DebugHTTPFlag, TraceHTTPFlag, NoColorFlag, DebugFlag, LogLevelFlag, ExperimentsFlag, ProfileFlag, RedactedVars, StrictSingleHooksFlag, KubernetesExecFlag, TraceContextEncodingFlag, NoMultipartArtifactUploadFlag, cli.StringSliceFlag{ Name: "meta-data", Value: &cli.StringSlice{}, Hidden: true, EnvVar: "BUILDKITE_AGENT_META_DATA", }, cli.BoolFlag{ Name: "meta-data-ec2", Hidden: true, EnvVar: "BUILDKITE_AGENT_META_DATA_EC2", }, cli.BoolFlag{ Name: "meta-data-ec2-tags", Hidden: true, EnvVar: "BUILDKITE_AGENT_TAGS_FROM_EC2_TAGS", }, cli.BoolFlag{ Name: "meta-data-gcp", Hidden: true, EnvVar: "BUILDKITE_AGENT_META_DATA_GCP", }, cli.BoolFlag{ Name: "no-automatic-ssh-fingerprint-verification", Hidden: true, EnvVar: "BUILDKITE_NO_AUTOMATIC_SSH_FINGERPRINT_VERIFICATION", }, cli.BoolFlag{ Name: "tags-from-ec2", Usage: "Include the host's EC2 meta-data as tags (instance-id, instance-type, and ami-id)", EnvVar: "BUILDKITE_AGENT_TAGS_FROM_EC2", }, cli.BoolFlag{ Name: "tags-from-gcp", Usage: "Include the host's Google Cloud instance meta-data as tags (instance-id, machine-type, preemptible, project-id, region, and zone)", EnvVar: "BUILDKITE_AGENT_TAGS_FROM_GCP", }, cli.IntFlag{ Name: "disconnect-after-job-timeout", Hidden: true, Usage: "When --disconnect-after-job is specified, the number of seconds to wait for a job before shutting down", EnvVar: "BUILDKITE_AGENT_DISCONNECT_AFTER_JOB_TIMEOUT", }, }, Action: func(c *cli.Context) error { ctx := context.Background() ctx, cfg, l, configFile, done := setupLoggerAndConfig[AgentStartConfig](ctx, c, withConfigFilePaths( defaultConfigFilePaths(), )) defer done() if err := UnsetConfigFromEnvironment(c); err != nil { return fmt.Errorf("failed to unset config from environment: %w", err) } if cfg.VerificationJWKSFile != "" { if !slices.Contains(verificationFailureBehaviors, cfg.VerificationFailureBehavior) { return fmt.Errorf( "invalid job verification no signature behavior %q. Must be one of: %v", cfg.VerificationFailureBehavior, verificationFailureBehaviors, ) } } if runtime.GOOS == "windows" { cfg.NoPTY = true } if cfg.BootstrapScript == "" { exePath, err := os.Executable() if err != nil { return errors.New("unable to find executable path for bootstrap") } cfg.BootstrapScript = fmt.Sprintf("%s bootstrap", shellwords.Quote(exePath)) } isSetNoPlugins := c.IsSet("no-plugins") if configFile != nil { if _, exists := configFile.Config["no-plugins"]; exists { isSetNoPlugins = true } } if isSetNoPlugins && !cfg.NoPlugins { msg := "Plugins have been specifically enabled, despite %s being enabled. " + "Plugins can execute arbitrary hooks and commands, make sure you are " + "whitelisting your plugins in " + "your environment hook." switch { case cfg.NoCommandEval: l.Warn(msg, "no-command-eval") case cfg.NoLocalHooks: l.Warn(msg, "no-local-hooks") } } if (cfg.NoCommandEval || cfg.NoLocalHooks) && !isSetNoPlugins { cfg.NoPlugins = true } if cfg.Shell == "" { cfg.Shell = DefaultShell() } if cfg.DisconnectAfterJobTimeout > 0 { cfg.DisconnectAfterIdleTimeout = cfg.DisconnectAfterJobTimeout } var ec2TagTimeout time.Duration if t := cfg.WaitForEC2TagsTimeout; t != "" { var err error ec2TagTimeout, err = time.ParseDuration(t) if err != nil { return fmt.Errorf("failed to parse ec2 tag timeout: %w", err) } } var ec2MetaDataTimeout time.Duration if t := cfg.WaitForEC2MetaDataTimeout; t != "" { var err error ec2MetaDataTimeout, err = time.ParseDuration(t) if err != nil { return fmt.Errorf("failed to parse ec2 meta-data timeout: %w", err) } } var ecsMetaDataTimeout time.Duration if t := cfg.WaitForECSMetaDataTimeout; t != "" { var err error ecsMetaDataTimeout, err = time.ParseDuration(t) if err != nil { return fmt.Errorf("failed to parse ecs meta-data timeout: %w", err) } } var gcpLabelsTimeout time.Duration if t := cfg.WaitForGCPLabelsTimeout; t != "" { var err error gcpLabelsTimeout, err = time.ParseDuration(t) if err != nil { return fmt.Errorf("failed to parse gcp labels timeout: %w", err) } } signalGracePeriod, err := signalGracePeriod(cfg.CancelGracePeriod, cfg.SignalGracePeriodSeconds) if err != nil { return err } if _, err := tracetools.ParseEncoding(cfg.TraceContextEncoding); err != nil { return fmt.Errorf("while parsing trace context encoding: %v", err) } mc := metrics.NewCollector(l, metrics.CollectorConfig{ Datadog: cfg.MetricsDatadog, DatadogHost: cfg.MetricsDatadogHost, DatadogDistributions: cfg.MetricsDatadogDistributions, }) if _, has := tracetools.ValidTracingBackends[cfg.TracingBackend]; !has { return fmt.Errorf( "the given tracing backend %q is not supported. Valid backends are: %q", cfg.TracingBackend, maps.Keys(tracetools.ValidTracingBackends), ) } if experiments.IsEnabled(ctx, experiments.AgentAPI) { shutdown, err := runAgentAPI(ctx, l, cfg.SocketsPath) if err != nil { return err } defer shutdown() } // if the agent is provided a KMS key ID, it should use the KMS signer, otherwise // it should load the JWKS from the file var verificationJWKS any switch { case cfg.SigningAWSKMSKey != "": var logMode aws.ClientLogMode if cfg.DebugSigning { logMode = aws.LogRetries | aws.LogRequest } awscfg, err := awslib.GetConfigV2( ctx, config.WithClientLogMode(logMode), ) if err != nil { return err } verificationJWKS, err = awssigner.NewKMS(kms.NewFromConfig(awscfg), cfg.SigningAWSKMSKey) if err != nil { return fmt.Errorf("couldn't create KMS signer: %w", err) } case cfg.VerificationJWKSFile != "": var err error verificationJWKS, err = parseAndValidateJWKS(ctx, "verification", cfg.VerificationJWKSFile) if err != nil { l.Fatal("Verification JWKS failed validation: %v", err) } } if cfg.SigningJWKSFile != "" { _, err := parseAndValidateJWKS(ctx, "signing", cfg.SigningJWKSFile) if err != nil { l.Fatal("Signing JWKS failed validation: %v", err) } } if len(cfg.AllowedEnvironmentVariables) > 0 && !cfg.EnableEnvironmentVariableAllowList { l.Fatal("allowed-environment-variables is set, but enable-environment-variable-allowlist is not set") } var allowedEnvironmentVariables []*regexp.Regexp if cfg.EnableEnvironmentVariableAllowList { allowedEnvironmentVariables = append(allowedEnvironmentVariables, buildkiteSetEnvironmentVariables...) for _, v := range cfg.AllowedEnvironmentVariables { re, err := regexp.Compile(v) if err != nil { l.Fatal("Regex %s in allowed-environment-variables failed to compile: %v", v, err) } allowedEnvironmentVariables = append(allowedEnvironmentVariables, re) } } agentConf := agent.AgentConfiguration{ BootstrapScript: cfg.BootstrapScript, BuildPath: cfg.BuildPath, SocketsPath: cfg.SocketsPath, GitMirrorsPath: cfg.GitMirrorsPath, GitMirrorsLockTimeout: cfg.GitMirrorsLockTimeout, GitMirrorsSkipUpdate: cfg.GitMirrorsSkipUpdate, HooksPath: cfg.HooksPath, PluginsPath: cfg.PluginsPath, GitCheckoutFlags: cfg.GitCheckoutFlags, GitCloneFlags: cfg.GitCloneFlags, GitCloneMirrorFlags: cfg.GitCloneMirrorFlags, GitCleanFlags: cfg.GitCleanFlags, GitFetchFlags: cfg.GitFetchFlags, GitSubmodules: !cfg.NoGitSubmodules, SSHKeyscan: !cfg.NoSSHKeyscan, CommandEval: !cfg.NoCommandEval, PluginsEnabled: !cfg.NoPlugins, PluginValidation: !cfg.NoPluginValidation, LocalHooksEnabled: !cfg.NoLocalHooks, AllowedEnvironmentVariables: allowedEnvironmentVariables, StrictSingleHooks: cfg.StrictSingleHooks, RunInPty: !cfg.NoPTY, ANSITimestamps: !cfg.NoANSITimestamps, TimestampLines: cfg.TimestampLines, DisconnectAfterJob: cfg.DisconnectAfterJob, DisconnectAfterIdleTimeout: cfg.DisconnectAfterIdleTimeout, CancelGracePeriod: cfg.CancelGracePeriod, SignalGracePeriod: signalGracePeriod, EnableJobLogTmpfile: cfg.EnableJobLogTmpfile, JobLogPath: cfg.JobLogPath, WriteJobLogsToStdout: cfg.WriteJobLogsToStdout, LogFormat: cfg.LogFormat, Shell: cfg.Shell, RedactedVars: cfg.RedactedVars, AcquireJob: cfg.AcquireJob, TracingBackend: cfg.TracingBackend, TracingServiceName: cfg.TracingServiceName, TraceContextEncoding: cfg.TraceContextEncoding, AllowMultipartArtifactUpload: !cfg.NoMultipartArtifactUpload, KubernetesExec: cfg.KubernetesExec, SigningJWKSFile: cfg.SigningJWKSFile, SigningJWKSKeyID: cfg.SigningJWKSKeyID, SigningAWSKMSKey: cfg.SigningAWSKMSKey, DebugSigning: cfg.DebugSigning, VerificationJWKS: verificationJWKS, VerificationFailureBehaviour: cfg.VerificationFailureBehavior, DisableWarningsFor: cfg.DisableWarningsFor, } if configFile != nil { agentConf.ConfigPath = configFile.Path } if cfg.LogFormat == "text" { welcomeMessage := "\n" + "%s _ _ _ _ _ _ _ _\n" + " | | (_) | | | | (_) | | |\n" + " | |__ _ _ _| | __| | | ___| |_ ___ __ _ __ _ ___ _ __ | |_\n" + " | '_ \\| | | | | |/ _` | |/ / | __/ _ \\ / _` |/ _` |/ _ \\ '_ \\| __|\n" + " | |_) | |_| | | | (_| | <| | || __/ | (_| | (_| | __/ | | | |_\n" + " |_.__/ \\__,_|_|_|\\__,_|_|\\_\\_|\\__\\___| \\__,_|\\__, |\\___|_| |_|\\__|\n" + " __/ |\n" + " https://buildkite.com/agent |___/\n%s\n" if !cfg.NoColor { fmt.Fprintf(os.Stderr, welcomeMessage, "\x1b[38;5;48m", "\x1b[0m") } else { fmt.Fprintf(os.Stderr, welcomeMessage, "", "") } } else if cfg.LogFormat != "json" { return fmt.Errorf("invalid log format %q. Only 'text' or 'json' are allowed.", cfg.LogFormat) } l.Notice("Starting buildkite-agent v%s with PID: %s", version.Version(), strconv.Itoa(os.Getpid())) l.Notice("The agent source code can be found here: https://github.com/buildkite/agent") l.Notice("For questions and support, email us at: hello@buildkite.com") if agentConf.ConfigPath != "" { l.WithFields(logger.StringField(`path`, agentConf.ConfigPath)).Info("Configuration loaded") } l.Debug("Bootstrap command: %s", agentConf.BootstrapScript) l.Debug("Build path: %s", agentConf.BuildPath) l.Debug("Hooks directory: %s", agentConf.HooksPath) l.Debug("Plugins directory: %s", agentConf.PluginsPath) if exps := experiments.KnownAndEnabled(ctx); len(exps) > 0 { l.WithFields(logger.StringField("experiments", fmt.Sprintf("%v", exps))).Info("Experiments are enabled") } if !agentConf.SSHKeyscan { l.Info("Automatic ssh-keyscan has been disabled") } if !agentConf.CommandEval { l.Info("Evaluating console commands has been disabled") } if !agentConf.PluginsEnabled { l.Info("Plugins have been disabled") } if !agentConf.RunInPty { l.Info("Running builds within a pseudoterminal (PTY) has been disabled") } if agentConf.DisconnectAfterJob { l.Info("Agents will disconnect after a job run has completed") } if agentConf.DisconnectAfterIdleTimeout > 0 { l.Info("Agents will disconnect after %d seconds of inactivity", agentConf.DisconnectAfterIdleTimeout) } if len(cfg.AllowedRepositories) > 0 { agentConf.AllowedRepositories = make([]*regexp.Regexp, 0, len(cfg.AllowedRepositories)) for _, v := range cfg.AllowedRepositories { r, err := regexp.Compile(v) if err != nil { l.Fatal("Regex %s is allowed-repositories failed to compile: %v", v, err) } agentConf.AllowedRepositories = append(agentConf.AllowedRepositories, r) } l.Info("Allowed repositories patterns: %q", agentConf.AllowedRepositories) } if len(cfg.AllowedPlugins) > 0 { agentConf.AllowedPlugins = make([]*regexp.Regexp, 0, len(cfg.AllowedPlugins)) for _, v := range cfg.AllowedPlugins { r, err := regexp.Compile(v) if err != nil { l.Fatal("Regex %s in allowed-plugins failed to compile: %v", v, err) } agentConf.AllowedPlugins = append(agentConf.AllowedPlugins, r) } l.Info("Allowed plugins patterns: %q", agentConf.AllowedPlugins) } cancelSig, err := process.ParseSignal(cfg.CancelSignal) if err != nil { return fmt.Errorf("failed to parse cancel-signal: %w", err) } tags := agent.FetchTags(ctx, l, agent.FetchTagsConfig{ Tags: cfg.Tags, TagsFromK8s: cfg.KubernetesExec, TagsFromEC2MetaData: (cfg.TagsFromEC2MetaData || cfg.TagsFromEC2), TagsFromEC2MetaDataPaths: cfg.TagsFromEC2MetaDataPaths, TagsFromEC2Tags: cfg.TagsFromEC2Tags, TagsFromECSMetaData: cfg.TagsFromECSMetaData, TagsFromGCPMetaData: (cfg.TagsFromGCPMetaData || cfg.TagsFromGCP), TagsFromGCPMetaDataPaths: cfg.TagsFromGCPMetaDataPaths, TagsFromGCPLabels: cfg.TagsFromGCPLabels, TagsFromHost: cfg.TagsFromHost, WaitForEC2TagsTimeout: ec2TagTimeout, WaitForEC2MetaDataTimeout: ec2MetaDataTimeout, WaitForECSMetaDataTimeout: ecsMetaDataTimeout, WaitForGCPLabelsTimeout: gcpLabelsTimeout, }) if cfg.Queue != "" { i := slices.IndexFunc(tags, func(s string) bool { return strings.HasPrefix(strings.TrimSpace(s), "queue=") }) if i != -1 { l.Fatal("Queue must be present in only one of the --tags or the --queue flags") } tags = append(tags, "queue="+cfg.Queue) } if !osutil.FileExists(agentConf.BuildPath) { l.Info("Build Path doesn't exist, creating it (%s)", agentConf.BuildPath) if err := os.MkdirAll(agentConf.BuildPath, 0o777); err != nil { return fmt.Errorf("failed to create builds path: %w", err) } } apiClient := api.NewClient(l, loadAPIClientConfig(cfg, "Token")) client := &core.Client{APIClient: apiClient, Logger: l} registerReq := api.AgentRegisterRequest{ Name: cfg.Name, Priority: cfg.Priority, ScriptEvalEnabled: !cfg.NoCommandEval, Tags: tags, IgnoreInDispatches: cfg.AcquireJob != "", Features: cfg.Features(ctx), } if cfg.SpawnPerCPU > 0 { if cfg.Spawn > 1 { return errors.New("You can't specify spawn and spawn-per-cpu at the same time") } cfg.Spawn = runtime.NumCPU() * cfg.SpawnPerCPU } if cfg.Spawn > 1 && cfg.AcquireJob != "" { return errors.New("You can't spawn multiple agents and acquire a job at the same time") } var workers []*agent.AgentWorker for i := 1; i <= cfg.Spawn; i++ { if cfg.Spawn == 1 { l.Info("Registering agent with Buildkite...") } else { l.Info("Registering agent %d of %d with Buildkite...", i, cfg.Spawn) } registerReq.Name = strings.ReplaceAll(cfg.Name, "%spawn", strconv.Itoa(i)) if cfg.SpawnWithPriority { p := i if experiments.IsEnabled(ctx, experiments.DescendingSpawnPriority) { p = -i } l.Info("Assigning priority %d for agent %d", p, i) registerReq.Priority = strconv.Itoa(p) } ag, err := client.Register(ctx, registerReq) if err != nil { return err } workers = append(workers, agent.NewAgentWorker( l.WithFields(logger.StringField("agent", ag.Name)), ag, mc, apiClient, agent.AgentWorkerConfig{ AgentConfiguration: agentConf, CancelSignal: cancelSig, SignalGracePeriod: signalGracePeriod, Debug: cfg.Debug, DebugHTTP: cfg.DebugHTTP, SpawnIndex: i, AgentStdout: os.Stdout, }, )) } pool := agent.NewAgentPool(workers) defer agentShutdownHook(l, cfg) if err := agentStartupHook(l, cfg); err != nil { return fmt.Errorf("failed to run startup hook: %w", err) } signals := handlePoolSignals(ctx, l, pool) defer close(signals) l.Info("Starting %d Agent(s)", cfg.Spawn) l.Info("You can press Ctrl-C to stop the agents") if cfg.HealthCheckAddr != "" { pool.StartStatusServer(ctx, l, cfg.HealthCheckAddr) } err = pool.Start(ctx) if errors.Is(err, core.ErrJobAcquisitionRejected) { const acquisitionFailedExitCode = 27 // chosen by fair dice roll return cli.NewExitError(err, acquisitionFailedExitCode) } return err }, }
var AnnotateCommand = cli.Command{ Name: "annotate", Usage: "Annotate the build page within the Buildkite UI with text from within a Buildkite job", Description: annotateHelpDescription, Flags: []cli.Flag{ cli.StringFlag{ Name: "context", Usage: "The context of the annotation used to differentiate this annotation from others", EnvVar: "BUILDKITE_ANNOTATION_CONTEXT", }, cli.StringFlag{ Name: "style", Usage: "The style of the annotation (′success′, ′info′, ′warning′ or ′error′)", EnvVar: "BUILDKITE_ANNOTATION_STYLE", }, cli.BoolFlag{ Name: "append", Usage: "Append to the body of an existing annotation", EnvVar: "BUILDKITE_ANNOTATION_APPEND", }, cli.IntFlag{ Name: "priority", Usage: "The priority of the annotation (′1′ to ′10′). Annotations with a priority of ′10′ are shown first, while annotations with a priority of ′1′ are shown last.", EnvVar: "BUILDKITE_ANNOTATION_PRIORITY", Value: 3, }, cli.StringFlag{ Name: "job", Value: "", Usage: "Which job should the annotation come from", EnvVar: "BUILDKITE_JOB_ID", }, AgentAccessTokenFlag, EndpointFlag, NoHTTP2Flag, DebugHTTPFlag, NoColorFlag, DebugFlag, LogLevelFlag, ExperimentsFlag, ProfileFlag, }, Action: func(c *cli.Context) error { ctx := context.Background() ctx, cfg, l, _, done := setupLoggerAndConfig[AnnotateConfig](ctx, c) defer done() if err := annotate(ctx, cfg, l); err != nil { return err } return nil }, }
var AnnotationRemoveCommand = cli.Command{ Name: "remove", Usage: "Remove an existing annotation from a Buildkite build", Description: annotationRemoveHelpDescription, Flags: []cli.Flag{ cli.StringFlag{ Name: "context", Value: "default", Usage: "The context of the annotation used to differentiate this annotation from others", EnvVar: "BUILDKITE_ANNOTATION_CONTEXT", }, cli.StringFlag{ Name: "job", Value: "", Usage: "Which job is removing the annotation", EnvVar: "BUILDKITE_JOB_ID", }, AgentAccessTokenFlag, EndpointFlag, NoHTTP2Flag, DebugHTTPFlag, NoColorFlag, DebugFlag, LogLevelFlag, ExperimentsFlag, ProfileFlag, }, Action: func(c *cli.Context) error { ctx := context.Background() ctx, cfg, l, _, done := setupLoggerAndConfig[AnnotationRemoveConfig](ctx, c) defer done() client := api.NewClient(l, loadAPIClientConfig(cfg, "AgentAccessToken")) if err := roko.NewRetrier( roko.WithMaxAttempts(5), roko.WithStrategy(roko.Constant(1*time.Second)), roko.WithJitter(), ).DoWithContext(ctx, func(r *roko.Retrier) error { resp, err := client.AnnotationRemove(ctx, cfg.Job, cfg.Context) if resp != nil && (resp.StatusCode == 401 || resp.StatusCode == 404 || resp.StatusCode == 400 || resp.StatusCode == 410) { r.Break() return err } if err != nil { l.Warn("%s (%s)", err, r) return err } return nil }); err != nil { return fmt.Errorf("failed to remove annotation: %w", err) } l.Debug("Successfully removed annotation") return nil }, }
var ArtifactDownloadCommand = cli.Command{ Name: "download", Usage: "Downloads artifacts from Buildkite to the local machine", Description: downloadHelpDescription, Flags: []cli.Flag{ cli.StringFlag{ Name: "step", Value: "", Usage: "Scope the search to a particular step. Can be the step's key or label, or a Job ID", }, cli.StringFlag{ Name: "build", Value: "", EnvVar: "BUILDKITE_BUILD_ID", Usage: "The build that the artifacts were uploaded to", }, cli.BoolFlag{ Name: "include-retried-jobs", EnvVar: "BUILDKITE_AGENT_INCLUDE_RETRIED_JOBS", Usage: "Include artifacts from retried jobs in the search", }, AgentAccessTokenFlag, EndpointFlag, NoHTTP2Flag, DebugHTTPFlag, TraceHTTPFlag, NoColorFlag, DebugFlag, LogLevelFlag, ExperimentsFlag, ProfileFlag, }, Action: func(c *cli.Context) error { ctx := context.Background() ctx, cfg, l, _, done := setupLoggerAndConfig[ArtifactDownloadConfig](ctx, c) defer done() client := api.NewClient(l, loadAPIClientConfig(cfg, "AgentAccessToken")) downloader := artifact.NewDownloader(l, client, artifact.DownloaderConfig{ Query: cfg.Query, Destination: cfg.Destination, BuildID: cfg.Build, Step: cfg.Step, IncludeRetriedJobs: cfg.IncludeRetriedJobs, DebugHTTP: cfg.DebugHTTP, TraceHTTP: cfg.TraceHTTP, DisableHTTP2: cfg.NoHTTP2, }) if err := downloader.Download(ctx); err != nil { return fmt.Errorf("failed to download artifacts: %w", err) } return nil }, }
var ArtifactSearchCommand = cli.Command{ Name: "search", Usage: "Searches artifacts in Buildkite", Description: searchHelpDescription, CustomHelpTemplate: artifactSearchHelpTemplate, Flags: []cli.Flag{ cli.StringFlag{ Name: "step", Value: "", Usage: "Scope the search to a particular step by using either its name or job ID", }, cli.StringFlag{ Name: "build", Value: "", EnvVar: "BUILDKITE_BUILD_ID", Usage: "The build that the artifacts were uploaded to", }, cli.BoolFlag{ Name: "include-retried-jobs", EnvVar: "BUILDKITE_AGENT_INCLUDE_RETRIED_JOBS", Usage: "Include artifacts from retried jobs in the search", }, cli.BoolFlag{ Name: "allow-empty-results", Usage: "By default, searches exit 1 if there are no results. If this flag is set, searches will exit 0 with an empty set", }, cli.StringFlag{ Name: "format", Value: "%j %p %c\n", Usage: "Output formatting of results. See below for listing of available format specifiers.", }, AgentAccessTokenFlag, EndpointFlag, NoHTTP2Flag, DebugHTTPFlag, NoColorFlag, DebugFlag, LogLevelFlag, ExperimentsFlag, ProfileFlag, }, Action: func(c *cli.Context) error { ctx := context.Background() ctx, cfg, l, _, done := setupLoggerAndConfig[ArtifactSearchConfig](ctx, c) defer done() client := api.NewClient(l, loadAPIClientConfig(cfg, "AgentAccessToken")) searcher := artifact.NewSearcher(l, client, cfg.Build) artifacts, err := searcher.Search(ctx, cfg.Query, cfg.Step, cfg.IncludeRetriedJobs, true) if err != nil { return err } if len(artifacts) == 0 { if !cfg.AllowEmptyResults { return fmt.Errorf("no matches found for %q", cfg.Query) } l.Info("No matches found for %q", cfg.Query) } for _, artifact := range artifacts { r := strings.NewReplacer( "%p", artifact.Path, "%c", artifact.CreatedAt.Format(time.RFC3339), "%j", artifact.JobID, "%s", strconv.FormatInt(artifact.FileSize, 10), "%S", artifact.Sha1Sum, "%T", artifact.Sha256Sum, "%u", artifact.URL, "%i", artifact.ID, ) if _, err := fmt.Fprint(c.App.Writer, r.Replace(cfg.PrintFormat)); err != nil { return err } } return nil }, }
var ArtifactShasumCommand = cli.Command{ Name: "shasum", Usage: "Prints the SHA-1 hash for a single artifact specified by a search query", Description: shasumHelpDescription, Flags: []cli.Flag{ cli.BoolFlag{ Name: "sha256", Usage: "Request SHA-256 instead of SHA-1, errors if SHA-256 not available", }, cli.StringFlag{ Name: "step", Value: "", Usage: "Scope the search to a particular step by its name or job ID", }, cli.StringFlag{ Name: "build", Value: "", EnvVar: "BUILDKITE_BUILD_ID", Usage: "The build that the artifact was uploaded to", }, cli.BoolFlag{ Name: "include-retried-jobs", EnvVar: "BUILDKITE_AGENT_INCLUDE_RETRIED_JOBS", Usage: "Include artifacts from retried jobs in the search", }, AgentAccessTokenFlag, EndpointFlag, NoHTTP2Flag, DebugHTTPFlag, NoColorFlag, DebugFlag, LogLevelFlag, ExperimentsFlag, ProfileFlag, }, Action: func(c *cli.Context) error { ctx := context.Background() ctx, cfg, l, _, done := setupLoggerAndConfig[ArtifactShasumConfig](ctx, c) defer done() return searchAndPrintShaSum(ctx, cfg, l, os.Stdout) }, }
var ArtifactUploadCommand = cli.Command{ Name: "upload", Usage: "Uploads files to a job as artifacts", Description: uploadHelpDescription, Flags: []cli.Flag{ cli.StringFlag{ Name: "job", Value: "", Usage: "Which job should the artifacts be uploaded to", EnvVar: "BUILDKITE_JOB_ID", }, cli.StringFlag{ Name: "content-type", Value: "", Usage: "A specific Content-Type to set for the artifacts (otherwise detected)", EnvVar: "BUILDKITE_ARTIFACT_CONTENT_TYPE", }, cli.BoolFlag{ Name: "glob-resolve-follow-symlinks", Usage: "Follow symbolic links to directories while resolving globs. Note: this will not prevent symlinks to files from being uploaded. Use --upload-skip-symlinks to do that", EnvVar: "BUILDKITE_AGENT_ARTIFACT_GLOB_RESOLVE_FOLLOW_SYMLINKS", }, cli.BoolFlag{ Name: "upload-skip-symlinks", Usage: "After the glob has been resolved to a list of files to upload, skip uploading those that are symlinks to files", EnvVar: "BUILDKITE_ARTIFACT_UPLOAD_SKIP_SYMLINKS", }, cli.BoolFlag{ Name: "follow-symlinks", Usage: "Follow symbolic links while resolving globs. Note this argument is deprecated. Use `--glob-resolve-follow-symlinks` instead", EnvVar: "BUILDKITE_AGENT_ARTIFACT_SYMLINKS", }, AgentAccessTokenFlag, EndpointFlag, NoHTTP2Flag, DebugHTTPFlag, TraceHTTPFlag, NoColorFlag, DebugFlag, LogLevelFlag, ExperimentsFlag, ProfileFlag, NoMultipartArtifactUploadFlag, }, Action: func(c *cli.Context) error { ctx := context.Background() ctx, cfg, l, _, done := setupLoggerAndConfig[ArtifactUploadConfig](ctx, c) defer done() client := api.NewClient(l, loadAPIClientConfig(cfg, "AgentAccessToken")) uploader := artifact.NewUploader(l, client, artifact.UploaderConfig{ JobID: cfg.Job, Paths: cfg.UploadPaths, Destination: cfg.Destination, ContentType: cfg.ContentType, DebugHTTP: cfg.DebugHTTP, TraceHTTP: cfg.TraceHTTP, DisableHTTP2: cfg.NoHTTP2, AllowMultipart: !cfg.NoMultipartUpload, GlobResolveFollowSymlinks: (cfg.GlobResolveFollowSymlinks || cfg.FollowSymlinks), UploadSkipSymlinks: cfg.UploadSkipSymlinks, }) if err := uploader.Upload(ctx); err != nil { return fmt.Errorf("failed to upload artifacts: %w", err) } return nil }, }
var BootstrapCommand = cli.Command{ Name: "bootstrap", Usage: "Run a Buildkite job locally", Description: bootstrapHelpDescription, Flags: []cli.Flag{ cli.StringFlag{ Name: "command", Value: "", Usage: "The command to run", EnvVar: "BUILDKITE_COMMAND", }, cli.StringFlag{ Name: "job", Value: "", Usage: "The ID of the job being run", EnvVar: "BUILDKITE_JOB_ID", }, cli.StringFlag{ Name: "repository", Value: "", Usage: "The repository to clone and run the job from", EnvVar: "BUILDKITE_REPO", }, cli.StringFlag{ Name: "commit", Value: "", Usage: "The commit to checkout in the repository", EnvVar: "BUILDKITE_COMMIT", }, cli.StringFlag{ Name: "branch", Value: "", Usage: "The branch the commit is in", EnvVar: "BUILDKITE_BRANCH", }, cli.StringFlag{ Name: "tag", Value: "", Usage: "The tag the commit", EnvVar: "BUILDKITE_TAG", }, cli.StringFlag{ Name: "refspec", Value: "", Usage: "Optional refspec to override git fetch", EnvVar: "BUILDKITE_REFSPEC", }, cli.StringFlag{ Name: "plugins", Value: "", Usage: "The plugins for the job", EnvVar: "BUILDKITE_PLUGINS", }, cli.StringFlag{ Name: "pullrequest", Value: "", Usage: "The number/id of the pull request this commit belonged to", EnvVar: "BUILDKITE_PULL_REQUEST", }, cli.StringFlag{ Name: "agent", Value: "", Usage: "The name of the agent running the job", EnvVar: "BUILDKITE_AGENT_NAME", }, cli.StringFlag{ Name: "queue", Value: "", Usage: "The name of the queue the agent belongs to, if tagged", EnvVar: "BUILDKITE_AGENT_META_DATA_QUEUE", }, cli.StringFlag{ Name: "organization", Value: "", Usage: "The slug of the organization that the job is a part of", EnvVar: "BUILDKITE_ORGANIZATION_SLUG", }, cli.StringFlag{ Name: "pipeline", Value: "", Usage: "The slug of the pipeline that the job is a part of", EnvVar: "BUILDKITE_PIPELINE_SLUG", }, cli.StringFlag{ Name: "pipeline-provider", Value: "", Usage: "The id of the SCM provider that the repository is hosted on", EnvVar: "BUILDKITE_PIPELINE_PROVIDER", }, cli.StringFlag{ Name: "artifact-upload-paths", Value: "", Usage: "Paths to files to automatically upload at the end of a job", EnvVar: "BUILDKITE_ARTIFACT_PATHS", }, cli.StringFlag{ Name: "artifact-upload-destination", Value: "", Usage: "A custom location to upload artifact paths to (for example, s3://my-custom-bucket/and/prefix)", EnvVar: "BUILDKITE_ARTIFACT_UPLOAD_DESTINATION", }, cli.BoolFlag{ Name: "clean-checkout", Usage: "Whether or not the bootstrap should remove the existing repository before running the command", EnvVar: "BUILDKITE_CLEAN_CHECKOUT", }, cli.StringFlag{ Name: "git-checkout-flags", Value: "-f", Usage: "Flags to pass to \"git checkout\" command", EnvVar: "BUILDKITE_GIT_CHECKOUT_FLAGS", }, cli.StringFlag{ Name: "git-clone-flags", Value: "-v", Usage: "Flags to pass to \"git clone\" command", EnvVar: "BUILDKITE_GIT_CLONE_FLAGS", }, cli.StringFlag{ Name: "git-clone-mirror-flags", Value: "-v", Usage: "Flags to pass to \"git clone\" command when mirroring", EnvVar: "BUILDKITE_GIT_CLONE_MIRROR_FLAGS", }, cli.StringFlag{ Name: "git-clean-flags", Value: "-ffxdq", Usage: "Flags to pass to \"git clean\" command", EnvVar: "BUILDKITE_GIT_CLEAN_FLAGS", }, cli.StringFlag{ Name: "git-fetch-flags", Value: "", Usage: "Flags to pass to \"git fetch\" command", EnvVar: "BUILDKITE_GIT_FETCH_FLAGS", }, cli.StringSliceFlag{ Name: "git-submodule-clone-config", Value: &cli.StringSlice{}, Usage: "Comma separated key=value git config pairs applied before git submodule clone commands. For example, ′update --init′. If the config is needed to be applied to all git commands, supply it in a global git config file for the system that the agent runs in instead.", EnvVar: "BUILDKITE_GIT_SUBMODULE_CLONE_CONFIG", }, cli.StringFlag{ Name: "git-mirrors-path", Value: "", Usage: "Path to where mirrors of git repositories are stored", EnvVar: "BUILDKITE_GIT_MIRRORS_PATH", }, cli.IntFlag{ Name: "git-mirrors-lock-timeout", Value: 300, Usage: "Seconds to lock a git mirror during clone, should exceed your longest checkout", EnvVar: "BUILDKITE_GIT_MIRRORS_LOCK_TIMEOUT", }, cli.BoolFlag{ Name: "git-mirrors-skip-update", Usage: "Skip updating the Git mirror", EnvVar: "BUILDKITE_GIT_MIRRORS_SKIP_UPDATE", }, cli.StringFlag{ Name: "bin-path", Value: "", Usage: "Directory where the buildkite-agent binary lives", EnvVar: "BUILDKITE_BIN_PATH", }, cli.StringFlag{ Name: "build-path", Value: "", Usage: "Directory where builds will be created", EnvVar: "BUILDKITE_BUILD_PATH", }, cli.StringFlag{ Name: "hooks-path", Value: "", Usage: "Directory where the hook scripts are found", EnvVar: "BUILDKITE_HOOKS_PATH", }, cli.StringFlag{ Name: "sockets-path", Value: defaultSocketsPath(), Usage: "Directory where the agent will place sockets", EnvVar: "BUILDKITE_SOCKETS_PATH", }, cli.StringFlag{ Name: "plugins-path", Value: "", Usage: "Directory where the plugins are saved to", EnvVar: "BUILDKITE_PLUGINS_PATH", }, cli.BoolTFlag{ Name: "command-eval", Usage: "Allow running of arbitrary commands", EnvVar: "BUILDKITE_COMMAND_EVAL", }, cli.BoolTFlag{ Name: "plugins-enabled", Usage: "Allow plugins to be run", EnvVar: "BUILDKITE_PLUGINS_ENABLED", }, cli.BoolFlag{ Name: "plugin-validation", Usage: "Validate plugin configuration", EnvVar: "BUILDKITE_PLUGIN_VALIDATION", }, cli.BoolFlag{ Name: "plugins-always-clone-fresh", Usage: "Always make a new clone of plugin source, even if already present", EnvVar: "BUILDKITE_PLUGINS_ALWAYS_CLONE_FRESH", }, cli.BoolTFlag{ Name: "local-hooks-enabled", Usage: "Allow local hooks to be run", EnvVar: "BUILDKITE_LOCAL_HOOKS_ENABLED", }, cli.BoolTFlag{ Name: "ssh-keyscan", Usage: "Automatically run ssh-keyscan before checkout", EnvVar: "BUILDKITE_SSH_KEYSCAN", }, cli.BoolTFlag{ Name: "git-submodules", Usage: "Enable git submodules", EnvVar: "BUILDKITE_GIT_SUBMODULES", }, cli.BoolTFlag{ Name: "pty", Usage: "Run jobs within a pseudo terminal", EnvVar: "BUILDKITE_PTY", }, cli.StringFlag{ Name: "shell", Usage: "The shell to use to interpret build commands", EnvVar: "BUILDKITE_SHELL", Value: DefaultShell(), }, cli.StringSliceFlag{ Name: "phases", Usage: "The specific phases to execute. The order they're defined is irrelevant.", EnvVar: "BUILDKITE_BOOTSTRAP_PHASES", }, cli.StringFlag{ Name: "tracing-backend", Usage: "The name of the tracing backend to use.", EnvVar: "BUILDKITE_TRACING_BACKEND", Value: "", }, cli.StringFlag{ Name: "tracing-service-name", Usage: "Service name to use when reporting traces.", EnvVar: "BUILDKITE_TRACING_SERVICE_NAME", Value: "buildkite-agent", }, cli.BoolFlag{ Name: "no-job-api", Usage: "Disables the Job API, which gives commands in jobs some abilities to introspect and mutate the state of the job.", EnvVar: "BUILDKITE_AGENT_NO_JOB_API", }, cli.StringSliceFlag{ Name: "disable-warnings-for", Usage: "A list of warning IDs to disable", EnvVar: "BUILDKITE_AGENT_DISABLE_WARNINGS_FOR", }, cli.IntFlag{ Name: "kubernetes-container-id", Usage: "This is intended to be used only by the Buildkite k8s stack " + "(github.com/buildkite/agent-stack-k8s); it sets an ID number " + "used to identify this container within the pod", EnvVar: "BUILDKITE_CONTAINER_ID", }, cancelSignalFlag, cancelGracePeriodFlag, signalGracePeriodSecondsFlag, DebugFlag, LogLevelFlag, ExperimentsFlag, ProfileFlag, RedactedVars, StrictSingleHooksFlag, KubernetesExecFlag, TraceContextEncodingFlag, }, Action: func(c *cli.Context) error { ctx := context.Background() ctx, cfg, l, _, done := setupLoggerAndConfig[BootstrapConfig](ctx, c) defer done() runInPty := cfg.PTY if runtime.GOOS == "windows" { runInPty = false } for _, phase := range cfg.Phases { switch phase { case "plugin", "checkout", "command": default: return fmt.Errorf("invalid phase %q", phase) } } cancelSig, err := process.ParseSignal(cfg.CancelSignal) if err != nil { return fmt.Errorf("failed to parse cancel-signal: %w", err) } signalGracePeriod, err := signalGracePeriod(cfg.CancelGracePeriod, cfg.SignalGracePeriodSeconds) if err != nil { return err } traceContextCodec, err := tracetools.ParseEncoding(cfg.TraceContextEncoding) if err != nil { return fmt.Errorf("while parsing trace context encoding: %v", err) } bootstrap := job.New(job.ExecutorConfig{ AgentName: cfg.AgentName, ArtifactUploadDestination: cfg.ArtifactUploadDestination, AutomaticArtifactUploadPaths: cfg.AutomaticArtifactUploadPaths, BinPath: cfg.BinPath, Branch: cfg.Branch, BuildPath: cfg.BuildPath, SocketsPath: cfg.SocketsPath, CancelSignal: cancelSig, SignalGracePeriod: signalGracePeriod, CleanCheckout: cfg.CleanCheckout, Command: cfg.Command, CommandEval: cfg.CommandEval, Commit: cfg.Commit, Debug: cfg.Debug, GitCheckoutFlags: cfg.GitCheckoutFlags, GitCleanFlags: cfg.GitCleanFlags, GitCloneFlags: cfg.GitCloneFlags, GitCloneMirrorFlags: cfg.GitCloneMirrorFlags, GitFetchFlags: cfg.GitFetchFlags, GitMirrorsLockTimeout: cfg.GitMirrorsLockTimeout, GitMirrorsPath: cfg.GitMirrorsPath, GitMirrorsSkipUpdate: cfg.GitMirrorsSkipUpdate, GitSubmodules: cfg.GitSubmodules, GitSubmoduleCloneConfig: cfg.GitSubmoduleCloneConfig, HooksPath: cfg.HooksPath, JobID: cfg.JobID, LocalHooksEnabled: cfg.LocalHooksEnabled, OrganizationSlug: cfg.OrganizationSlug, Phases: cfg.Phases, PipelineProvider: cfg.PipelineProvider, PipelineSlug: cfg.PipelineSlug, PluginValidation: cfg.PluginValidation, Plugins: cfg.Plugins, PluginsEnabled: cfg.PluginsEnabled, PluginsAlwaysCloneFresh: cfg.PluginsAlwaysCloneFresh, PluginsPath: cfg.PluginsPath, PullRequest: cfg.PullRequest, Queue: cfg.Queue, RedactedVars: cfg.RedactedVars, RefSpec: cfg.RefSpec, Repository: cfg.Repository, RunInPty: runInPty, SSHKeyscan: cfg.SSHKeyscan, Shell: cfg.Shell, StrictSingleHooks: cfg.StrictSingleHooks, Tag: cfg.Tag, TracingBackend: cfg.TracingBackend, TracingServiceName: cfg.TracingServiceName, TraceContextCodec: traceContextCodec, JobAPI: !cfg.NoJobAPI, DisabledWarnings: cfg.DisableWarningsFor, KubernetesExec: cfg.KubernetesExec, KubernetesContainerID: cfg.KubernetesContainerID, }) cctx, cancel := context.WithCancel(ctx) defer cancel() signals := make(chan os.Signal, 1) signal.Notify(signals, os.Interrupt, syscall.SIGHUP, syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT) defer signal.Stop(signals) var ( cancelled bool received os.Signal signalMu sync.Mutex ) go func() { sig := <-signals signalMu.Lock() defer signalMu.Unlock() if err := bootstrap.Cancel(); err != nil { l.Debug("Failed to cancel bootstrap: %v", err) } cancelled = true received = sig signal.Stop(signals) }() exitCode := bootstrap.Run(cctx) signalMu.Lock() defer signalMu.Unlock() if cancelled && runtime.GOOS != "windows" { p, err := os.FindProcess(os.Getpid()) if err != nil { l.Error("Failed to find current process: %v", err) } l.Debug("Terminating bootstrap after cancellation with %v", received) err = p.Signal(received) if err != nil { l.Error("Failed to signal self: %v", err) } } return &SilentExitError{code: exitCode} }, }
var BuildCancelCommand = cli.Command{ Name: "cancel", Usage: "Cancel a build", Description: buildCancelDescription, Flags: []cli.Flag{ cli.StringFlag{ Name: "build", Value: "", Usage: "The build UUID to cancel", EnvVar: "BUILDKITE_BUILD_ID", }, AgentAccessTokenFlag, EndpointFlag, NoHTTP2Flag, DebugHTTPFlag, NoColorFlag, DebugFlag, LogLevelFlag, ExperimentsFlag, ProfileFlag, }, Action: func(c *cli.Context) error { ctx := context.Background() ctx, cfg, l, _, done := setupLoggerAndConfig[BuildCancelConfig](ctx, c) defer done() return cancelBuild(ctx, cfg, l) }, }
var BuildkiteAgentCommands = []cli.Command{ AgentStartCommand, BootstrapCommand, AcknowledgementsCommand, AnnotateCommand, { Name: "annotation", Usage: "Make changes to an annotation on the currently running build", Subcommands: []cli.Command{ AnnotationRemoveCommand, }, }, { Name: "artifact", Usage: "Upload/download artifacts from Buildkite jobs", Subcommands: []cli.Command{ ArtifactUploadCommand, ArtifactDownloadCommand, ArtifactSearchCommand, ArtifactShasumCommand, }, }, { Name: "build", Usage: "Interact with a Buildkite build", Subcommands: []cli.Command{ BuildCancelCommand, }, }, { Name: "env", Usage: "Process environment subcommands", Subcommands: []cli.Command{ EnvDumpCommand, EnvGetCommand, EnvSetCommand, EnvUnsetCommand, }, }, GitCredentialsHelperCommand, { Name: "lock", Usage: "Process lock subcommands", Subcommands: []cli.Command{ LockAcquireCommand, LockDoCommand, LockDoneCommand, LockGetCommand, LockReleaseCommand, }, }, { Name: "redactor", Usage: "Redact sensitive information from logs", Subcommands: []cli.Command{ RedactorAddCommand, }, }, { Name: "meta-data", Usage: "Get/set data from Buildkite jobs", Subcommands: []cli.Command{ MetaDataSetCommand, MetaDataGetCommand, MetaDataExistsCommand, MetaDataKeysCommand, }, }, { Name: "oidc", Usage: "Interact with Buildkite OpenID Connect (OIDC)", Subcommands: []cli.Command{ OIDCRequestTokenCommand, }, }, { Name: "pipeline", Usage: "Make changes to the pipeline of the currently running build", Subcommands: []cli.Command{ PipelineUploadCommand, }, }, { Name: "secret", Usage: "Interact with Pipelines Secrets", Subcommands: []cli.Command{ SecretGetCommand, }, }, { Name: "step", Usage: "Get or update an attribute of a build step", Subcommands: []cli.Command{ StepGetCommand, StepUpdateCommand, }, }, { Name: "tool", Usage: "Utility commands, intended for users and operators of the agent to run directly on their machines, and not as part of a Buildkite job", Subcommands: []cli.Command{ ToolKeygenCommand, ToolSignCommand, }, }, }
var EnvDumpCommand = cli.Command{ Name: "dump", Usage: "Print the environment of the current process as a JSON object", Description: envDumpHelpDescription, Flags: []cli.Flag{ cli.StringFlag{ Name: "format", Usage: "Output format; json or json-pretty", EnvVar: "BUILDKITE_AGENT_ENV_DUMP_FORMAT", Value: "json", }, NoColorFlag, DebugFlag, LogLevelFlag, ExperimentsFlag, ProfileFlag, }, Action: func(c *cli.Context) error { _, cfg, _, _, done := setupLoggerAndConfig[EnvDumpConfig](context.Background(), c) defer done() envn := os.Environ() envMap := make(map[string]string, len(envn)) for _, e := range envn { if k, v, ok := env.Split(e); ok { envMap[k] = v } } enc := json.NewEncoder(c.App.Writer) if cfg.Format == "json-pretty" { enc.SetIndent("", " ") } if err := enc.Encode(envMap); err != nil { return fmt.Errorf("error marshalling JSON: %w", err) } return nil }, }
var EnvGetCommand = cli.Command{ Name: "get", Usage: "Gets variables from the job execution environment", Description: envGetHelpDescription, Flags: []cli.Flag{ cli.StringFlag{ Name: "format", Usage: "Output format: plain, json, or json-pretty", EnvVar: "BUILDKITE_AGENT_ENV_GET_FORMAT", Value: "plain", }, NoColorFlag, DebugFlag, LogLevelFlag, ExperimentsFlag, ProfileFlag, }, Action: envGetAction, }
var EnvSetCommand = cli.Command{ Name: "set", Usage: "Sets variables in the job execution environment", Description: envSetHelpDescription, Flags: []cli.Flag{ cli.StringFlag{ Name: "input-format", Usage: "Input format: plain or json", EnvVar: "BUILDKITE_AGENT_ENV_SET_INPUT_FORMAT", Value: "plain", }, cli.StringFlag{ Name: "output-format", Usage: "Output format: quiet (no output), plain, json, or json-pretty", EnvVar: "BUILDKITE_AGENT_ENV_SET_OUTPUT_FORMAT", Value: "plain", }, NoColorFlag, DebugFlag, LogLevelFlag, ExperimentsFlag, ProfileFlag, }, Action: envSetAction, }
var EnvUnsetCommand = cli.Command{ Name: "unset", Usage: "Unsets variables from the job execution environment", Description: envUnsetHelpDescription, Flags: []cli.Flag{ cli.StringFlag{ Name: "input-format", Usage: "Input format: plain or json", EnvVar: "BUILDKITE_AGENT_ENV_UNSET_INPUT_FORMAT", Value: "plain", }, cli.StringFlag{ Name: "output-format", Usage: "Output format: quiet (no output), plain, json, or json-pretty", EnvVar: "BUILDKITE_AGENT_ENV_UNSET_OUTPUT_FORMAT", Value: "plain", }, NoColorFlag, DebugFlag, LogLevelFlag, ExperimentsFlag, ProfileFlag, }, Action: envUnsetAction, }
var GitCredentialsHelperCommand = cli.Command{ Name: "git-credentials-helper", Usage: "Ask buildkite for credentials to use to authenticate with Github when cloning", Description: gitCredentialsHelperHelpDescription, Flags: []cli.Flag{ cli.StringFlag{ Name: "job-id", Usage: "The job id to get credentials for", EnvVar: "BUILDKITE_JOB_ID", }, AgentAccessTokenFlag, EndpointFlag, NoHTTP2Flag, NoColorFlag, DebugFlag, LogLevelFlag, ExperimentsFlag, ProfileFlag, }, Action: func(c *cli.Context) error { ctx := context.Background() ctx, cfg, l, _, done := setupLoggerAndConfig[GitCredentialsHelperConfig](ctx, c) defer done() l.Debug("Action: %s", cfg.Action) if cfg.Action != "get" { return nil } if os.Getenv("BUILDKITE_JOB_ID") == "" { l.Warn("📎💬 It looks like you're calling this command directly in a step, rather than having the agent automatically call it") l.Warn("This command is intended to be used as a git credential helper, and not called directly.") } stdin, err := io.ReadAll(os.Stdin) if err != nil { return handleAuthError(c, l, fmt.Errorf("failed to read stdin: %w", err)) } l.Debug("Git credential input:\n%s\n", string(stdin)) l.Debug("Authenticating checkout using Buildkite Github App Credentials...") repo, err := parseGitURLFromCredentialInput(string(stdin)) if err != nil { return handleAuthError(c, l, fmt.Errorf("failed to parse git URL from stdin: %w", err)) } client := api.NewClient(l, loadAPIClientConfig(cfg, "AgentAccessToken")) tok, _, err := client.GenerateGithubCodeAccessToken(ctx, repo, cfg.JobID) if err != nil { return handleAuthError(c, l, fmt.Errorf("failed to get github app credentials: %w", err)) } fmt.Fprintln(c.App.Writer, "username=token") fmt.Fprintln(c.App.Writer, "password="+tok) fmt.Fprintln(c.App.Writer, "") l.Debug("Authentication successful!") return nil }, }
var LockAcquireCommand = cli.Command{
Name: "acquire",
Usage: "Acquires a lock from the agent leader",
Description: lockAcquireHelpDescription,
Flags: lockAcquireFlags(),
Action: lockAcquireAction,
}
var LockDoCommand = cli.Command{
Name: "do",
Usage: "Begins a do-once lock",
Description: lockDoHelpDescription,
Flags: lockDoFlags(),
Action: lockDoAction,
}
var LockDoneCommand = cli.Command{ Name: "done", Usage: "Completes a do-once lock", Description: lockDoneHelpDescription, Flags: append(globalFlags(), lockCommonFlags...), Action: lockDoneAction, }
var LockGetCommand = cli.Command{ Name: "get", Usage: "Gets a lock value from the agent leader", Description: lockGetHelpDescription, Flags: append(globalFlags(), lockCommonFlags...), Action: lockGetAction, }
var LockReleaseCommand = cli.Command{ Name: "release", Usage: "Releases a previously-acquired lock", Description: lockReleaseHelpDescription, Flags: append(globalFlags(), lockCommonFlags...), Action: lockReleaseAction, }
var MetaDataExistsCommand = cli.Command{ Name: "exists", Usage: "Check to see if the meta data key exists for a build", Description: metaDataExistsHelpDescription, Flags: []cli.Flag{ cli.StringFlag{ Name: "job", Value: "", Usage: "Which job's build should the meta-data be checked for", EnvVar: "BUILDKITE_JOB_ID", }, cli.StringFlag{ Name: "build", Value: "", Usage: "Which build should the meta-data be retrieved from. --build will take precedence over --job", EnvVar: "BUILDKITE_METADATA_BUILD_ID", }, AgentAccessTokenFlag, EndpointFlag, NoHTTP2Flag, DebugHTTPFlag, NoColorFlag, DebugFlag, LogLevelFlag, ExperimentsFlag, ProfileFlag, }, Action: func(c *cli.Context) error { ctx := context.Background() ctx, cfg, l, _, done := setupLoggerAndConfig[MetaDataExistsConfig](ctx, c) defer done() client := api.NewClient(l, loadAPIClientConfig(cfg, "AgentAccessToken")) scope := "job" id := cfg.Job if cfg.Build != "" { scope = "build" id = cfg.Build } r := roko.NewRetrier( roko.WithMaxAttempts(10), roko.WithStrategy(roko.Constant(5*time.Second)), ) exists, err := roko.DoFunc(ctx, r, func(r *roko.Retrier) (*api.MetaDataExists, error) { exists, resp, err := client.ExistsMetaData(ctx, scope, id, cfg.Key) if resp != nil && (resp.StatusCode == 401 || resp.StatusCode == 404) { r.Break() } if err != nil { l.Warn("%s (%s)", err, r) return nil, err } return exists, nil }) if err != nil { return fmt.Errorf("failed to see if meta-data exists: %w", err) } if !exists.Exists { return &SilentExitError{code: 100} } return nil }, }
var MetaDataGetCommand = cli.Command{ Name: "get", Usage: "Get data from a build", Description: metaDataGetHelpDescription, Flags: []cli.Flag{ cli.StringFlag{ Name: "default", Value: "", Usage: "If the meta-data value doesn't exist return this instead", }, cli.StringFlag{ Name: "job", Value: "", Usage: "Which job's build should the meta-data be retrieved from", EnvVar: "BUILDKITE_JOB_ID", }, cli.StringFlag{ Name: "build", Value: "", Usage: "Which build should the meta-data be retrieved from. --build will take precedence over --job", EnvVar: "BUILDKITE_METADATA_BUILD_ID", }, AgentAccessTokenFlag, EndpointFlag, NoHTTP2Flag, DebugHTTPFlag, NoColorFlag, DebugFlag, LogLevelFlag, ExperimentsFlag, ProfileFlag, }, Action: func(c *cli.Context) error { ctx := context.Background() ctx, cfg, l, _, done := setupLoggerAndConfig[MetaDataGetConfig](ctx, c) defer done() client := api.NewClient(l, loadAPIClientConfig(cfg, "AgentAccessToken")) scope := "job" id := cfg.Job if cfg.Build != "" { scope = "build" id = cfg.Build } r := roko.NewRetrier( roko.WithMaxAttempts(10), roko.WithStrategy(roko.Constant(5*time.Second)), ) metaData, resp, err := roko.DoFunc2(ctx, r, func(r *roko.Retrier) (*api.MetaData, *api.Response, error) { metaData, resp, err := client.GetMetaData(ctx, scope, id, cfg.Key) if resp != nil && (resp.StatusCode == 401 || resp.StatusCode == 404 || resp.StatusCode == 400) { r.Break() return nil, resp, err } if err != nil { l.Warn("%s (%s)", err, r) return nil, resp, err } return metaData, resp, nil }) if err != nil { if resp.StatusCode == 404 && c.IsSet("default") { l.Warn( "No meta-data value exists with key %q, returning the supplied default %q", cfg.Key, cfg.Default, ) fmt.Fprint(c.App.Writer, cfg.Default) return nil } return fmt.Errorf("failed to get meta-data: %w", err) } _, err = fmt.Fprint(c.App.Writer, metaData.Value) return err }, }
var MetaDataKeysCommand = cli.Command{ Name: "keys", Usage: "Lists all meta-data keys that have been previously set", Description: metaDataKeysHelpDescription, Flags: []cli.Flag{ cli.StringFlag{ Name: "job", Value: "", Usage: "Which job's build should the meta-data be checked for", EnvVar: "BUILDKITE_JOB_ID", }, cli.StringFlag{ Name: "build", Value: "", Usage: "Which build should the meta-data be retrieved from. --build will take precedence over --job", EnvVar: "BUILDKITE_METADATA_BUILD_ID", }, AgentAccessTokenFlag, EndpointFlag, NoHTTP2Flag, DebugHTTPFlag, NoColorFlag, DebugFlag, LogLevelFlag, ExperimentsFlag, ProfileFlag, }, Action: func(c *cli.Context) error { ctx := context.Background() ctx, cfg, l, _, done := setupLoggerAndConfig[MetaDataKeysConfig](ctx, c) defer done() client := api.NewClient(l, loadAPIClientConfig(cfg, "AgentAccessToken")) scope := "job" id := cfg.Job if cfg.Build != "" { scope = "build" id = cfg.Build } r := roko.NewRetrier( roko.WithMaxAttempts(10), roko.WithStrategy(roko.Constant(5*time.Second)), ) keys, err := roko.DoFunc(ctx, r, func(r *roko.Retrier) ([]string, error) { keys, resp, err := client.MetaDataKeys(ctx, scope, id) if resp != nil && (resp.StatusCode == 401 || resp.StatusCode == 404) { r.Break() } if err != nil { l.Warn("%s (%s)", err, r) } return keys, err }) if err != nil { return fmt.Errorf("failed to find meta-data keys: %w", err) } for _, key := range keys { fmt.Fprintf(c.App.Writer, "%s\n", key) } return nil }, }
var MetaDataSetCommand = cli.Command{ Name: "set", Usage: "Set data on a build", Description: metaDataSetHelpDescription, Flags: []cli.Flag{ cli.StringFlag{ Name: "job", Value: "", Usage: "Which job's build should the meta-data be set on", EnvVar: "BUILDKITE_JOB_ID", }, AgentAccessTokenFlag, EndpointFlag, NoHTTP2Flag, DebugHTTPFlag, NoColorFlag, DebugFlag, LogLevelFlag, ExperimentsFlag, ProfileFlag, }, Action: func(c *cli.Context) error { ctx := context.Background() ctx, cfg, l, _, done := setupLoggerAndConfig[MetaDataSetConfig](ctx, c) defer done() if len(c.Args()) < 2 { l.Info("Reading meta-data value from STDIN") input, err := io.ReadAll(os.Stdin) if err != nil { return fmt.Errorf("failed to read from STDIN: %w", err) } cfg.Value = string(input) } if strings.TrimSpace(cfg.Key) == "" { return errors.New("key cannot be empty, or composed of only whitespace characters") } if strings.TrimSpace(cfg.Value) == "" { return errors.New("value cannot be empty, or composed of only whitespace characters") } client := api.NewClient(l, loadAPIClientConfig(cfg, "AgentAccessToken")) metaData := &api.MetaData{ Key: cfg.Key, Value: cfg.Value, } if err := roko.NewRetrier( roko.WithMaxAttempts(10), roko.WithStrategy(roko.ExponentialSubsecond(2*time.Second)), ).DoWithContext(ctx, func(r *roko.Retrier) error { resp, err := client.SetMetaData(ctx, cfg.Job, metaData) if resp != nil && (resp.StatusCode == 401 || resp.StatusCode == 404) { r.Break() } if err != nil { l.Warn("%s (%s)", err, r) return err } return nil }); err != nil { return fmt.Errorf("failed to set meta-data: %w", err) } return nil }, }
var OIDCRequestTokenCommand = cli.Command{ Name: "request-token", Usage: "Requests and prints an OIDC token from Buildkite with the specified audience,", Description: oidcTokenDescription, Flags: []cli.Flag{ cli.StringFlag{ Name: "audience", Usage: "The audience that will consume the OIDC token. The API will choose a default audience if it is omitted.", }, cli.IntFlag{ Name: "lifetime", Usage: "The time (in seconds) the OIDC token will be valid for before expiry. Must be a non-negative integer. If the flag is omitted or set to 0, the API will choose a default finite lifetime.", }, cli.StringFlag{ Name: "job", Usage: "Buildkite Job Id to claim in the OIDC token", EnvVar: "BUILDKITE_JOB_ID", }, cli.StringSliceFlag{ Name: "claim", Value: &cli.StringSlice{}, Usage: "Claims to add to the OIDC token", EnvVar: "BUILDKITE_OIDC_TOKEN_CLAIMS", }, cli.StringSliceFlag{ Name: "aws-session-tag", Value: &cli.StringSlice{}, Usage: "Add claims as AWS Session Tags", EnvVar: "BUILDKITE_OIDC_TOKEN_AWS_SESSION_TAGS", }, AgentAccessTokenFlag, EndpointFlag, NoHTTP2Flag, DebugHTTPFlag, NoColorFlag, DebugFlag, LogLevelFlag, ExperimentsFlag, ProfileFlag, }, Action: func(c *cli.Context) error { ctx := context.Background() ctx, cfg, l, _, done := setupLoggerAndConfig[OIDCTokenConfig](ctx, c) defer done() if cfg.Lifetime < 0 { return fmt.Errorf("lifetime %d must be a non-negative integer.", cfg.Lifetime) } client := api.NewClient(l, loadAPIClientConfig(cfg, "AgentAccessToken")) r := roko.NewRetrier( roko.WithMaxAttempts(maxAttempts), roko.WithStrategy(roko.Exponential(backoffSeconds*time.Second, 0)), ) token, err := roko.DoFunc(ctx, r, func(r *roko.Retrier) (*api.OIDCToken, error) { req := &api.OIDCTokenRequest{ Job: cfg.Job, Audience: cfg.Audience, Lifetime: cfg.Lifetime, Claims: cfg.Claims, AWSSessionTags: cfg.AWSSessionTags, } token, resp, err := client.OIDCToken(ctx, req) if resp != nil { switch resp.StatusCode { case http.StatusBadRequest, http.StatusUnauthorized, http.StatusForbidden, http.StatusUnprocessableEntity: r.Break() return nil, err } } if err != nil { l.Warn("%s (%s)", err, r) } return token, err }) if err != nil { if len(cfg.Audience) > 0 { l.Error("Could not obtain OIDC token for audience %s", cfg.Audience) } else { l.Error("Could not obtain OIDC token for default audience") } return err } _, _ = fmt.Fprintln(c.App.Writer, token.Token) return nil }, }
var PipelineUploadCommand = cli.Command{ Name: "upload", Usage: "Uploads a description of a build pipeline adds it to the currently running build after the current job", Description: pipelineUploadHelpDescription, Flags: []cli.Flag{ cli.BoolFlag{ Name: "replace", Usage: "Replace the rest of the existing pipeline with the steps uploaded. Jobs that are already running are not removed.", EnvVar: "BUILDKITE_PIPELINE_REPLACE", }, cli.StringFlag{ Name: "job", Value: "", Usage: "The job that is making the changes to its build", EnvVar: "BUILDKITE_JOB_ID", }, cli.BoolFlag{ Name: "dry-run", Usage: "Rather than uploading the pipeline, it will be echoed to stdout", EnvVar: "BUILDKITE_PIPELINE_UPLOAD_DRY_RUN", }, cli.StringFlag{ Name: "format", Usage: "In dry-run mode, specifies the form to output the pipeline in. Must be one of: json,yaml", Value: "json", EnvVar: "BUILDKITE_PIPELINE_UPLOAD_DRY_RUN_FORMAT", }, cli.BoolFlag{ Name: "no-interpolation", Usage: "Skip variable interpolation into the pipeline prior to upload", EnvVar: "BUILDKITE_PIPELINE_NO_INTERPOLATION", }, cli.BoolFlag{ Name: "reject-secrets", Usage: "When true, fail the pipeline upload early if the pipeline contains secrets", EnvVar: "BUILDKITE_AGENT_PIPELINE_UPLOAD_REJECT_SECRETS", }, cli.StringFlag{ Name: "jwks-file", Usage: "Path to a file containing a JWKS. Passing this flag enables pipeline signing", EnvVar: "BUILDKITE_AGENT_JWKS_FILE", }, cli.StringFlag{ Name: "jwks-key-id", Usage: "The JWKS key ID to use when signing the pipeline. Required when using a JWKS", EnvVar: "BUILDKITE_AGENT_JWKS_KEY_ID", }, cli.StringFlag{ Name: "signing-aws-kms-key", Usage: "The AWS KMS key identifier which is used to sign pipelines.", EnvVar: "BUILDKITE_AGENT_AWS_KMS_KEY", }, cli.BoolFlag{ Name: "debug-signing", Usage: "Enable debug logging for pipeline signing. This can potentially leak secrets to the logs as it prints each step in full before signing. Requires debug logging to be enabled", EnvVar: "BUILDKITE_AGENT_DEBUG_SIGNING", }, AgentAccessTokenFlag, EndpointFlag, NoHTTP2Flag, DebugHTTPFlag, NoColorFlag, DebugFlag, LogLevelFlag, ExperimentsFlag, ProfileFlag, RedactedVars, }, Action: func(c *cli.Context) error { ctx := context.Background() ctx, cfg, l, _, done := setupLoggerAndConfig[PipelineUploadConfig](ctx, c) defer done() // Find the pipeline either from STDIN or the first argument var input *os.File var filename string switch { case cfg.FilePath != "": l.Info("Reading pipeline config from %q", cfg.FilePath) filename = filepath.Base(cfg.FilePath) file, err := os.Open(cfg.FilePath) if err != nil { return fmt.Errorf("failed to read file: %w", err) } defer file.Close() input = file case stdin.IsReadable(): l.Info("Reading pipeline config from STDIN") input = os.Stdin default: l.Info("Searching for pipeline config...") paths := []string{ "buildkite.yml", "buildkite.yaml", "buildkite.json", filepath.FromSlash(".buildkite/pipeline.yml"), filepath.FromSlash(".buildkite/pipeline.yaml"), filepath.FromSlash(".buildkite/pipeline.json"), filepath.FromSlash("buildkite/pipeline.yml"), filepath.FromSlash("buildkite/pipeline.yaml"), filepath.FromSlash("buildkite/pipeline.json"), } exists := []string{} for _, path := range paths { if _, err := os.Stat(path); err == nil { exists = append(exists, path) } } if len(exists) > 1 { return fmt.Errorf("found multiple configuration files: %s. Please only have 1 configuration file present.", strings.Join(exists, ", ")) } if len(exists) == 0 { return fmt.Errorf("could not find a default pipeline configuration file. See `buildkite-agent pipeline upload --help` for more information.") } found := exists[0] l.Info("Found config file %q", found) filename = path.Base(found) file, err := os.Open(found) if err != nil { return fmt.Errorf("failed to read file %q: %w", found, err) } defer file.Close() input = file } if input != os.Stdin { fi, err := input.Stat() if err != nil { return fmt.Errorf("couldn't stat pipeline configuration file %q: %w", input.Name(), err) } if fi.Size() == 0 { return fmt.Errorf("pipeline file %q is empty", input.Name()) } } environ := env.FromSlice(os.Environ()) if !cfg.NoInterpolation { resolveCommit(l, environ) } src := filename if src == "" { src = "(stdin)" } result, err := cfg.parseAndInterpolate(ctx, src, input, environ) if err != nil { w := warning.As(err) if w == nil { return err } l.Warn("There were some issues with the pipeline input - pipeline upload will proceed, but might not succeed:\n%v", w) } if len(cfg.RedactedVars) > 0 { searchForSecrets(l, &cfg, environ, result, src) } var ( key signature.Key ) switch { case cfg.SigningAWSKMSKey != "": awscfg, err := awslib.GetConfigV2(ctx) if err != nil { return err } key, err = awssigner.NewKMS(kms.NewFromConfig(awscfg), cfg.SigningAWSKMSKey) if err != nil { return fmt.Errorf("couldn't create KMS signer: %w", err) } case cfg.JWKSFile != "": key, err = jwkutil.LoadKey(cfg.JWKSFile, cfg.JWKSKeyID) if err != nil { return fmt.Errorf("couldn't read the signing key file: %w", err) } } if key != nil { err = signature.SignSteps( ctx, result.Steps, key, os.Getenv("BUILDKITE_REPO"), signature.WithEnv(result.Env.ToMap()), signature.WithLogger(l), signature.WithDebugSigning(cfg.DebugSigning), ) if err != nil { return fmt.Errorf("couldn't sign pipeline: %w", err) } } if cfg.DryRun { var encode func(any) error switch cfg.DryRunFormat { case "json": enc := json.NewEncoder(c.App.Writer) enc.SetIndent("", " ") encode = enc.Encode case "yaml": encode = yaml.NewEncoder(c.App.Writer).Encode default: return fmt.Errorf("unknown output format %q", cfg.DryRunFormat) } if err := encode(result); err != nil { return err } return nil } if cfg.Job == "" { return errors.New("missing job parameter. Usually this is set in the environment for a Buildkite job via BUILDKITE_JOB_ID.") } if cfg.AgentAccessToken == "" { return errors.New("missing agent-access-token parameter. Usually this is set in the environment for a Buildkite job via BUILDKITE_AGENT_ACCESS_TOKEN.") } uploader := &agent.PipelineUploader{ Client: api.NewClient(l, loadAPIClientConfig(cfg, "AgentAccessToken")), JobID: cfg.Job, Change: &api.PipelineChange{ UUID: api.NewUUID(), Replace: cfg.Replace, Pipeline: result, }, RetrySleepFunc: time.Sleep, } if err := uploader.Upload(ctx, l); err != nil { return err } l.Info("Successfully uploaded and parsed pipeline config") return nil }, }
var RedactorAddCommand = cli.Command{ Name: "add", Usage: "Add values to redact from a job's log output", Description: `Usage: buildkite-agent redactor add [options...] [file-with-content-to-redact] Description: This command may be used to parse a file for values to redact from a running job's log output. If you dynamically fetch secrets during a job, it is recommended that you use this command to ensure they will be redacted from subsequent logs. Secrets fetched with the builtin ′secret get′ command do not require the use of this command, they will be redacted automatically. Example: To redact the verbatim contents of the file 'id_ed25519' from future logs: $ buildkite-agent redactor add id_ed25519 To redact the string 'llamasecret' from future logs: $ echo llamasecret | buildkite-agent redactor add To redact multiple secrets from future logs in one command, create a flat JSON object file (for example, 'my-secrets.json'), with multiple "key" values, one for each secret: $ echo '{"key":"secret1","key":"secret2"}' | buildkite-agent redactor add --format=json Or $ buildkite-agent redactor add --format=json my-secrets.json`, Flags: []cli.Flag{ cli.StringFlag{ Name: "format", Usage: fmt.Sprintf( "The format for the input, one of: %s. ′none′ will add the entire input as a to the redactor, save for leading and trailing whitespace, ′json′ will parse it a string valued JSON Object, where each value of each key will be added to the redactor.", secretsFormats, ), EnvVar: "BUILDKITE_AGENT_REDACT_ADD_FORMAT", Value: FormatStringNone, }, AgentAccessTokenFlag, EndpointFlag, NoHTTP2Flag, DebugHTTPFlag, NoColorFlag, DebugFlag, LogLevelFlag, ExperimentsFlag, ProfileFlag, }, Action: func(c *cli.Context) error { ctx := context.Background() ctx, cfg, l, _, done := setupLoggerAndConfig[RedactorAddConfig](ctx, c) defer done() if !slices.Contains(secretsFormats, cfg.Format) { return fmt.Errorf("invalid format: %s, must be one of %q", cfg.Format, secretsFormats) } fileName := "(stdin)" secretsReader := bufio.NewReader(os.Stdin) if cfg.File != "" { fileName = cfg.File secretsFile, err := os.Open(fileName) if err != nil { return fmt.Errorf("failed to open file %s: %w", fileName, err) } defer secretsFile.Close() secretsReader = bufio.NewReader(secretsFile) } l.Info("Reading secrets from %s for redaction", fileName) secrets, err := ParseSecrets(l, cfg, secretsReader) if err != nil { if cfg.Debug { return err } return errSecretParse } client, err := jobapi.NewDefaultClient(ctx) if err != nil { return fmt.Errorf("failed to create Job API client: %w", err) } if err := AddToRedactor(ctx, l, client, secrets...); err != nil { if cfg.Debug { return err } return errSecretRedact } return nil }, }
var SecretGetCommand = cli.Command{ Name: "get", Usage: "Get a secret by its key and print it to stdout", Description: `Usage: buildkite-agent secret get [key] [options...] Description: Gets a secret from Buildkite secrets and prints it to stdout. The ′key′ specified in this command is the key's name defined for the secret in its cluster. The key's name is case insensitive in this command, and the key's value is automatically redacted in the build logs. Examples: The following examples reference the same Buildkite secret ′key′: $ buildkite-agent secret get deploy_key $ buildkite-agent secret get DEPLOY_KEY`, Flags: []cli.Flag{ cli.StringFlag{ Name: "job", Usage: "Which job should should the secret be for", EnvVar: "BUILDKITE_JOB_ID", }, cli.BoolFlag{ Name: "skip-redaction", Usage: "Skip redacting the retrieved secret from the logs. Then, the command will print the secret to the Job's logs if called directly.", EnvVar: "BUILDKITE_AGENT_SECRET_GET_SKIP_SECRET_REDACTION", }, AgentAccessTokenFlag, EndpointFlag, NoHTTP2Flag, DebugHTTPFlag, NoColorFlag, DebugFlag, LogLevelFlag, ExperimentsFlag, ProfileFlag, }, Action: func(c *cli.Context) error { ctx := context.Background() ctx, cfg, l, _, done := setupLoggerAndConfig[SecretGetConfig](ctx, c) defer done() agentClient := api.NewClient(l, loadAPIClientConfig(cfg, "AgentAccessToken")) secret, _, err := agentClient.GetSecret(ctx, &api.GetSecretRequest{Key: cfg.Key, JobID: cfg.Job}) if err != nil { return err } jobClient, err := jobapi.NewDefaultClient(ctx) if err != nil { return fmt.Errorf("failed to create Job API client: %w", err) } if !cfg.SkipRedaction { if err := AddToRedactor(ctx, l, jobClient, secret.Value); err != nil { if cfg.Debug { return err } return errSecretRedact } } _, err = fmt.Fprintln(c.App.Writer, secret.Value) return err }, }
var StepGetCommand = cli.Command{ Name: "get", Usage: "Get the value of an attribute", Description: stepGetHelpDescription, Flags: []cli.Flag{ cli.StringFlag{ Name: "step", Value: "", Usage: "The step to get. Can be either its ID (BUILDKITE_STEP_ID) or key (BUILDKITE_STEP_KEY)", EnvVar: "BUILDKITE_STEP_ID", }, cli.StringFlag{ Name: "build", Value: "", Usage: "The build to look for the step in. Only required when targeting a step using its key (BUILDKITE_STEP_KEY)", EnvVar: "BUILDKITE_BUILD_ID", }, cli.StringFlag{ Name: "format", Value: "", Usage: "The format to output the attribute value in (currently only JSON is supported)", EnvVar: "BUILDKITE_STEP_GET_FORMAT", }, AgentAccessTokenFlag, EndpointFlag, NoHTTP2Flag, DebugHTTPFlag, NoColorFlag, DebugFlag, LogLevelFlag, ExperimentsFlag, ProfileFlag, }, Action: func(c *cli.Context) error { ctx, cfg, l, _, done := setupLoggerAndConfig[StepGetConfig](context.Background(), c) defer done() client := api.NewClient(l, loadAPIClientConfig(cfg, "AgentAccessToken")) stepExportRequest := &api.StepExportRequest{ Build: cfg.Build, Attribute: cfg.Attribute, Format: cfg.Format, } r := roko.NewRetrier( roko.WithMaxAttempts(10), roko.WithStrategy(roko.Constant(5*time.Second)), ) stepExportResponse, err := roko.DoFunc(ctx, r, func(r *roko.Retrier) (*api.StepExportResponse, error) { stepExportResponse, resp, err := client.StepExport(ctx, cfg.StepOrKey, stepExportRequest) if resp != nil && (resp.StatusCode == 401 || resp.StatusCode == 404 || resp.StatusCode == 400) { r.Break() } if err != nil { l.Warn("%s (%s)", err, r) } return stepExportResponse, err }) if err != nil { return fmt.Errorf("failed to get step: %w", err) } _, err = fmt.Fprintln(c.App.Writer, stepExportResponse.Output) return err }, }
var StepUpdateCommand = cli.Command{ Name: "update", Usage: "Change the value of an attribute", Description: stepUpdateHelpDescription, Flags: []cli.Flag{ cli.StringFlag{ Name: "step", Value: "", Usage: "The step to update. Can be either its ID (BUILDKITE_STEP_ID) or key (BUILDKITE_STEP_KEY)", EnvVar: "BUILDKITE_STEP_ID", }, cli.StringFlag{ Name: "build", Value: "", Usage: "The build to look for the step in. Only required when targeting a step using its key (BUILDKITE_STEP_KEY)", EnvVar: "BUILDKITE_BUILD_ID", }, cli.BoolFlag{ Name: "append", Usage: "Append to current attribute instead of replacing it", EnvVar: "BUILDKITE_STEP_UPDATE_APPEND", }, AgentAccessTokenFlag, EndpointFlag, NoHTTP2Flag, DebugHTTPFlag, NoColorFlag, DebugFlag, LogLevelFlag, ExperimentsFlag, ProfileFlag, }, Action: func(c *cli.Context) error { ctx, cfg, l, _, done := setupLoggerAndConfig[StepUpdateConfig](context.Background(), c) defer done() if len(c.Args()) < 2 { l.Info("Reading value from STDIN") input, err := io.ReadAll(os.Stdin) if err != nil { return fmt.Errorf("failed to read from STDIN: %w", err) } cfg.Value = string(input) } client := api.NewClient(l, loadAPIClientConfig(cfg, "AgentAccessToken")) idempotencyUUID := api.NewUUID() update := &api.StepUpdate{ IdempotencyUUID: idempotencyUUID, Build: cfg.Build, Attribute: cfg.Attribute, Value: cfg.Value, Append: cfg.Append, } if err := roko.NewRetrier( roko.WithMaxAttempts(10), roko.WithStrategy(roko.Constant(5*time.Second)), ).DoWithContext(ctx, func(r *roko.Retrier) error { resp, err := client.StepUpdate(ctx, cfg.StepOrKey, update) if resp != nil && (resp.StatusCode == 400 || resp.StatusCode == 401 || resp.StatusCode == 404) { r.Break() } if err != nil { l.Warn("%s (%s)", err, r) return err } return nil }); err != nil { return fmt.Errorf("failed to change step: %w", err) } return nil }, }
var ToolKeygenCommand = cli.Command{ Name: "keygen", Usage: "Generate a new JWS key pair, used for signing and verifying jobs in Buildkite", Description: `Usage: buildkite-agent tool keygen [options...] Description: This command generates a new JWS key pair, used for signing and verifying jobs in Buildkite. The pair is written as a JSON Web Key Set (JWKS) to two files, a private JWKS file and a public JWKS file. The private JWKS should be used as for signing, and the public JWKS for verification. For more information about JWS, see https://tools.ietf.org/html/rfc7515 and for information about JWKS, see https://tools.ietf.org/html/rfc7517`, Flags: []cli.Flag{ cli.StringFlag{ Name: "alg", EnvVar: "BUILDKITE_AGENT_KEYGEN_ALG", Usage: fmt.Sprintf("The JWS signing algorithm to use for the key pair. Defaults to 'EdDSA'. Valid algorithms are: %v", jwkutil.ValidSigningAlgorithms), }, cli.StringFlag{ Name: "key-id", EnvVar: "BUILDKITE_AGENT_KEYGEN_KEY_ID", Usage: "The ID to use for the keys generated. If none is provided, a random one will be generated", }, cli.StringFlag{ Name: "private-jwks-file", EnvVar: "BUILDKITE_AGENT_KEYGEN_PRIVATE_JWKS_FILE", Usage: "The filename to write the private key to. Defaults to a name based on the key id in the current directory", }, cli.StringFlag{ Name: "public-jwks-file", EnvVar: "BUILDKITE_AGENT_KEYGEN_PUBLIC_JWKS_FILE", Usage: "The filename to write the public keyset to. Defaults to a name based on the key id in the current directory", }, NoColorFlag, DebugFlag, LogLevelFlag, ExperimentsFlag, ProfileFlag, }, Action: func(c *cli.Context) { _, cfg, l, _, done := setupLoggerAndConfig[ToolKeygenConfig](context.Background(), c) defer done() if cfg.Alg == "" { cfg.Alg = "EdDSA" l.Info("No algorithm provided, using %s", cfg.Alg) } if cfg.KeyID == "" { cfg.KeyID = petname.Generate(2, "-") l.Info("No key ID provided, using a randomly generated one: %s", cfg.KeyID) } sigAlg := jwa.SignatureAlgorithm(cfg.Alg) if !slices.Contains(jwkutil.ValidSigningAlgorithms, sigAlg) { l.Fatal("Invalid signing algorithm: %s. Valid signing algorithms are: %s", cfg.Alg, jwkutil.ValidSigningAlgorithms) } priv, pub, err := jwkutil.NewKeyPair(cfg.KeyID, sigAlg) if err != nil { l.Fatal("Failed to generate key pair: %v", err) } if cfg.PrivateJWKSFile == "" { cfg.PrivateJWKSFile = fmt.Sprintf("./%s-%s-private.json", cfg.Alg, cfg.KeyID) } if cfg.PublicJWKSFile == "" { cfg.PublicJWKSFile = fmt.Sprintf("./%s-%s-public.json", cfg.Alg, cfg.KeyID) } l.Info("Writing private key set to %s...", cfg.PrivateJWKSFile) pKey, err := json.Marshal(priv) if err != nil { l.Fatal("Failed to marshal private key: %v", err) } err = writeIfNotExists(cfg.PrivateJWKSFile, pKey) if err != nil { l.Fatal("Failed to write private key file: %v", err) } l.Info("Writing public key set to %s...", cfg.PublicJWKSFile) pubKey, err := json.Marshal(pub) if err != nil { l.Fatal("Failed to marshal private key: %v", err) } err = writeIfNotExists(cfg.PublicJWKSFile, pubKey) if err != nil { l.Fatal("Failed to write private key file: %v", err) } l.Info("Done! Enjoy your new keys ^_^") }, }
TODO: Add docs link when there is one.
var ToolSignCommand = cli.Command{ Name: "sign", Usage: "Sign pipeline steps", Description: `Usage: buildkite-agent tool sign [options...] [pipeline-file] Description: This command takes a pipeline in YAML format as input, and annotates the appropriate parts of the pipeline with signatures. This can then be input into the YAML steps editor in the Buildkite UI so that the agents running these steps can verify the signatures. If a token is provided using the ′graphql-token′ flag, the tool will attempt to retrieve the pipeline definition and repo using the Buildkite GraphQL API. If ′update′ is also set, it will update the pipeline definition with the signed version using the GraphQL API too. Examples: Retrieving the pipeline from the GraphQL API and signing it: $ buildkite-agent tool sign \ --graphql-token <graphql token> \ --organization-slug <your org slug> \ --pipeline-slug <slug of the pipeline whose steps you want to sign \ --jwks-file /path/to/private/key.json \ --update Signing a pipeline from a file: $ buildkite-agent tool sign pipeline.yml \ --jwks-file /path/to/private/key.json \ --repo <repo url for your pipeline> # or $ cat pipeline.yml | buildkite-agent tool sign \ --jwks-file /path/to/private/key.json \ --repo <repo url for your pipeline>`, Flags: []cli.Flag{ cli.StringFlag{ Name: "graphql-token", Usage: "A token for the buildkite graphql API. This will be used to populate the value of the repository URL, and download the pipeline definition. Both ′repo′ and ′pipeline-file′ will be ignored in preference of values from the GraphQL API if the token in provided.", EnvVar: "BUILDKITE_GRAPHQL_TOKEN", }, cli.BoolFlag{ Name: "update", Usage: "Update the pipeline using the GraphQL API after signing it. This can only be used if ′graphql-token′ is provided.", EnvVar: "BUILDKITE_TOOL_SIGN_UPDATE", }, cli.BoolFlag{ Name: "no-confirm", Usage: "Show confirmation prompts before updating the pipeline with the GraphQL API.", EnvVar: "BUILDKITE_TOOL_SIGN_NO_CONFIRM", }, cli.StringFlag{ Name: "jwks-file", Usage: "Path to a file containing a JWKS.", EnvVar: "BUILDKITE_AGENT_JWKS_FILE", }, cli.StringFlag{ Name: "jwks-key-id", Usage: "The JWKS key ID to use when signing the pipeline. If none is provided and the JWKS file contains only one key, that key will be used.", EnvVar: "BUILDKITE_AGENT_JWKS_KEY_ID", }, cli.StringFlag{ Name: "signing-aws-kms-key", Usage: "The AWS KMS key identifier which is used to sign pipelines.", EnvVar: "BUILDKITE_AGENT_AWS_KMS_KEY", }, cli.BoolFlag{ Name: "debug-signing", Usage: "Enable debug logging for pipeline signing. This can potentially leak secrets to the logs as it prints each step in full before signing. Requires debug logging to be enabled", EnvVar: "BUILDKITE_AGENT_DEBUG_SIGNING", }, cli.StringFlag{ Name: "organization-slug", Usage: "The organization slug. Required to connect to the GraphQL API.", EnvVar: "BUILDKITE_ORGANIZATION_SLUG", }, cli.StringFlag{ Name: "pipeline-slug", Usage: "The pipeline slug. Required to connect to the GraphQL API.", EnvVar: "BUILDKITE_PIPELINE_SLUG", }, cli.StringFlag{ Name: "graphql-endpoint", Usage: "The endpoint for the Buildkite GraphQL API. This is only needed if you are using the the graphql-token flag, and is mostly useful for development purposes", Value: bkgql.DefaultEndpoint, EnvVar: "BUILDKITE_GRAPHQL_ENDPOINT", }, cli.StringFlag{ Name: "repo", Usage: "The URL of the pipeline's repository, which is used in the pipeline signature. If the GraphQL token is provided, this will be ignored.", EnvVar: "BUILDKITE_REPO", }, NoColorFlag, DebugFlag, LogLevelFlag, ExperimentsFlag, ProfileFlag, }, Action: func(c *cli.Context) error { ctx, cfg, l, _, done := setupLoggerAndConfig[ToolSignConfig](context.Background(), c) defer done() var ( key signature.Key err error ) switch { case cfg.AWSKMSKeyID != "": awscfg, err := awslib.GetConfigV2(ctx) if err != nil { return err } key, err = awssigner.NewKMS(kms.NewFromConfig(awscfg), cfg.AWSKMSKeyID) if err != nil { return fmt.Errorf("couldn't create KMS signer: %w", err) } default: key, err = jwkutil.LoadKey(cfg.JWKSFile, cfg.JWKSKeyID) if err != nil { return fmt.Errorf("couldn't read the signing key file: %w", err) } } sign := signWithGraphQL if cfg.GraphQLToken == "" { sign = signOffline } err = sign(ctx, c, l, key, &cfg) if err != nil { return fmt.Errorf("Error signing pipeline: %w", err) } return nil }, }
Functions ¶
func AddToRedactor ¶ added in v3.66.0
func CreateLogger ¶
func DefaultShell ¶
func DefaultShell() string
func HandleGlobalFlags ¶
func HandleProfileFlag ¶
func ParseSecrets ¶ added in v3.66.0
func PrintMessageAndReturnExitCode ¶ added in v3.54.0
PrintMessageAndReturnExitCode prints the error message to stderr, preceded by "fatal: " and returns the exit code for the given error. If `err` is a SilentExitError or ExitError, it will return the code from that. Otherwise it will return 0 for nil errors and 1 for all other errors. Also, when `err` is a SilentExitError, it will not print anything to stderr.
Types ¶
type AcknowledgementsConfig ¶ added in v3.45.0
type AcknowledgementsConfig struct{}
type AgentStartConfig ¶
type AgentStartConfig struct { Config string `cli:"config"` Name string `cli:"name"` Priority string `cli:"priority"` Spawn int `cli:"spawn"` SpawnPerCPU int `cli:"spawn-per-cpu"` SpawnWithPriority bool `cli:"spawn-with-priority"` RedactedVars []string `cli:"redacted-vars" normalize:"list"` CancelSignal string `cli:"cancel-signal"` SigningJWKSKeyID string `cli:"signing-jwks-key-id"` SigningJWKSFile string `cli:"signing-jwks-file" normalize:"filepath"` SigningAWSKMSKey string `cli:"signing-aws-kms-key"` DebugSigning bool `cli:"debug-signing"` VerificationJWKSFile string `cli:"verification-jwks-file" normalize:"filepath"` VerificationFailureBehavior string `cli:"verification-failure-behavior"` AcquireJob string `cli:"acquire-job"` DisconnectAfterJob bool `cli:"disconnect-after-job"` DisconnectAfterIdleTimeout int `cli:"disconnect-after-idle-timeout"` CancelGracePeriod int `cli:"cancel-grace-period"` SignalGracePeriodSeconds int `cli:"signal-grace-period-seconds"` EnableJobLogTmpfile bool `cli:"enable-job-log-tmpfile"` JobLogPath string `cli:"job-log-path" normalize:"filepath"` LogFormat string `cli:"log-format"` WriteJobLogsToStdout bool `cli:"write-job-logs-to-stdout"` DisableWarningsFor []string `cli:"disable-warnings-for" normalize:"list"` BuildPath string `cli:"build-path" normalize:"filepath" validate:"required"` HooksPath string `cli:"hooks-path" normalize:"filepath"` SocketsPath string `cli:"sockets-path" normalize:"filepath"` PluginsPath string `cli:"plugins-path" normalize:"filepath"` Shell string `cli:"shell"` BootstrapScript string `cli:"bootstrap-script" normalize:"commandpath"` NoPTY bool `cli:"no-pty"` NoANSITimestamps bool `cli:"no-ansi-timestamps"` TimestampLines bool `cli:"timestamp-lines"` Queue string `cli:"queue"` Tags []string `cli:"tags" normalize:"list"` TagsFromEC2MetaData bool `cli:"tags-from-ec2-meta-data"` TagsFromEC2MetaDataPaths []string `cli:"tags-from-ec2-meta-data-paths" normalize:"list"` TagsFromEC2Tags bool `cli:"tags-from-ec2-tags"` TagsFromECSMetaData bool `cli:"tags-from-ecs-meta-data"` TagsFromGCPMetaData bool `cli:"tags-from-gcp-meta-data"` TagsFromGCPMetaDataPaths []string `cli:"tags-from-gcp-meta-data-paths" normalize:"list"` TagsFromGCPLabels bool `cli:"tags-from-gcp-labels"` TagsFromHost bool `cli:"tags-from-host"` WaitForEC2TagsTimeout string `cli:"wait-for-ec2-tags-timeout"` WaitForEC2MetaDataTimeout string `cli:"wait-for-ec2-meta-data-timeout"` WaitForECSMetaDataTimeout string `cli:"wait-for-ecs-meta-data-timeout"` WaitForGCPLabelsTimeout string `cli:"wait-for-gcp-labels-timeout"` GitCheckoutFlags string `cli:"git-checkout-flags"` GitCloneFlags string `cli:"git-clone-flags"` GitCloneMirrorFlags string `cli:"git-clone-mirror-flags"` GitCleanFlags string `cli:"git-clean-flags"` GitFetchFlags string `cli:"git-fetch-flags"` GitMirrorsPath string `cli:"git-mirrors-path" normalize:"filepath"` GitMirrorsLockTimeout int `cli:"git-mirrors-lock-timeout"` GitMirrorsSkipUpdate bool `cli:"git-mirrors-skip-update"` NoGitSubmodules bool `cli:"no-git-submodules"` NoSSHKeyscan bool `cli:"no-ssh-keyscan"` NoCommandEval bool `cli:"no-command-eval"` NoLocalHooks bool `cli:"no-local-hooks"` NoPlugins bool `cli:"no-plugins"` NoPluginValidation bool `cli:"no-plugin-validation"` NoFeatureReporting bool `cli:"no-feature-reporting"` AllowedRepositories []string `cli:"allowed-repositories" normalize:"list"` AllowedPlugins []string `cli:"allowed-plugins" normalize:"list"` EnableEnvironmentVariableAllowList bool `cli:"enable-environment-variable-allowlist"` AllowedEnvironmentVariables []string `cli:"allowed-environment-variables" normalize:"list"` HealthCheckAddr string `cli:"health-check-addr"` MetricsDatadog bool `cli:"metrics-datadog"` MetricsDatadogHost string `cli:"metrics-datadog-host"` MetricsDatadogDistributions bool `cli:"metrics-datadog-distributions"` TracingBackend string `cli:"tracing-backend"` TracingServiceName string `cli:"tracing-service-name"` // Global flags Debug bool `cli:"debug"` LogLevel string `cli:"log-level"` NoColor bool `cli:"no-color"` Experiments []string `cli:"experiment" normalize:"list"` Profile string `cli:"profile"` StrictSingleHooks bool `cli:"strict-single-hooks"` KubernetesExec bool `cli:"kubernetes-exec"` TraceContextEncoding string `cli:"trace-context-encoding"` NoMultipartArtifactUpload bool `cli:"no-multipart-artifact-upload"` // API config DebugHTTP bool `cli:"debug-http"` TraceHTTP bool `cli:"trace-http"` Token string `cli:"token" validate:"required"` Endpoint string `cli:"endpoint" validate:"required"` NoHTTP2 bool `cli:"no-http2"` // Deprecated NoSSHFingerprintVerification bool `cli:"no-automatic-ssh-fingerprint-verification" deprecated-and-renamed-to:"NoSSHKeyscan"` MetaData []string `cli:"meta-data" deprecated-and-renamed-to:"Tags"` MetaDataEC2 bool `cli:"meta-data-ec2" deprecated-and-renamed-to:"TagsFromEC2"` MetaDataEC2Tags bool `cli:"meta-data-ec2-tags" deprecated-and-renamed-to:"TagsFromEC2Tags"` 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"` DisconnectAfterJobTimeout int `cli:"disconnect-after-job-timeout" deprecated:"Use disconnect-after-idle-timeout instead"` }
type AnnotateConfig ¶
type AnnotateConfig struct { Body string `cli:"arg:0" label:"annotation body"` Style string `cli:"style"` Context string `cli:"context"` Append bool `cli:"append"` Priority int `cli:"priority"` Job string `cli:"job" validate:"required"` // Global flags Debug bool `cli:"debug"` LogLevel string `cli:"log-level"` NoColor bool `cli:"no-color"` Experiments []string `cli:"experiment" normalize:"list"` Profile string `cli:"profile"` // API config DebugHTTP bool `cli:"debug-http"` AgentAccessToken string `cli:"agent-access-token" validate:"required"` Endpoint string `cli:"endpoint" validate:"required"` NoHTTP2 bool `cli:"no-http2"` }
type AnnotationRemoveConfig ¶ added in v3.28.1
type AnnotationRemoveConfig struct { Context string `cli:"context" validate:"required"` Job string `cli:"job" validate:"required"` // Global flags Debug bool `cli:"debug"` LogLevel string `cli:"log-level"` NoColor bool `cli:"no-color"` Experiments []string `cli:"experiment" normalize:"list"` Profile string `cli:"profile"` // API config DebugHTTP bool `cli:"debug-http"` AgentAccessToken string `cli:"agent-access-token" validate:"required"` Endpoint string `cli:"endpoint" validate:"required"` NoHTTP2 bool `cli:"no-http2"` }
type ArtifactDownloadConfig ¶
type ArtifactDownloadConfig struct { Query string `cli:"arg:0" label:"artifact search query" validate:"required"` Destination string `cli:"arg:1" label:"artifact download path" validate:"required"` Step string `cli:"step"` Build string `cli:"build" validate:"required"` IncludeRetriedJobs bool `cli:"include-retried-jobs"` // Global flags Debug bool `cli:"debug"` LogLevel string `cli:"log-level"` NoColor bool `cli:"no-color"` Experiments []string `cli:"experiment" normalize:"list"` Profile string `cli:"profile"` // API config DebugHTTP bool `cli:"debug-http"` TraceHTTP bool `cli:"trace-http"` AgentAccessToken string `cli:"agent-access-token" validate:"required"` Endpoint string `cli:"endpoint" validate:"required"` NoHTTP2 bool `cli:"no-http2"` }
type ArtifactSearchConfig ¶ added in v3.23.0
type ArtifactSearchConfig struct { Query string `cli:"arg:0" label:"artifact search query" validate:"required"` Step string `cli:"step"` Build string `cli:"build" validate:"required"` IncludeRetriedJobs bool `cli:"include-retried-jobs"` AllowEmptyResults bool `cli:"allow-empty-results"` PrintFormat string `cli:"format"` // Global flags Debug bool `cli:"debug"` LogLevel string `cli:"log-level"` NoColor bool `cli:"no-color"` Experiments []string `cli:"experiment" normalize:"list"` Profile string `cli:"profile"` // API config DebugHTTP bool `cli:"debug-http"` AgentAccessToken string `cli:"agent-access-token" validate:"required"` Endpoint string `cli:"endpoint" validate:"required"` NoHTTP2 bool `cli:"no-http2"` }
type ArtifactShasumConfig ¶
type ArtifactShasumConfig struct { Query string `cli:"arg:0" label:"artifact search query" validate:"required"` Sha256 bool `cli:"sha256"` Step string `cli:"step"` Build string `cli:"build" validate:"required"` IncludeRetriedJobs bool `cli:"include-retried-jobs"` // Global flags Debug bool `cli:"debug"` LogLevel string `cli:"log-level"` NoColor bool `cli:"no-color"` Experiments []string `cli:"experiment" normalize:"list"` Profile string `cli:"profile"` // API config DebugHTTP bool `cli:"debug-http"` AgentAccessToken string `cli:"agent-access-token" validate:"required"` Endpoint string `cli:"endpoint" validate:"required"` NoHTTP2 bool `cli:"no-http2"` }
type ArtifactUploadConfig ¶
type ArtifactUploadConfig struct { UploadPaths string `cli:"arg:0" label:"upload paths" validate:"required"` Destination string `cli:"arg:1" label:"destination" env:"BUILDKITE_ARTIFACT_UPLOAD_DESTINATION"` Job string `cli:"job" validate:"required"` ContentType string `cli:"content-type"` // Global flags Debug bool `cli:"debug"` LogLevel string `cli:"log-level"` NoColor bool `cli:"no-color"` Experiments []string `cli:"experiment" normalize:"list"` Profile string `cli:"profile"` // API config DebugHTTP bool `cli:"debug-http"` TraceHTTP bool `cli:"trace-http"` AgentAccessToken string `cli:"agent-access-token" validate:"required"` Endpoint string `cli:"endpoint" validate:"required"` NoHTTP2 bool `cli:"no-http2"` // Uploader flags GlobResolveFollowSymlinks bool `cli:"glob-resolve-follow-symlinks"` UploadSkipSymlinks bool `cli:"upload-skip-symlinks"` NoMultipartUpload bool `cli:"no-multipart-artifact-upload"` // deprecated FollowSymlinks bool `cli:"follow-symlinks" deprecated-and-renamed-to:"GlobResolveFollowSymlinks"` }
type BootstrapConfig ¶
type BootstrapConfig struct { Command string `cli:"command"` JobID string `cli:"job" validate:"required"` Repository string `cli:"repository" validate:"required"` Commit string `cli:"commit" validate:"required"` Branch string `cli:"branch" validate:"required"` Tag string `cli:"tag"` RefSpec string `cli:"refspec"` Plugins string `cli:"plugins"` PullRequest string `cli:"pullrequest"` GitSubmodules bool `cli:"git-submodules"` SSHKeyscan bool `cli:"ssh-keyscan"` AgentName string `cli:"agent" validate:"required"` Queue string `cli:"queue"` OrganizationSlug string `cli:"organization" validate:"required"` PipelineSlug string `cli:"pipeline" validate:"required"` PipelineProvider string `cli:"pipeline-provider" validate:"required"` AutomaticArtifactUploadPaths string `cli:"artifact-upload-paths"` ArtifactUploadDestination string `cli:"artifact-upload-destination"` CleanCheckout bool `cli:"clean-checkout"` GitCheckoutFlags string `cli:"git-checkout-flags"` GitCloneFlags string `cli:"git-clone-flags"` GitFetchFlags string `cli:"git-fetch-flags"` GitCloneMirrorFlags string `cli:"git-clone-mirror-flags"` GitCleanFlags string `cli:"git-clean-flags"` GitMirrorsPath string `cli:"git-mirrors-path" normalize:"filepath"` GitMirrorsLockTimeout int `cli:"git-mirrors-lock-timeout"` GitMirrorsSkipUpdate bool `cli:"git-mirrors-skip-update"` GitSubmoduleCloneConfig []string `cli:"git-submodule-clone-config"` BinPath string `cli:"bin-path" normalize:"filepath"` BuildPath string `cli:"build-path" normalize:"filepath"` HooksPath string `cli:"hooks-path" normalize:"filepath"` SocketsPath string `cli:"sockets-path" normalize:"filepath"` PluginsPath string `cli:"plugins-path" normalize:"filepath"` CommandEval bool `cli:"command-eval"` PluginsEnabled bool `cli:"plugins-enabled"` PluginValidation bool `cli:"plugin-validation"` PluginsAlwaysCloneFresh bool `cli:"plugins-always-clone-fresh"` LocalHooksEnabled bool `cli:"local-hooks-enabled"` StrictSingleHooks bool `cli:"strict-single-hooks"` PTY bool `cli:"pty"` LogLevel string `cli:"log-level"` Debug bool `cli:"debug"` Shell string `cli:"shell"` Experiments []string `cli:"experiment" normalize:"list"` Phases []string `cli:"phases" normalize:"list"` Profile string `cli:"profile"` CancelSignal string `cli:"cancel-signal"` CancelGracePeriod int `cli:"cancel-grace-period"` SignalGracePeriodSeconds int `cli:"signal-grace-period-seconds"` RedactedVars []string `cli:"redacted-vars" normalize:"list"` TracingBackend string `cli:"tracing-backend"` TracingServiceName string `cli:"tracing-service-name"` TraceContextEncoding string `cli:"trace-context-encoding"` NoJobAPI bool `cli:"no-job-api"` DisableWarningsFor []string `cli:"disable-warnings-for" normalize:"list"` KubernetesExec bool `cli:"kubernetes-exec"` KubernetesContainerID int `cli:"kubernetes-container-id"` }
type BuildCancelConfig ¶ added in v3.84.0
type BuildCancelConfig struct { Build string `cli:"build" validate:"required"` // Global flags Debug bool `cli:"debug"` LogLevel string `cli:"log-level"` NoColor bool `cli:"no-color"` Experiments []string `cli:"experiment" normalize:"list"` Profile string `cli:"profile"` // API config DebugHTTP bool `cli:"debug-http"` AgentAccessToken string `cli:"agent-access-token" validate:"required"` Endpoint string `cli:"endpoint" validate:"required"` NoHTTP2 bool `cli:"no-http2"` }
type EnvDumpConfig ¶ added in v3.43.0
type EnvGetConfig ¶ added in v3.45.0
type EnvSetConfig ¶ added in v3.45.0
type EnvSetConfig struct { InputFormat string `cli:"input-format"` OutputFormat string `cli:"output-format"` // Global flags Debug bool `cli:"debug"` LogLevel string `cli:"log-level"` NoColor bool `cli:"no-color"` Experiments []string `cli:"experiment" normalize:"list"` Profile string `cli:"profile"` }
type EnvUnsetConfig ¶ added in v3.45.0
type EnvUnsetConfig struct { InputFormat string `cli:"input-format"` OutputFormat string `cli:"output-format"` // Global flags Debug bool `cli:"debug"` LogLevel string `cli:"log-level"` NoColor bool `cli:"no-color"` Experiments []string `cli:"experiment" normalize:"list"` Profile string `cli:"profile"` }
type ExitError ¶ added in v3.54.0
type ExitError struct {
// contains filtered or unexported fields
}
ExitError is used to signal that the command should exit with the exit code in `code`. It also wraps an error, which can be used to provide more context.
func NewExitError ¶ added in v3.54.0
NewExitError returns ExitError with the given code and wrapped error.
func (*ExitError) Error ¶ added in v3.54.0
Error prints the message of the wrapped error. It ignores the exit code.
type GitCredentialsHelperConfig ¶ added in v3.63.0
type GitCredentialsHelperConfig struct { JobID string `cli:"job-id" validate:"required"` Action string `cli:"arg:0"` // Global flags Debug bool `cli:"debug"` LogLevel string `cli:"log-level"` NoColor bool `cli:"no-color"` Experiments []string `cli:"experiment" normalize:"list"` Profile string `cli:"profile"` // API config // DebugHTTP bool // Not present due to the possibility of leaking code access tokens to logs AgentAccessToken string `cli:"agent-access-token" validate:"required"` Endpoint string `cli:"endpoint" validate:"required"` NoHTTP2 bool `cli:"no-http2"` }
type LockAcquireConfig ¶ added in v3.46.1
type LockAcquireConfig struct { // Common config options LockScope string `cli:"lock-scope"` SocketsPath string `cli:"sockets-path" normalize:"filepath"` LockWaitTimeout time.Duration `cli:"lock-wait-timeout"` // Global flags Debug bool `cli:"debug"` LogLevel string `cli:"log-level"` NoColor bool `cli:"no-color"` Experiments []string `cli:"experiment" normalize:"list"` Profile string `cli:"profile"` }
type LockDoConfig ¶ added in v3.46.1
type LockDoConfig struct { // Common config options LockScope string `cli:"lock-scope"` SocketsPath string `cli:"sockets-path" normalize:"filepath"` LockWaitTimeout time.Duration `cli:"lock-wait-timeout"` // Global flags Debug bool `cli:"debug"` LogLevel string `cli:"log-level"` NoColor bool `cli:"no-color"` Experiments []string `cli:"experiment" normalize:"list"` Profile string `cli:"profile"` }
type LockDoneConfig ¶ added in v3.46.1
type LockDoneConfig struct { // Common config options LockScope string `cli:"lock-scope"` SocketsPath string `cli:"sockets-path" normalize:"filepath"` // Global flags Debug bool `cli:"debug"` LogLevel string `cli:"log-level"` NoColor bool `cli:"no-color"` Experiments []string `cli:"experiment" normalize:"list"` Profile string `cli:"profile"` }
type LockGetConfig ¶ added in v3.46.1
type LockGetConfig struct { // Common config options LockScope string `cli:"lock-scope"` SocketsPath string `cli:"sockets-path" normalize:"filepath"` // Global flags Debug bool `cli:"debug"` LogLevel string `cli:"log-level"` NoColor bool `cli:"no-color"` Experiments []string `cli:"experiment" normalize:"list"` Profile string `cli:"profile"` }
type LockReleaseConfig ¶ added in v3.46.1
type LockReleaseConfig struct { // Common config options LockScope string `cli:"lock-scope"` SocketsPath string `cli:"sockets-path" normalize:"filepath"` // Global flags Debug bool `cli:"debug"` LogLevel string `cli:"log-level"` NoColor bool `cli:"no-color"` Experiments []string `cli:"experiment" normalize:"list"` Profile string `cli:"profile"` }
type MetaDataExistsConfig ¶
type MetaDataExistsConfig struct { Key string `cli:"arg:0" label:"meta-data key" validate:"required"` Job string `cli:"job"` Build string `cli:"build"` // Global flags Debug bool `cli:"debug"` LogLevel string `cli:"log-level"` NoColor bool `cli:"no-color"` Experiments []string `cli:"experiment" normalize:"list"` Profile string `cli:"profile"` // API config DebugHTTP bool `cli:"debug-http"` AgentAccessToken string `cli:"agent-access-token" validate:"required"` Endpoint string `cli:"endpoint" validate:"required"` NoHTTP2 bool `cli:"no-http2"` }
type MetaDataGetConfig ¶
type MetaDataGetConfig struct { Key string `cli:"arg:0" label:"meta-data key" validate:"required"` Default string `cli:"default"` Job string `cli:"job"` Build string `cli:"build"` // Global flags Debug bool `cli:"debug"` LogLevel string `cli:"log-level"` NoColor bool `cli:"no-color"` Experiments []string `cli:"experiment" normalize:"list"` Profile string `cli:"profile"` // API config DebugHTTP bool `cli:"debug-http"` AgentAccessToken string `cli:"agent-access-token" validate:"required"` Endpoint string `cli:"endpoint" validate:"required"` NoHTTP2 bool `cli:"no-http2"` }
type MetaDataKeysConfig ¶
type MetaDataKeysConfig struct { Job string `cli:"job"` Build string `cli:"build"` // Global flags Debug bool `cli:"debug"` LogLevel string `cli:"log-level"` NoColor bool `cli:"no-color"` Experiments []string `cli:"experiment" normalize:"list"` Profile string `cli:"profile"` // API config DebugHTTP bool `cli:"debug-http"` AgentAccessToken string `cli:"agent-access-token" validate:"required"` Endpoint string `cli:"endpoint" validate:"required"` NoHTTP2 bool `cli:"no-http2"` }
type MetaDataSetConfig ¶
type MetaDataSetConfig struct { Key string `cli:"arg:0" label:"meta-data key" validate:"required"` Value string `cli:"arg:1" label:"meta-data value"` Job string `cli:"job" validate:"required"` // Global flags Debug bool `cli:"debug"` LogLevel string `cli:"log-level"` NoColor bool `cli:"no-color"` Experiments []string `cli:"experiment" normalize:"list"` Profile string `cli:"profile"` // API config DebugHTTP bool `cli:"debug-http"` AgentAccessToken string `cli:"agent-access-token" validate:"required"` Endpoint string `cli:"endpoint" validate:"required"` NoHTTP2 bool `cli:"no-http2"` }
type OIDCTokenConfig ¶ added in v3.41.0
type OIDCTokenConfig struct { Audience string `cli:"audience"` Lifetime int `cli:"lifetime"` Job string `cli:"job" validate:"required"` // TODO: enumerate possible values, perhaps by adding a link to the documentation Claims []string `cli:"claim" normalize:"list"` AWSSessionTags []string `cli:"aws-session-tag" normalize:"list"` // Global flags Debug bool `cli:"debug"` LogLevel string `cli:"log-level"` NoColor bool `cli:"no-color"` Experiments []string `cli:"experiment" normalize:"list"` Profile string `cli:"profile"` // API config DebugHTTP bool `cli:"debug-http"` AgentAccessToken string `cli:"agent-access-token" validate:"required"` Endpoint string `cli:"endpoint" validate:"required"` NoHTTP2 bool `cli:"no-http2"` }
type PipelineUploadConfig ¶
type PipelineUploadConfig struct { FilePath string `cli:"arg:0" label:"upload paths"` Replace bool `cli:"replace"` Job string `cli:"job"` // required, but not in dry-run mode DryRun bool `cli:"dry-run"` DryRunFormat string `cli:"format"` NoInterpolation bool `cli:"no-interpolation"` RedactedVars []string `cli:"redacted-vars" normalize:"list"` RejectSecrets bool `cli:"reject-secrets"` // Used for signing JWKSFile string `cli:"jwks-file"` JWKSKeyID string `cli:"jwks-key-id"` SigningAWSKMSKey string `cli:"signing-aws-kms-key"` DebugSigning bool `cli:"debug-signing"` // Global flags Debug bool `cli:"debug"` LogLevel string `cli:"log-level"` NoColor bool `cli:"no-color"` Experiments []string `cli:"experiment" normalize:"list"` Profile string `cli:"profile"` // API config DebugHTTP bool `cli:"debug-http"` AgentAccessToken string `cli:"agent-access-token"` // required, but not in dry-run mode Endpoint string `cli:"endpoint" validate:"required"` NoHTTP2 bool `cli:"no-http2"` }
type RedactorAddConfig ¶ added in v3.66.0
type RedactorAddConfig struct { File string `cli:"arg:0"` Format string `cli:"format"` // Global flags Debug bool `cli:"debug"` LogLevel string `cli:"log-level"` NoColor bool `cli:"no-color"` Experiments []string `cli:"experiment" normalize:"list"` Profile string `cli:"profile"` // API config DebugHTTP bool `cli:"debug-http"` AgentAccessToken string `cli:"agent-access-token" validate:"required"` Endpoint string `cli:"endpoint" validate:"required"` NoHTTP2 bool `cli:"no-http2"` }
type SecretGetConfig ¶ added in v3.64.0
type SecretGetConfig struct { Key string `cli:"arg:0"` Job string `cli:"job" validate:"required"` SkipRedaction bool `cli:"skip-redaction"` // Global flags Debug bool `cli:"debug"` LogLevel string `cli:"log-level"` NoColor bool `cli:"no-color"` Experiments []string `cli:"experiment" normalize:"list"` Profile string `cli:"profile"` // API config DebugHTTP bool `cli:"debug-http"` AgentAccessToken string `cli:"agent-access-token" validate:"required"` Endpoint string `cli:"endpoint" validate:"required"` NoHTTP2 bool `cli:"no-http2"` }
type SilentExitError ¶ added in v3.54.0
type SilentExitError struct {
// contains filtered or unexported fields
}
SilentExitError instructs ExitCode to not log anything and just exit with status `code`.
func NewSilentExitError ¶ added in v3.54.0
func NewSilentExitError(code int) *SilentExitError
NewSilentExitError returns SilentExitError with the given code.
func (*SilentExitError) Code ¶ added in v3.54.0
func (e *SilentExitError) Code() int
Code returns the exit code.
func (*SilentExitError) Error ¶ added in v3.54.0
func (e *SilentExitError) Error() string
Error prints a message with the exit code. It should not be used as the the purpose of this error is to not print anything.
func (*SilentExitError) Is ¶ added in v3.54.0
func (e *SilentExitError) Is(target error) bool
Is will return true if the target is a SilentExitError with the same code.
type StepGetConfig ¶
type StepGetConfig struct { Attribute string `cli:"arg:0" label:"step attribute"` StepOrKey string `cli:"step" validate:"required"` Build string `cli:"build"` Format string `cli:"format"` // Global flags Debug bool `cli:"debug"` LogLevel string `cli:"log-level"` NoColor bool `cli:"no-color"` Experiments []string `cli:"experiment" normalize:"list"` Profile string `cli:"profile"` // API config DebugHTTP bool `cli:"debug-http"` AgentAccessToken string `cli:"agent-access-token" validate:"required"` Endpoint string `cli:"endpoint" validate:"required"` NoHTTP2 bool `cli:"no-http2"` }
type StepUpdateConfig ¶
type StepUpdateConfig struct { Attribute string `cli:"arg:0" label:"attribute" validate:"required"` Value string `cli:"arg:1" label:"value"` Append bool `cli:"append"` StepOrKey string `cli:"step" validate:"required"` Build string `cli:"build"` // Global flags Debug bool `cli:"debug"` LogLevel string `cli:"log-level"` NoColor bool `cli:"no-color"` Experiments []string `cli:"experiment" normalize:"list"` Profile string `cli:"profile"` // API config DebugHTTP bool `cli:"debug-http"` AgentAccessToken string `cli:"agent-access-token" validate:"required"` Endpoint string `cli:"endpoint" validate:"required"` NoHTTP2 bool `cli:"no-http2"` }
type ToolKeygenConfig ¶ added in v3.58.0
type ToolKeygenConfig struct { Alg string `cli:"alg"` KeyID string `cli:"key-id"` PrivateJWKSFile string `cli:"private-jwks-file" normalize:"filepath"` PublicJWKSFile string `cli:"public-jwks-file" normalize:"filepath"` NoColor bool `cli:"no-color"` Debug bool `cli:"debug"` LogLevel string `cli:"log-level"` Experiments []string `cli:"experiment"` Profile string `cli:"profile"` }
type ToolSignConfig ¶ added in v3.58.0
type ToolSignConfig struct { PipelineFile string `cli:"arg:0" label:"pipeline file"` // These change the behaviour GraphQLToken string `cli:"graphql-token"` Update bool `cli:"update"` NoConfirm bool `cli:"no-confirm"` // Used for signing JWKSFile string `cli:"jwks-file"` JWKSKeyID string `cli:"jwks-key-id"` // AWS KMS key used for signing pipelines AWSKMSKeyID string `cli:"signing-aws-kms-key"` // Enable debug logging for pipeline signing, this depends on debug logging also being enabled DebugSigning bool `cli:"debug-signing"` // Needed for to use GraphQL API OrganizationSlug string `cli:"organization-slug"` PipelineSlug string `cli:"pipeline-slug"` GraphQLEndpoint string `cli:"graphql-endpoint"` // Added to signature Repository string `cli:"repo"` // Global flags Debug bool `cli:"debug"` LogLevel string `cli:"log-level"` NoColor bool `cli:"no-color"` Experiments []string `cli:"experiment" normalize:"list"` Profile string `cli:"profile"` }
Source Files ¶
- acknowledgements.go
- agent_start.go
- annotate.go
- annotation_remove.go
- artifact_download.go
- artifact_search.go
- artifact_shasum.go
- artifact_upload.go
- bootstrap.go
- build_cancel.go
- cancel_signal.go
- commands.go
- doc.go
- env_dump.go
- env_get.go
- env_set.go
- env_unset.go
- errors.go
- git_credentials_helper.go
- global.go
- lock_acquire.go
- lock_common.go
- lock_do.go
- lock_done.go
- lock_get.go
- lock_release.go
- meta_data_exists.go
- meta_data_get.go
- meta_data_keys.go
- meta_data_set.go
- oidc_request_token.go
- pipeline_upload.go
- profiler.go
- redactor_add.go
- secret_get.go
- step_get.go
- step_update.go
- tool_keygen.go
- tool_sign.go