// Run runs cmd in the given image using the docker client cl. It mounts cwd into containerMount in the running container and sends on the following channels: // // - rmContainerCh: a function closure that the receiver should call, after they receive on errCh or exitCodeCh, to remove the container. this is commonly done with a 'defer' // - stdOut: all logs from STDOUT in the container. this may never receive // - stdErr: all logs from STDERR in the container. this may never receive // - exitCodeCh: the exit code of the container // - errCh: any error in setting up or running the container. if errCh receives, exitCodeCh may not receive func Run( cl *docker.Client, image *Image, taskName, cwd, containerMount, cmd string, env []string, rmContainerCh chan<- func(), stdOut chan<- Log, stdErr chan<- Log, exitCodeCh chan<- int, errCh chan<- error, ) { mounts := []docker.Mount{ {Name: "pwd", Source: cwd, Destination: containerMount, Mode: "rxw"}, } cmdSpl := strings.Split(cmd, " ") containerName := NewContainerName(taskName, cwd) createContainerOpts, hostConfig := CreateAndStartContainerOpts(image.String(), containerName, cmdSpl, env, mounts, containerMount) if err := EnsureImage(cl, image.String(), func() (io.Writer, error) { return os.Stdout, nil }); err != nil { errCh <- err return } container, err := cl.CreateContainer(createContainerOpts) if err != nil { errCh <- err } rmContainerCh <- func() { if err := cl.RemoveContainer(docker.RemoveContainerOptions{ID: container.ID, Force: true}); err != nil { log.Warn("Error removing container %s (%s)", container.ID, err) } } log.Debug(CmdStr(createContainerOpts, hostConfig)) attachOpts := AttachToContainerOpts(container.ID, NewChanWriter(stdOut), NewChanWriter(stdErr)) // attach before the container starts, so we get all the logs etc... go AttachAndWait(cl, container.ID, attachOpts, exitCodeCh, errCh) if startErr := cl.StartContainer(container.ID, &hostConfig); startErr != nil { errCh <- err return } }
// execOrDie prints the command before executing it, executes it and returns its output. // if the command failed, calls log.Die with a helpful error message func runOrDie(cmd *exec.Cmd, env []string) []byte { cmd.Env = env log.Info(cmdStr(cmd)) log.Debug("Env: %s", envStr(cmd)) out, err := cmd.CombinedOutput() if err != nil { var s string if len(out) > 0 { s += fmt.Sprintf("%s\n", string(out)) } if err != nil { s += err.Error() } log.Die(s) } return out }
// tarDir tars the entire directory under dir. It skips all hidden directories (starting with a '.') as well as files or directories which skip returns true for. If skip returns true for a directory, its entire contents will be skipped and skip will not be called for any of that directory's contents // // - All hidden directories // - All files with names in skipNames and all directories with names in skipDirs func tarDir(dir string, tarWriter *tar.Writer, skip func(path string, info os.FileInfo) bool) error { return filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { if err != nil { return err } if skip(path, info) { if info.IsDir() { return filepath.SkipDir } return nil } if info.IsDir() { return nil } rel, err := filepath.Rel(dir, path) if err != nil { return err } log.Debug("Adding to Docker build context: %s", path) fileBytes, err := ioutil.ReadFile(rel) if err != nil { return err } // empty link because filepath.Walk doesn't follow symbolic links hdr, err := tar.FileInfoHeader(info, "") if err != nil { return err } tarWriter.WriteHeader(hdr) tarWriter.Write(fileBytes) return nil }) }