File size: 3,368 Bytes
d61821a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
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