github.com/secure-build/gitlab-runner@v12.5.0+incompatible/shells/cmd.go (about) 1 package shells 2 3 import ( 4 "bufio" 5 "bytes" 6 "fmt" 7 "io" 8 "path" 9 "path/filepath" 10 "runtime" 11 "strings" 12 13 "gitlab.com/gitlab-org/gitlab-runner/common" 14 "gitlab.com/gitlab-org/gitlab-runner/helpers" 15 "gitlab.com/gitlab-org/gitlab-runner/helpers/featureflags" 16 ) 17 18 type CmdShell struct { 19 AbstractShell 20 } 21 22 type CmdWriter struct { 23 bytes.Buffer 24 TemporaryPath string 25 indent int 26 disableDelayedErrorLevelExpansion bool 27 } 28 29 func batchQuote(text string) string { 30 return "\"" + batchEscapeInsideQuotedString(text) + "\"" 31 } 32 33 func batchEscapeInsideQuotedString(text string) string { 34 // taken from: http://www.robvanderwoude.com/escapechars.php 35 text = strings.Replace(text, "^", "^^", -1) 36 text = strings.Replace(text, "!", "^^!", -1) 37 text = strings.Replace(text, "&", "^&", -1) 38 text = strings.Replace(text, "<", "^<", -1) 39 text = strings.Replace(text, ">", "^>", -1) 40 text = strings.Replace(text, "|", "^|", -1) 41 text = strings.Replace(text, "\r", "", -1) 42 text = strings.Replace(text, "\n", "!nl!", -1) 43 return text 44 } 45 46 func batchEscapeVariable(text string) string { 47 text = strings.Replace(text, "%", "%%", -1) 48 text = batchEscape(text) 49 return text 50 } 51 52 // If not inside a quoted string (e.g., echo text), escape more things 53 func batchEscape(text string) string { 54 text = batchEscapeInsideQuotedString(text) 55 text = strings.Replace(text, "(", "^(", -1) 56 text = strings.Replace(text, ")", "^)", -1) 57 return text 58 } 59 60 func (b *CmdShell) GetName() string { 61 return "cmd" 62 } 63 64 func (b *CmdWriter) GetTemporaryPath() string { 65 return b.TemporaryPath 66 } 67 68 func (b *CmdWriter) Line(text string) { 69 b.WriteString(strings.Repeat(" ", b.indent) + text + "\r\n") 70 } 71 72 func (b *CmdWriter) CheckForErrors() { 73 b.checkErrorLevel() 74 } 75 76 func (b *CmdWriter) Indent() { 77 b.indent++ 78 } 79 80 func (b *CmdWriter) Unindent() { 81 b.indent-- 82 } 83 84 func (b *CmdWriter) checkErrorLevel() { 85 errCheck := "IF !errorlevel! NEQ 0 exit /b !errorlevel!" 86 b.Line(b.updateErrLevelCheck(errCheck)) 87 b.Line("") 88 } 89 90 func (b *CmdWriter) updateErrLevelCheck(errCheck string) string { 91 if b.disableDelayedErrorLevelExpansion { 92 return strings.Replace(errCheck, "!", "%", -1) 93 } 94 95 return errCheck 96 } 97 98 func (b *CmdWriter) Command(command string, arguments ...string) { 99 b.Line(b.buildCommand(command, arguments...)) 100 b.checkErrorLevel() 101 } 102 103 func (b *CmdWriter) buildCommand(command string, arguments ...string) string { 104 list := []string{ 105 batchQuote(command), 106 } 107 108 for _, argument := range arguments { 109 list = append(list, batchQuote(argument)) 110 } 111 112 return strings.Join(list, " ") 113 } 114 115 func (b *CmdWriter) TmpFile(name string) string { 116 filePath := b.Absolute(path.Join(b.TemporaryPath, name)) 117 return helpers.ToBackslash(filePath) 118 } 119 120 func (b *CmdWriter) EnvVariableKey(name string) string { 121 return fmt.Sprintf("%%%s%%", name) 122 } 123 124 func (b *CmdWriter) Variable(variable common.JobVariable) { 125 if variable.File { 126 variableFile := b.TmpFile(variable.Key) 127 b.Line(fmt.Sprintf("md %q 2>NUL 1>NUL", batchEscape(helpers.ToBackslash(b.TemporaryPath)))) 128 b.Line(fmt.Sprintf("echo %s > %s", batchEscapeVariable(variable.Value), batchEscape(variableFile))) 129 b.Line("SET " + batchEscapeVariable(variable.Key) + "=" + batchEscape(variableFile)) 130 } else { 131 b.Line("SET " + batchEscapeVariable(variable.Key) + "=" + batchEscapeVariable(variable.Value)) 132 } 133 } 134 135 func (b *CmdWriter) IfDirectory(path string) { 136 b.Line("IF EXIST " + batchQuote(helpers.ToBackslash(path)) + " (") 137 b.Indent() 138 } 139 140 func (b *CmdWriter) IfFile(path string) { 141 b.Line("IF EXIST " + batchQuote(helpers.ToBackslash(path)) + " (") 142 b.Indent() 143 } 144 145 func (b *CmdWriter) IfCmd(cmd string, arguments ...string) { 146 cmdline := b.buildCommand(cmd, arguments...) 147 b.Line(fmt.Sprintf("%s 2>NUL 1>NUL", cmdline)) 148 errCheck := "IF !errorlevel! EQU 0 (" 149 b.Line(b.updateErrLevelCheck(errCheck)) 150 b.Indent() 151 } 152 153 func (b *CmdWriter) IfCmdWithOutput(cmd string, arguments ...string) { 154 cmdline := b.buildCommand(cmd, arguments...) 155 b.Line(cmdline) 156 errCheck := "IF !errorlevel! EQU 0 (" 157 b.Line(b.updateErrLevelCheck(errCheck)) 158 b.Indent() 159 } 160 161 func (b *CmdWriter) Else() { 162 b.Unindent() 163 b.Line(") ELSE (") 164 b.Indent() 165 } 166 167 func (b *CmdWriter) EndIf() { 168 b.Unindent() 169 b.Line(")") 170 } 171 172 func (b *CmdWriter) Cd(path string) { 173 b.Line("cd /D " + batchQuote(helpers.ToBackslash(path))) 174 b.checkErrorLevel() 175 } 176 177 func (b *CmdWriter) MkDir(path string) { 178 args := batchQuote(helpers.ToBackslash(path)) + " 2>NUL 1>NUL" 179 b.Line("dir " + args + " || md " + args) 180 } 181 182 func (b *CmdWriter) MkTmpDir(name string) string { 183 path := helpers.ToBackslash(path.Join(b.TemporaryPath, name)) 184 b.MkDir(path) 185 186 return path 187 } 188 189 func (b *CmdWriter) RmDir(path string) { 190 b.Line("rd /s /q " + batchQuote(helpers.ToBackslash(path)) + " 2>NUL 1>NUL") 191 } 192 193 func (b *CmdWriter) RmFile(path string) { 194 b.Line("del /f /q " + batchQuote(helpers.ToBackslash(path)) + " 2>NUL 1>NUL") 195 } 196 197 func (b *CmdWriter) Print(format string, arguments ...interface{}) { 198 coloredText := helpers.ANSI_RESET + fmt.Sprintf(format, arguments...) + helpers.ANSI_RESET 199 b.Line("echo " + batchEscapeVariable(coloredText)) 200 } 201 202 func (b *CmdWriter) Notice(format string, arguments ...interface{}) { 203 coloredText := helpers.ANSI_BOLD_GREEN + fmt.Sprintf(format, arguments...) + helpers.ANSI_RESET 204 b.Line("echo " + batchEscapeVariable(coloredText)) 205 } 206 207 func (b *CmdWriter) Warning(format string, arguments ...interface{}) { 208 coloredText := helpers.ANSI_YELLOW + fmt.Sprintf(format, arguments...) + helpers.ANSI_RESET 209 b.Line("echo " + batchEscapeVariable(coloredText)) 210 } 211 212 func (b *CmdWriter) Error(format string, arguments ...interface{}) { 213 coloredText := helpers.ANSI_BOLD_RED + fmt.Sprintf(format, arguments...) + helpers.ANSI_RESET 214 b.Line("echo " + batchEscapeVariable(coloredText)) 215 } 216 217 func (b *CmdWriter) EmptyLine() { 218 b.Line("echo.") 219 } 220 221 func (b *CmdWriter) Absolute(dir string) string { 222 if filepath.IsAbs(dir) { 223 return dir 224 } 225 return filepath.Join("%CD%", dir) 226 } 227 228 func (b *CmdWriter) Finish(trace bool) string { 229 var buffer bytes.Buffer 230 w := bufio.NewWriter(&buffer) 231 232 if trace { 233 io.WriteString(w, "@echo on\r\n") 234 } else { 235 io.WriteString(w, "@echo off\r\n") 236 } 237 238 io.WriteString(w, "setlocal enableextensions\r\n") 239 io.WriteString(w, "setlocal enableDelayedExpansion\r\n") 240 io.WriteString(w, "set nl=^\r\n\r\n\r\n") 241 242 io.WriteString(w, b.String()) 243 w.Flush() 244 return buffer.String() 245 } 246 247 func (b *CmdShell) GetConfiguration(info common.ShellScriptInfo) (script *common.ShellConfiguration, err error) { 248 script = &common.ShellConfiguration{ 249 Command: "cmd", 250 Arguments: []string{"/C"}, 251 PassFile: true, 252 Extension: "cmd", 253 } 254 return 255 } 256 257 func (b *CmdShell) GenerateScript(buildStage common.BuildStage, info common.ShellScriptInfo) (script string, err error) { 258 w := &CmdWriter{ 259 TemporaryPath: info.Build.TmpProjectDir(), 260 disableDelayedErrorLevelExpansion: info.Build.IsFeatureFlagOn(featureflags.CmdDisableDelayedErrorLevelExpansion), 261 } 262 263 if buildStage == common.BuildStagePrepare { 264 if len(info.Build.Hostname) != 0 { 265 w.Line("echo Running on %COMPUTERNAME% via " + batchEscape(info.Build.Hostname) + "...") 266 } else { 267 w.Line("echo Running on %COMPUTERNAME%...") 268 } 269 } 270 271 err = b.writeScript(w, buildStage, info) 272 script = w.Finish(info.Build.IsDebugTraceEnabled()) 273 return 274 } 275 276 func (b *CmdShell) IsDefault() bool { 277 // TODO: Remove in 13.0 - Make PowerShell default shell for Windows. 278 return runtime.GOOS == "windows" 279 } 280 281 func init() { 282 common.RegisterShell(&CmdShell{}) 283 }