executors/shell/executor_shell.go (130 lines of code) (raw):

package shell import ( "bytes" "errors" "io/ioutil" "os" "os/exec" "path/filepath" "fmt" "github.com/Sirupsen/logrus" "github.com/kardianos/osext" "gitlab.com/gitlab-org/gitlab-ci-multi-runner/common" "gitlab.com/gitlab-org/gitlab-ci-multi-runner/executors" "gitlab.com/gitlab-org/gitlab-ci-multi-runner/helpers" "time" ) type executor struct { executors.AbstractExecutor } func (s *executor) Prepare(options common.ExecutorPrepareOptions) error { if options.User != "" { s.Shell().User = options.User } // expand environment variables to have current directory wd, err := os.Getwd() if err != nil { return fmt.Errorf("Getwd: %v", err) } mapping := func(key string) string { switch key { case "PWD": return wd default: return "" } } s.DefaultBuildsDir = os.Expand(s.DefaultBuildsDir, mapping) s.DefaultCacheDir = os.Expand(s.DefaultCacheDir, mapping) // Pass control to executor err = s.AbstractExecutor.Prepare(options) if err != nil { return err } s.Println("Using Shell executor...") return nil } func (s *executor) killAndWait(cmd *exec.Cmd, waitCh chan error) error { for { s.Debugln("Aborting command...") helpers.KillProcessGroup(cmd) select { case <-time.After(time.Second): case err := <-waitCh: return err } } } func (s *executor) Run(cmd common.ExecutorCommand) error { // Create execution command c := exec.Command(s.BuildShell.Command, s.BuildShell.Arguments...) if c == nil { return errors.New("Failed to generate execution command") } helpers.SetProcessGroup(c) defer helpers.KillProcessGroup(c) // Fill process environment variables c.Env = append(os.Environ(), s.BuildShell.Environment...) c.Stdout = s.Trace c.Stderr = s.Trace if s.BuildShell.PassFile { scriptDir, err := ioutil.TempDir("", "build_script") if err != nil { return err } defer os.RemoveAll(scriptDir) scriptFile := filepath.Join(scriptDir, "script."+s.BuildShell.Extension) err = ioutil.WriteFile(scriptFile, []byte(cmd.Script), 0700) if err != nil { return err } c.Args = append(c.Args, scriptFile) } else { c.Stdin = bytes.NewBufferString(cmd.Script) } // Start a process err := c.Start() if err != nil { return fmt.Errorf("Failed to start process: %s", err) } // Wait for process to finish waitCh := make(chan error) go func() { err := c.Wait() if _, ok := err.(*exec.ExitError); ok { err = &common.BuildError{Inner: err} } waitCh <- err }() // Support process abort select { case err = <-waitCh: return err case <-cmd.Context.Done(): return s.killAndWait(c, waitCh) } } func init() { // Look for self runnerCommand, err := osext.Executable() if err != nil { logrus.Warningln(err) } options := executors.ExecutorOptions{ DefaultBuildsDir: "$PWD/builds", DefaultCacheDir: "$PWD/cache", SharedBuildsDir: true, Shell: common.ShellScriptInfo{ Shell: common.GetDefaultShell(), Type: common.LoginShell, RunnerCommand: runnerCommand, }, ShowHostname: false, } creator := func() common.Executor { return &executor{ AbstractExecutor: executors.AbstractExecutor{ ExecutorOptions: options, }, } } featuresUpdater := func(features *common.FeaturesInfo) { features.Variables = true } common.RegisterExecutor("shell", executors.DefaultExecutorProvider{ Creator: creator, FeaturesUpdater: featuresUpdater, }) }