1// SPDX-License-Identifier: MIT
2
3package executor
4
5import (
6 "strings"
7)
8
9// ContainerStepHost (Runner.Worker/Handlers/StepHost.cs:102-296): the steps of
10// a job that has a container run through `docker exec` in it.
11
12// stepContainer is the job container when steps run in it, else nil.
13func (j *job) stepContainer() *container {
14 if j.cs == nil {
15 return nil
16 }
17 return j.cs.job
18}
19
20// containerReady is the check of ActionRunner.cs:157-164: the container must
21// exist. After a failed "Initialize containers" an always() step gets the
22// runner's error.
23func (j *job) containerReady() error {
24 if c := j.stepContainer(); c != nil && c.id == "" {
25 return ghErrorf("Value cannot be null. (Parameter 'ContainerId')")
26 }
27 return nil
28}
29
30// hostEnv is the environment a handler gives its process: the step's
31// variables then the runtime variables of the contexts (GITHUB_*, RUNNER_*)
32// (ScriptHandler.cs:289-299). It is what `docker exec -e` passes on.
33func (j *job) handlerEnv(stepEnv *ordered) *ordered {
34 env := stepEnv.Clone()
35 j.mu.Lock()
36 rt := j.state.RuntimeEnv()
37 j.mu.Unlock()
38 for _, k := range rt.Keys() {
39 v, _ := rt.Get(k)
40 env.Set(k, v)
41 }
42 return env
43}
44
45// execArgv is ContainerStepHost.ExecuteAsync (StepHost.cs:196-246):
46// `docker exec -i --workdir <dir> -e NAME... [-e PATH="<prepend>:<PATH>"] <id>
47// <command> <arguments>`. The values of the variables are not on the command
48// line: -e NAME takes them from the environment of docker, which gets them
49// with the host paths translated to the container's (:248-254). args are the
50// arguments of the command, already split.
51func (j *job) execArgv(c *container, workDir string, env *ordered, command string, args []string) (argv []string, procEnv []string) {
52 argv = []string{j.dockerPath(), "exec", "-i", "--workdir", workDir}
53 cenv := newOrdered()
54 for _, k := range env.Keys() {
55 v, _ := env.Get(k)
56 v = c.translateToContainer(v)
57 // PATH is set below. The docker process is started by its absolute
58 // path, so the variables of the container are not read by it, except
59 // those the CLI itself reads.
60 p := j.envForCLI(k, v, "")
61 argv = append(argv, p.args...)
62 for _, kv := range p.env {
63 cenv.Set(k, strings.TrimPrefix(kv, k+"="))
64 }
65 }
66 j.mu.Lock()
67 prepend := j.state.Prepend()
68 j.mu.Unlock()
69 if len(prepend) > 0 {
70 rev := make([]string, len(prepend))
71 for i, p := range prepend {
72 rev[len(prepend)-1-i] = p
73 }
74 full := strings.Join(rev, ":")
75 if c.runtimePath != "" {
76 full += ":" + c.runtimePath
77 }
78 // The value is in the environment of docker.exec only: the process is
79 // started by absolute path and `docker exec` runs nothing of its own.
80 argv = append(argv, "-e", "PATH")
81 cenv.Set("PATH", full)
82 }
83 argv = append(argv, c.id, command)
84 argv = append(argv, args...)
85 return argv, j.dockerProcessEnv(cenv)
86}
87
88// containerScript prepares a run step for the container: the script is
89// written on the host in RUNNER_TEMP, which the container has at /__w/_temp,
90// and the shell gets that path.
91func (j *job) containerScript(c *container, plan ShellPlan, in scriptIn, s *stepRun) (argv []string, err error) {
92 sc, err := WriteScriptFor(j.tempRoot, plan, in.script, c.translateToContainer)
93 if err != nil {
94 return nil, err
95 }
96 j.mu.Lock()
97 workspace := j.state.GitHub("workspace")
98 j.mu.Unlock()
99 wd := c.resolvePath(ResolveWorkingDirectory(workspace, in.workingDirectory))
100 var rest []string
101 if len(sc.Argv) > 1 {
102 rest = sc.Argv[1:]
103 }
104 henv := j.handlerEnv(s.actEnv)
105 if err := checkEnvNames(henv.Keys()); err != nil {
106 return nil, err
107 }
108 a, penv := j.execArgv(c, wd, henv, plan.CommandPath, rest)
109 s.rawEnvList = penv
110 return a, nil
111}