github.com/secure-build/gitlab-runner@v12.5.0+incompatible/shells/powershell.go (about) 1 package shells 2 3 import ( 4 "bufio" 5 "bytes" 6 "fmt" 7 "io" 8 "path" 9 "path/filepath" 10 "strings" 11 12 "gitlab.com/gitlab-org/gitlab-runner/common" 13 "gitlab.com/gitlab-org/gitlab-runner/helpers" 14 ) 15 16 const dockerWindowsExecutor = "docker-windows" 17 18 type PowerShell struct { 19 AbstractShell 20 } 21 22 type PsWriter struct { 23 bytes.Buffer 24 TemporaryPath string 25 indent int 26 } 27 28 func psQuote(text string) string { 29 // taken from: http://www.robvanderwoude.com/escapechars.php 30 text = strings.Replace(text, "`", "``", -1) 31 // text = strings.Replace(text, "\0", "`0", -1) 32 text = strings.Replace(text, "\a", "`a", -1) 33 text = strings.Replace(text, "\b", "`b", -1) 34 text = strings.Replace(text, "\f", "^f", -1) 35 text = strings.Replace(text, "\r", "`r", -1) 36 text = strings.Replace(text, "\n", "`n", -1) 37 text = strings.Replace(text, "\t", "^t", -1) 38 text = strings.Replace(text, "\v", "^v", -1) 39 text = strings.Replace(text, "#", "`#", -1) 40 text = strings.Replace(text, "'", "`'", -1) 41 text = strings.Replace(text, "\"", "`\"", -1) 42 return "\"" + text + "\"" 43 } 44 45 func psQuoteVariable(text string) string { 46 text = psQuote(text) 47 text = strings.Replace(text, "$", "`$", -1) 48 return text 49 } 50 51 func (b *PsWriter) GetTemporaryPath() string { 52 return b.TemporaryPath 53 } 54 55 func (b *PsWriter) Line(text string) { 56 b.WriteString(strings.Repeat(" ", b.indent) + text + "\r\n") 57 } 58 59 func (b *PsWriter) CheckForErrors() { 60 b.checkErrorLevel() 61 } 62 63 func (b *PsWriter) Indent() { 64 b.indent++ 65 } 66 67 func (b *PsWriter) Unindent() { 68 b.indent-- 69 } 70 71 func (b *PsWriter) checkErrorLevel() { 72 b.Line("if(!$?) { Exit &{if($LASTEXITCODE) {$LASTEXITCODE} else {1}} }") 73 b.Line("") 74 } 75 76 func (b *PsWriter) Command(command string, arguments ...string) { 77 b.Line(b.buildCommand(command, arguments...)) 78 b.checkErrorLevel() 79 } 80 81 func (b *PsWriter) buildCommand(command string, arguments ...string) string { 82 list := []string{ 83 psQuote(command), 84 } 85 86 for _, argument := range arguments { 87 list = append(list, psQuote(argument)) 88 } 89 90 return "& " + strings.Join(list, " ") 91 } 92 93 func (b *PsWriter) TmpFile(name string) string { 94 filePath := b.Absolute(path.Join(b.TemporaryPath, name)) 95 return helpers.ToBackslash(filePath) 96 } 97 98 func (b *PsWriter) EnvVariableKey(name string) string { 99 return fmt.Sprintf("$%s", name) 100 } 101 102 func (b *PsWriter) Variable(variable common.JobVariable) { 103 if variable.File { 104 variableFile := b.TmpFile(variable.Key) 105 b.Line(fmt.Sprintf("New-Item -ItemType directory -Force -Path %s | out-null", psQuote(helpers.ToBackslash(b.TemporaryPath)))) 106 b.Line(fmt.Sprintf("Set-Content %s -Value %s -Encoding UTF8 -Force", psQuote(variableFile), psQuoteVariable(variable.Value))) 107 b.Line("$" + variable.Key + "=" + psQuote(variableFile)) 108 } else { 109 b.Line("$" + variable.Key + "=" + psQuoteVariable(variable.Value)) 110 } 111 112 b.Line("$env:" + variable.Key + "=$" + variable.Key) 113 } 114 115 func (b *PsWriter) IfDirectory(path string) { 116 b.Line("if(Test-Path " + psQuote(helpers.ToBackslash(path)) + " -PathType Container) {") 117 b.Indent() 118 } 119 120 func (b *PsWriter) IfFile(path string) { 121 b.Line("if(Test-Path " + psQuote(helpers.ToBackslash(path)) + " -PathType Leaf) {") 122 b.Indent() 123 } 124 125 func (b *PsWriter) IfCmd(cmd string, arguments ...string) { 126 b.ifInTryCatch(b.buildCommand(cmd, arguments...) + " 2>$null") 127 } 128 129 func (b *PsWriter) IfCmdWithOutput(cmd string, arguments ...string) { 130 b.ifInTryCatch(b.buildCommand(cmd, arguments...)) 131 } 132 133 func (b *PsWriter) ifInTryCatch(cmd string) { 134 b.Line("Set-Variable -Name cmdErr -Value $false") 135 b.Line("Try {") 136 b.Indent() 137 b.Line(cmd) 138 b.Line("if(!$?) { throw &{if($LASTEXITCODE) {$LASTEXITCODE} else {1}} }") 139 b.Unindent() 140 b.Line("} Catch {") 141 b.Indent() 142 b.Line("Set-Variable -Name cmdErr -Value $true") 143 b.Unindent() 144 b.Line("}") 145 b.Line("if(!$cmdErr) {") 146 b.Indent() 147 } 148 149 func (b *PsWriter) Else() { 150 b.Unindent() 151 b.Line("} else {") 152 b.Indent() 153 } 154 155 func (b *PsWriter) EndIf() { 156 b.Unindent() 157 b.Line("}") 158 } 159 160 func (b *PsWriter) Cd(path string) { 161 b.Line("cd " + psQuote(helpers.ToBackslash(path))) 162 b.checkErrorLevel() 163 } 164 165 func (b *PsWriter) MkDir(path string) { 166 b.Line(fmt.Sprintf("New-Item -ItemType directory -Force -Path %s | out-null", psQuote(helpers.ToBackslash(path)))) 167 } 168 169 func (b *PsWriter) MkTmpDir(name string) string { 170 path := helpers.ToBackslash(path.Join(b.TemporaryPath, name)) 171 b.MkDir(path) 172 173 return path 174 } 175 176 func (b *PsWriter) RmDir(path string) { 177 path = psQuote(helpers.ToBackslash(path)) 178 b.Line("if( (Get-Command -Name Remove-Item2 -Module NTFSSecurity -ErrorAction SilentlyContinue) -and (Test-Path " + path + " -PathType Container) ) {") 179 b.Indent() 180 b.Line("Remove-Item2 -Force -Recurse " + path) 181 b.Unindent() 182 b.Line("} elseif(Test-Path " + path + ") {") 183 b.Indent() 184 b.Line("Remove-Item -Force -Recurse " + path) 185 b.Unindent() 186 b.Line("}") 187 b.Line("") 188 } 189 190 func (b *PsWriter) RmFile(path string) { 191 path = psQuote(helpers.ToBackslash(path)) 192 b.Line("if( (Get-Command -Name Remove-Item2 -Module NTFSSecurity -ErrorAction SilentlyContinue) -and (Test-Path " + path + " -PathType Leaf) ) {") 193 b.Indent() 194 b.Line("Remove-Item2 -Force " + path) 195 b.Unindent() 196 b.Line("} elseif(Test-Path " + path + ") {") 197 b.Indent() 198 b.Line("Remove-Item -Force " + path) 199 b.Unindent() 200 b.Line("}") 201 b.Line("") 202 } 203 204 func (b *PsWriter) Print(format string, arguments ...interface{}) { 205 coloredText := helpers.ANSI_RESET + fmt.Sprintf(format, arguments...) 206 b.Line("echo " + psQuoteVariable(coloredText)) 207 } 208 209 func (b *PsWriter) Notice(format string, arguments ...interface{}) { 210 coloredText := helpers.ANSI_BOLD_GREEN + fmt.Sprintf(format, arguments...) + helpers.ANSI_RESET 211 b.Line("echo " + psQuoteVariable(coloredText)) 212 } 213 214 func (b *PsWriter) Warning(format string, arguments ...interface{}) { 215 coloredText := helpers.ANSI_YELLOW + fmt.Sprintf(format, arguments...) + helpers.ANSI_RESET 216 b.Line("echo " + psQuoteVariable(coloredText)) 217 } 218 219 func (b *PsWriter) Error(format string, arguments ...interface{}) { 220 coloredText := helpers.ANSI_BOLD_RED + fmt.Sprintf(format, arguments...) + helpers.ANSI_RESET 221 b.Line("echo " + psQuoteVariable(coloredText)) 222 } 223 224 func (b *PsWriter) EmptyLine() { 225 b.Line("echo \"\"") 226 } 227 228 func (b *PsWriter) Absolute(dir string) string { 229 if filepath.IsAbs(dir) { 230 return dir 231 } 232 233 b.Line("$CurrentDirectory = (Resolve-Path .\\).Path") 234 return filepath.Join("$CurrentDirectory", dir) 235 } 236 237 func (b *PsWriter) Finish(trace bool) string { 238 var buffer bytes.Buffer 239 w := bufio.NewWriter(&buffer) 240 241 // write BOM 242 io.WriteString(w, "\xef\xbb\xbf") 243 if trace { 244 io.WriteString(w, "Set-PSDebug -Trace 2\r\n") 245 } 246 247 // add empty line to close code-block when it is piped to STDIN 248 b.Line("") 249 io.WriteString(w, b.String()) 250 w.Flush() 251 return buffer.String() 252 } 253 254 func (b *PowerShell) GetName() string { 255 return "powershell" 256 } 257 258 func (b *PowerShell) GetConfiguration(info common.ShellScriptInfo) (script *common.ShellConfiguration, err error) { 259 script = &common.ShellConfiguration{ 260 Command: "powershell", 261 Arguments: []string{"-noprofile", "-noninteractive", "-executionpolicy", "Bypass", "-command"}, 262 PassFile: info.Build.Runner.Executor != dockerWindowsExecutor, 263 Extension: "ps1", 264 DockerCommand: []string{"PowerShell", "-NoProfile", "-NoLogo", "-InputFormat", "text", "-OutputFormat", "text", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", "-"}, 265 } 266 return 267 } 268 269 func (b *PowerShell) GenerateScript(buildStage common.BuildStage, info common.ShellScriptInfo) (script string, err error) { 270 w := &PsWriter{ 271 TemporaryPath: info.Build.TmpProjectDir(), 272 } 273 274 if buildStage == common.BuildStagePrepare { 275 if len(info.Build.Hostname) != 0 { 276 w.Line("echo \"Running on $env:computername via " + psQuoteVariable(info.Build.Hostname) + "...\"") 277 } else { 278 w.Line("echo \"Running on $env:computername...\"") 279 } 280 } 281 282 err = b.writeScript(w, buildStage, info) 283 284 // No need to set up BOM or tracing since no script was generated. 285 if w.Buffer.Len() > 0 { 286 script = w.Finish(info.Build.IsDebugTraceEnabled()) 287 } 288 289 return 290 } 291 292 func (b *PowerShell) IsDefault() bool { 293 return false 294 } 295 296 func init() { 297 common.RegisterShell(&PowerShell{}) 298 }