diff --git a/apps/gitlab-runner-helper/main.go b/apps/gitlab-runner-helper/main.go index 206fcf20..e469fbb6 100644 --- a/apps/gitlab-runner-helper/main.go +++ b/apps/gitlab-runner-helper/main.go @@ -29,6 +29,9 @@ func main() { } }() + // Must run before cli.NewApp. See commands/steps.RecoverArgv. + steps.RecoverArgv() + app := cli.NewApp() app.Name = filepath.Base(os.Args[0]) app.Usage = "a GitLab Runner Helper" diff --git a/commands/steps/recovery.go b/commands/steps/recovery.go new file mode 100644 index 00000000..0d0d2d04 --- /dev/null +++ b/commands/steps/recovery.go @@ -0,0 +1,49 @@ +package steps + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "os" +) + +// RecoveryEnvVar carries argv (base64-encoded JSON) for the helper to +// reconstruct when an image entrypoint drops CMD. Set by the executor on +// the build container; read and unset by RecoverArgv. +const RecoveryEnvVar = "_GITLAB_RUNNER_HELPER_NATIVE_STEPS_ARGV" + +// EncodeRecoveryArgv returns the RecoveryEnvVar payload for argv. +func EncodeRecoveryArgv(argv []string) (string, error) { + raw, err := json.Marshal(argv) + if err != nil { + return "", fmt.Errorf("marshalling argv: %w", err) + } + return base64.StdEncoding.EncodeToString(raw), nil +} + +// RecoverArgv reconstructs os.Args from RecoveryEnvVar when the helper was +// invoked with no subcommand, the fingerprint of an `sh -c` entrypoint +// dropping CMD[1:] as positional params that exec doesn't forward. Call +// from main before any arg processing. No-op otherwise. +func RecoverArgv() { + if len(os.Args) > 1 { + return + } + encoded := os.Getenv(RecoveryEnvVar) + if encoded == "" { + return + } + raw, err := base64.StdEncoding.DecodeString(encoded) + if err != nil { + return + } + var argv []string + if err := json.Unmarshal(raw, &argv); err != nil { + return + } + if len(argv) == 0 { + return + } + os.Args = append(os.Args, argv...) + _ = os.Unsetenv(RecoveryEnvVar) +} diff --git a/executors/docker/docker.go b/executors/docker/docker.go index a3997d28..05b871f5 100644 --- a/executors/docker/docker.go +++ b/executors/docker/docker.go @@ -30,6 +30,7 @@ import ( v1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/sirupsen/logrus" + "gitlab.com/gitlab-org/gitlab-runner/commands/steps" "gitlab.com/gitlab-org/gitlab-runner/common" "gitlab.com/gitlab-org/gitlab-runner/common/buildlogger" "gitlab.com/gitlab-org/gitlab-runner/common/spec" @@ -942,9 +943,16 @@ func (e *executor) createContainerConfig( if e.Build.UseNativeSteps() { config.Cmd = append([]string{bootstrappedBinary, "steps", "serve"}, config.Cmd...) - // Environment variables interferes with steps. Given this situation, when - // native steps are enabled, we no longer add the env vars to the container. - config.Env = nil + // Recovery for `sh -c` entrypoints that drop CMD[1:] as positional + // params exec doesn't forward. See commands/steps.RecoveryEnvVar. + recoveryArgv, err := steps.EncodeRecoveryArgv(config.Cmd[1:]) + if err != nil { + return nil, fmt.Errorf("encoding native-steps recovery argv: %w", err) + } + + // Job env vars are delivered by step-runner; the recovery var is + // the only one we set here. + config.Env = []string{steps.RecoveryEnvVar + "=" + recoveryArgv} } // user config should only be set in build containers