1package shells
2
3import (
4	"bufio"
5	"bytes"
6	"fmt"
7	"io"
8	"strings"
9
10	"gitlab.com/gitlab-org/gitlab-runner/common"
11)
12
13// pwshTrapShellScript is used to wrap a shell script in a trap that makes sure the script always exits
14// with exit code of 0. This can be useful in container environments where exiting with an exit code different from 0
15// would kill the container.
16// At the same time it writes to a file the actual exit code of the script as well as the filename
17// At the same time it writes the actual exit code of the script as well as
18// the filename of the script (as json) to a file.
19// With powershell $? returns True if the last command was successful so the exit_code is set to 0 in that case
20const pwshTrapShellScript = `
21function runner_script_trap() {
22	$lastExit = $?
23	$code = 1
24	If($lastExit -eq "True"){ $code = 0 }
25
26	$log_file=%q
27	$out_json= '{"command_exit_code": ' + $code + ', "script": "' + $MyInvocation.MyCommand.Name + '"}'
28
29	# Make sure the command status will always be printed on a new line
30	if ( $((Get-Content -Path $log_file | Measure-Object -Line).Lines) -gt 0 )
31	{
32		Add-Content $log_file "$out_json"
33	}
34	else
35	{
36		Add-Content $log_file ""
37		Add-Content $log_file "$out_json"
38	}
39}
40
41trap {runner_script_trap}
42
43`
44
45type PwshTrapShellWriter struct {
46	*PsWriter
47
48	logFile string
49}
50
51func (b *PwshTrapShellWriter) Finish(trace bool) string {
52	var buffer bytes.Buffer
53	w := bufio.NewWriter(&buffer)
54
55	b.writeShebang(w)
56	b.writeTrap(w)
57	b.writeTrace(w, trace)
58	b.writeScript(w)
59
60	_ = w.Flush()
61	return buffer.String()
62}
63
64func (b *PwshTrapShellWriter) writeTrap(w io.Writer) {
65	// For code readability purpose, the pwshTrapShellScript is written with \n as EOL within the script
66	// However when written into the generated script for a job, the \n used within the trap script is
67	// replaced by the shell EOL to avoid having multiple EOL within it and to keep it consistent
68	_, _ = fmt.Fprintf(w, strings.ReplaceAll(pwshTrapShellScript, "\n", b.EOL), b.logFile)
69}
70
71type PwshTrapShell struct {
72	*PowerShell
73
74	LogFile string
75}
76
77func (b *PwshTrapShell) GenerateScript(buildStage common.BuildStage, info common.ShellScriptInfo) (string, error) {
78	w := &PwshTrapShellWriter{
79		PsWriter: &PsWriter{
80			TemporaryPath: info.Build.TmpProjectDir(),
81			Shell:         b.Shell,
82			EOL:           b.EOL,
83		},
84		logFile: b.LogFile,
85	}
86
87	return b.generateScript(w, buildStage, info)
88}
89