1// SPDX-License-Identifier: MIT
2
3package executor
4
5import (
6 "fmt"
7 "path/filepath"
8 "runtime"
9 "strings"
10)
11
12// alpineCheck is the command of ContainerStepHost.DetermineNodeRuntimeVersion
13// (StepHost.cs:176): the distribution is read from the release files.
14const alpineCheck = `cat /etc/*release | grep ^ID`
15
16// nodeInContainer is ContainerStepHost.DetermineNodeRuntimeVersion
17// (StepHost.cs:154-196, container hooks off): "<version>_alpine" when the
18// image is Alpine (musl, where the glibc builds do not run), else the version
19// itself. The output of the probe is not logged, only the command is.
20func (j *job) nodeInContainer(s *stepRun, c *container, version string, cancel <-chan struct{}) (string, error) {
21 cmd := j.newDockerCmd("exec", dockerPart{line: ""}, raw(c.id),
22 dockerPart{line: `sh -c "` + alpineCheck + `"`, args: []string{"sh", "-c", alpineCheck}})
23 j.out(s.num, "##[command]"+cmd.line)
24 var out []string
25 res, err := runProcess(procSpec{
26 Argv: cmd.argv, Dir: dirOrCwd(j.githubValue("workspace")), Env: j.processEnv(newOrdered()), Credential: j.cfg.Credential,
27 OnLine: func(l string) {
28 if l != "" {
29 out = append(out, l)
30 }
31 },
32 OnErrLine: func(l string) {
33 if l != "" {
34 out = append(out, l)
35 }
36 },
37 Cancel: cancel, TermAfter: j.cfg.TermAfter, KillAfter: j.cfg.KillAfter, WaitDelay: j.cfg.WaitDelay, Signal: j.cfg.Signal,
38 })
39 if err != nil {
40 return "", err
41 }
42 if res.Cancelled {
43 return "", errCancelled
44 }
45 if res.ExitCode == 0 {
46 for _, l := range out {
47 j.debug(s.num, l)
48 if strings.Contains(strings.ToLower(l), "alpine") {
49 return j.alpineNode(s, version)
50 }
51 }
52 }
53 j.debug(s.num, "Running JavaScript Action with default external tool: "+version)
54 return version, nil
55}
56
57// alpineNode is CheckPlatformForAlpineContainer (StepHost.cs:248-279): the
58// alpine builds exist for x64 only.
59func (j *job) alpineNode(s *stepRun, version string) (string, error) {
60 if runtime.GOARCH != "amd64" {
61 arch := map[string]string{"arm64": "Arm64", "arm": "Arm", "386": "X86"}[runtime.GOARCH]
62 if arch == "" {
63 arch = runtime.GOARCH
64 }
65 return "", fmt.Errorf("JavaScript Actions in Alpine containers are only supported on x64 Linux runners. Detected Linux %s", arch)
66 }
67 v := version + "_alpine"
68 j.debug(s.num, "Container distribution is alpine. Running JavaScript Action with external tool: "+v)
69 return v, nil
70}
71
72// runNodeInContainer is NodeScriptActionHandler.RunAsync (:127-190) with a
73// ContainerStepHost: the node of the externals mounted at /__e runs the script
74// at its container path, in the container's workspace.
75func (j *job) runNodeInContainer(s *stepRun, c *container, script string, env *ordered, cancel <-chan struct{}) stepResult {
76 num := s.num
77 fail := func(err error) stepResult { j.errorf(num, "%s", err.Error()); return resFailed }
78 if err := j.containerReady(); err != nil {
79 return fail(err)
80 }
81 version, err := j.nodeInContainer(s, c, s.nodeVersion, cancel)
82 if err != nil {
83 return j.containerResult(s, err)
84 }
85 file := c.resolvePath(filepath.Join(j.externalsDir(), version, "bin", "node"))
86 j.mu.Lock()
87 wd := j.state.GitHub("workspace")
88 j.mu.Unlock()
89 henv := j.handlerEnv(env)
90 // After the runtime variables of the contexts, which hold GITHUB_SERVER_URL.
91 if s.serverURL != "" {
92 henv.Set("GITHUB_SERVER_URL", s.serverURL)
93 }
94 if err := checkEnvNames(henv.Keys()); err != nil {
95 return fail(err)
96 }
97 argv, penv := j.execArgv(c, c.resolvePath(wd), henv, file, []string{c.resolvePath(`"` + script + `"`)})
98 s.rawEnvList = penv
99 return j.runProc(s, file, j.state.Layout().WorkRoot, argv, "Node Action run completed with exit code %d", cancel)
100}