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  }