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  }