shells/bash.go (209 lines of code) (raw):
package shells
import (
"bufio"
"bytes"
"fmt"
"gitlab.com/gitlab-org/gitlab-ci-multi-runner/common"
"gitlab.com/gitlab-org/gitlab-ci-multi-runner/helpers"
"io"
"path"
"runtime"
"strconv"
"strings"
)
const bashDetectShell = `if [ -x /usr/local/bin/bash ]; then
exec /usr/local/bin/bash $@
elif [ -x /usr/bin/bash ]; then
exec /usr/bin/bash $@
elif [ -x /bin/bash ]; then
exec /bin/bash $@
elif [ -x /usr/local/bin/sh ]; then
exec /usr/local/bin/sh $@
elif [ -x /usr/bin/sh ]; then
exec /usr/bin/sh $@
elif [ -x /bin/sh ]; then
exec /bin/sh $@
else
echo shell not found
exit 1
fi
`
type BashShell struct {
AbstractShell
Shell string
}
type BashWriter struct {
bytes.Buffer
TemporaryPath string
indent int
}
func (b *BashWriter) GetTemporaryPath() string {
return b.TemporaryPath
}
func (b *BashWriter) Line(text string) {
b.WriteString(strings.Repeat(" ", b.indent) + text + "\n")
}
func (b *BashWriter) CheckForErrors() {
}
func (b *BashWriter) Indent() {
b.indent++
}
func (b *BashWriter) Unindent() {
b.indent--
}
func (b *BashWriter) Command(command string, arguments ...string) {
b.Line(b.buildCommand(command, arguments...))
}
func (b *BashWriter) buildCommand(command string, arguments ...string) string {
list := []string{
helpers.ShellEscape(command),
}
for _, argument := range arguments {
list = append(list, strconv.Quote(argument))
}
return strings.Join(list, " ")
}
func (b *BashWriter) Variable(variable common.JobVariable) {
if variable.File {
variableFile := b.Absolute(path.Join(b.TemporaryPath, variable.Key))
b.Line(fmt.Sprintf("mkdir -p %q", helpers.ToSlash(b.TemporaryPath)))
b.Line(fmt.Sprintf("echo -n %s > %q", helpers.ShellEscape(variable.Value), variableFile))
b.Line(fmt.Sprintf("export %s=%q", helpers.ShellEscape(variable.Key), variableFile))
} else {
b.Line(fmt.Sprintf("export %s=%s", helpers.ShellEscape(variable.Key), helpers.ShellEscape(variable.Value)))
}
}
func (b *BashWriter) IfDirectory(path string) {
b.Line(fmt.Sprintf("if [[ -d %q ]]; then", path))
b.Indent()
}
func (b *BashWriter) IfFile(path string) {
b.Line(fmt.Sprintf("if [[ -e %q ]]; then", path))
b.Indent()
}
func (b *BashWriter) IfCmd(cmd string, arguments ...string) {
cmdline := b.buildCommand(cmd, arguments...)
b.Line(fmt.Sprintf("if %s >/dev/null 2>/dev/null; then", cmdline))
b.Indent()
}
func (b *BashWriter) IfCmdWithOutput(cmd string, arguments ...string) {
cmdline := b.buildCommand(cmd, arguments...)
b.Line(fmt.Sprintf("if %s; then", cmdline))
b.Indent()
}
func (b *BashWriter) Else() {
b.Unindent()
b.Line("else")
b.Indent()
}
func (b *BashWriter) EndIf() {
b.Unindent()
b.Line("fi")
}
func (b *BashWriter) Cd(path string) {
b.Command("cd", path)
}
func (b *BashWriter) MkDir(path string) {
b.Command("mkdir", "-p", path)
}
func (b *BashWriter) MkTmpDir(name string) string {
path := path.Join(b.TemporaryPath, name)
b.MkDir(path)
return path
}
func (b *BashWriter) RmDir(path string) {
b.Command("rm", "-r", "-f", path)
}
func (b *BashWriter) RmFile(path string) {
b.Command("rm", "-f", path)
}
func (b *BashWriter) Absolute(dir string) string {
if path.IsAbs(dir) {
return dir
}
return path.Join("$PWD", dir)
}
func (b *BashWriter) Print(format string, arguments ...interface{}) {
coloredText := helpers.ANSI_RESET + fmt.Sprintf(format, arguments...)
b.Line("echo " + helpers.ShellEscape(coloredText))
}
func (b *BashWriter) Notice(format string, arguments ...interface{}) {
coloredText := helpers.ANSI_BOLD_GREEN + fmt.Sprintf(format, arguments...) + helpers.ANSI_RESET
b.Line("echo " + helpers.ShellEscape(coloredText))
}
func (b *BashWriter) Warning(format string, arguments ...interface{}) {
coloredText := helpers.ANSI_YELLOW + fmt.Sprintf(format, arguments...) + helpers.ANSI_RESET
b.Line("echo " + helpers.ShellEscape(coloredText))
}
func (b *BashWriter) Error(format string, arguments ...interface{}) {
coloredText := helpers.ANSI_BOLD_RED + fmt.Sprintf(format, arguments...) + helpers.ANSI_RESET
b.Line("echo " + helpers.ShellEscape(coloredText))
}
func (b *BashWriter) EmptyLine() {
b.Line("echo")
}
func (b *BashWriter) Finish(trace bool) string {
var buffer bytes.Buffer
w := bufio.NewWriter(&buffer)
if trace {
io.WriteString(w, "set -o xtrace\n")
}
io.WriteString(w, "set -eo pipefail\n")
io.WriteString(w, "set +o noclobber\n")
io.WriteString(w, ": | eval "+helpers.ShellEscape(b.String())+"\n")
io.WriteString(w, "exit 0\n")
w.Flush()
return buffer.String()
}
func (b *BashShell) GetName() string {
return b.Shell
}
func (b *BashShell) GetConfiguration(info common.ShellScriptInfo) (script *common.ShellConfiguration, err error) {
var detectScript string
var shellCommand string
if info.Type == common.LoginShell {
detectScript = strings.Replace(bashDetectShell, "$@", "--login", -1)
shellCommand = b.Shell + " --login"
} else {
detectScript = strings.Replace(bashDetectShell, "$@", "", -1)
shellCommand = b.Shell
}
script = &common.ShellConfiguration{}
script.DockerCommand = []string{"sh", "-c", detectScript}
// su
if info.User != "" {
script.Command = "su"
if runtime.GOOS == "linux" {
script.Arguments = append(script.Arguments, "-s", "/bin/"+b.Shell)
}
script.Arguments = append(script.Arguments, info.User)
script.Arguments = append(script.Arguments, "-c", shellCommand)
} else {
script.Command = b.Shell
if info.Type == common.LoginShell {
script.Arguments = append(script.Arguments, "--login")
}
}
return
}
func (b *BashShell) GenerateScript(buildStage common.BuildStage, info common.ShellScriptInfo) (script string, err error) {
w := &BashWriter{
TemporaryPath: info.Build.FullProjectDir() + ".tmp",
}
if buildStage == common.BuildStagePrepare {
if len(info.Build.Hostname) != 0 {
w.Line("echo " + strconv.Quote("Running on $(hostname) via "+info.Build.Hostname+"..."))
} else {
w.Line("echo " + strconv.Quote("Running on $(hostname)..."))
}
}
err = b.writeScript(w, buildStage, info)
script = w.Finish(info.Build.IsDebugTraceEnabled())
return
}
func (b *BashShell) IsDefault() bool {
return runtime.GOOS != "windows" && b.Shell == "bash"
}
func init() {
common.RegisterShell(&BashShell{Shell: "sh"})
common.RegisterShell(&BashShell{Shell: "bash"})
}