github.com/mitchellh/packer@v1.3.2/builder/docker/driver_docker.go (about)

     1  package docker
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"log"
     8  	"os"
     9  	"os/exec"
    10  	"regexp"
    11  	"runtime"
    12  	"strings"
    13  	"sync"
    14  
    15  	"github.com/hashicorp/go-version"
    16  	"github.com/hashicorp/packer/packer"
    17  	"github.com/hashicorp/packer/template/interpolate"
    18  )
    19  
    20  type DockerDriver struct {
    21  	Ui  packer.Ui
    22  	Ctx *interpolate.Context
    23  
    24  	l sync.Mutex
    25  }
    26  
    27  func (d *DockerDriver) DeleteImage(id string) error {
    28  	var stderr bytes.Buffer
    29  	cmd := exec.Command("docker", "rmi", id)
    30  	cmd.Stderr = &stderr
    31  
    32  	log.Printf("Deleting image: %s", id)
    33  	if err := cmd.Start(); err != nil {
    34  		return err
    35  	}
    36  
    37  	if err := cmd.Wait(); err != nil {
    38  		err = fmt.Errorf("Error deleting image: %s\nStderr: %s",
    39  			err, stderr.String())
    40  		return err
    41  	}
    42  
    43  	return nil
    44  }
    45  
    46  func (d *DockerDriver) Commit(id string, author string, changes []string, message string) (string, error) {
    47  	var stdout bytes.Buffer
    48  	var stderr bytes.Buffer
    49  
    50  	args := []string{"commit"}
    51  	if author != "" {
    52  		args = append(args, "--author", author)
    53  	}
    54  	for _, change := range changes {
    55  		args = append(args, "--change", change)
    56  	}
    57  	if message != "" {
    58  		args = append(args, "--message", message)
    59  	}
    60  	args = append(args, id)
    61  
    62  	log.Printf("Committing container with args: %v", args)
    63  	cmd := exec.Command("docker", args...)
    64  	cmd.Stdout = &stdout
    65  	cmd.Stderr = &stderr
    66  
    67  	if err := cmd.Start(); err != nil {
    68  		return "", err
    69  	}
    70  
    71  	if err := cmd.Wait(); err != nil {
    72  		err = fmt.Errorf("Error committing container: %s\nStderr: %s",
    73  			err, stderr.String())
    74  		return "", err
    75  	}
    76  
    77  	return strings.TrimSpace(stdout.String()), nil
    78  }
    79  
    80  func (d *DockerDriver) Export(id string, dst io.Writer) error {
    81  	var stderr bytes.Buffer
    82  	cmd := exec.Command("docker", "export", id)
    83  	cmd.Stdout = dst
    84  	cmd.Stderr = &stderr
    85  
    86  	log.Printf("Exporting container: %s", id)
    87  	if err := cmd.Start(); err != nil {
    88  		return err
    89  	}
    90  
    91  	if err := cmd.Wait(); err != nil {
    92  		err = fmt.Errorf("Error exporting: %s\nStderr: %s",
    93  			err, stderr.String())
    94  		return err
    95  	}
    96  
    97  	return nil
    98  }
    99  
   100  func (d *DockerDriver) Import(path string, repo string) (string, error) {
   101  	var stdout, stderr bytes.Buffer
   102  	cmd := exec.Command("docker", "import", "-", repo)
   103  	cmd.Stdout = &stdout
   104  	cmd.Stderr = &stderr
   105  	stdin, err := cmd.StdinPipe()
   106  	if err != nil {
   107  		return "", err
   108  	}
   109  
   110  	// There should be only one artifact of the Docker builder
   111  	file, err := os.Open(path)
   112  	if err != nil {
   113  		return "", err
   114  	}
   115  	defer file.Close()
   116  
   117  	if err := cmd.Start(); err != nil {
   118  		return "", err
   119  	}
   120  
   121  	go func() {
   122  		defer stdin.Close()
   123  		io.Copy(stdin, file)
   124  	}()
   125  
   126  	if err := cmd.Wait(); err != nil {
   127  		return "", fmt.Errorf("Error importing container: %s\n\nStderr: %s", err, stderr.String())
   128  	}
   129  
   130  	return strings.TrimSpace(stdout.String()), nil
   131  }
   132  
   133  func (d *DockerDriver) IPAddress(id string) (string, error) {
   134  	var stderr, stdout bytes.Buffer
   135  	cmd := exec.Command(
   136  		"docker",
   137  		"inspect",
   138  		"--format",
   139  		"{{ .NetworkSettings.IPAddress }}",
   140  		id)
   141  	cmd.Stdout = &stdout
   142  	cmd.Stderr = &stderr
   143  	if err := cmd.Run(); err != nil {
   144  		return "", fmt.Errorf("Error: %s\n\nStderr: %s", err, stderr.String())
   145  	}
   146  
   147  	return strings.TrimSpace(stdout.String()), nil
   148  }
   149  
   150  func (d *DockerDriver) Login(repo, user, pass string) error {
   151  	d.l.Lock()
   152  
   153  	version_running, err := d.Version()
   154  	if err != nil {
   155  		d.l.Unlock()
   156  		return err
   157  	}
   158  
   159  	// Version 17.07.0 of Docker adds support for the new
   160  	// `--password-stdin` option which can be used to offer
   161  	// password via the standard input, rather than passing
   162  	// the password and/or token using a command line switch.
   163  	constraint, err := version.NewConstraint(">= 17.07.0")
   164  	if err != nil {
   165  		d.l.Unlock()
   166  		return err
   167  	}
   168  
   169  	cmd := exec.Command("docker")
   170  	cmd.Args = append(cmd.Args, "login")
   171  
   172  	if user != "" {
   173  		cmd.Args = append(cmd.Args, "-u", user)
   174  	}
   175  
   176  	if pass != "" {
   177  		if constraint.Check(version_running) {
   178  			cmd.Args = append(cmd.Args, "--password-stdin")
   179  
   180  			stdin, err := cmd.StdinPipe()
   181  			if err != nil {
   182  				d.l.Unlock()
   183  				return err
   184  			}
   185  			io.WriteString(stdin, pass)
   186  			stdin.Close()
   187  		} else {
   188  			cmd.Args = append(cmd.Args, "-p", pass)
   189  		}
   190  	}
   191  
   192  	if repo != "" {
   193  		cmd.Args = append(cmd.Args, repo)
   194  	}
   195  
   196  	err = runAndStream(cmd, d.Ui)
   197  	if err != nil {
   198  		d.l.Unlock()
   199  		return err
   200  	}
   201  
   202  	return nil
   203  }
   204  
   205  func (d *DockerDriver) Logout(repo string) error {
   206  	args := []string{"logout"}
   207  	if repo != "" {
   208  		args = append(args, repo)
   209  	}
   210  
   211  	cmd := exec.Command("docker", args...)
   212  	err := runAndStream(cmd, d.Ui)
   213  	d.l.Unlock()
   214  	return err
   215  }
   216  
   217  func (d *DockerDriver) Pull(image string) error {
   218  	cmd := exec.Command("docker", "pull", image)
   219  	return runAndStream(cmd, d.Ui)
   220  }
   221  
   222  func (d *DockerDriver) Push(name string) error {
   223  	cmd := exec.Command("docker", "push", name)
   224  	return runAndStream(cmd, d.Ui)
   225  }
   226  
   227  func (d *DockerDriver) SaveImage(id string, dst io.Writer) error {
   228  	var stderr bytes.Buffer
   229  	cmd := exec.Command("docker", "save", id)
   230  	cmd.Stdout = dst
   231  	cmd.Stderr = &stderr
   232  
   233  	log.Printf("Exporting image: %s", id)
   234  	if err := cmd.Start(); err != nil {
   235  		return err
   236  	}
   237  
   238  	if err := cmd.Wait(); err != nil {
   239  		err = fmt.Errorf("Error exporting: %s\nStderr: %s",
   240  			err, stderr.String())
   241  		return err
   242  	}
   243  
   244  	return nil
   245  }
   246  
   247  func (d *DockerDriver) StartContainer(config *ContainerConfig) (string, error) {
   248  	// Build up the template data
   249  	var tplData startContainerTemplate
   250  	tplData.Image = config.Image
   251  	ctx := *d.Ctx
   252  	ctx.Data = &tplData
   253  
   254  	// Args that we're going to pass to Docker
   255  	args := []string{"run"}
   256  	if config.Privileged {
   257  		args = append(args, "--privileged")
   258  	}
   259  	for host, guest := range config.Volumes {
   260  		if runtime.GOOS == "windows" {
   261  			// docker-toolbox can't handle the normal C:\filepath format in CLI
   262  			host = strings.Replace(host, "\\", "/", -1)
   263  			host = strings.Replace(host, "C:/", "/c/", 1)
   264  		}
   265  		args = append(args, "-v", fmt.Sprintf("%s:%s", host, guest))
   266  	}
   267  	for _, v := range config.RunCommand {
   268  		v, err := interpolate.Render(v, &ctx)
   269  		if err != nil {
   270  			return "", err
   271  		}
   272  
   273  		args = append(args, v)
   274  	}
   275  	d.Ui.Message(fmt.Sprintf(
   276  		"Run command: docker %s", strings.Join(args, " ")))
   277  
   278  	// Start the container
   279  	var stdout, stderr bytes.Buffer
   280  	cmd := exec.Command("docker", args...)
   281  	cmd.Stdout = &stdout
   282  	cmd.Stderr = &stderr
   283  
   284  	log.Printf("Starting container with args: %v", args)
   285  	if err := cmd.Start(); err != nil {
   286  		return "", err
   287  	}
   288  
   289  	log.Println("Waiting for container to finish starting")
   290  	if err := cmd.Wait(); err != nil {
   291  		if _, ok := err.(*exec.ExitError); ok {
   292  			err = fmt.Errorf("Docker exited with a non-zero exit status.\nStderr: %s",
   293  				stderr.String())
   294  		}
   295  
   296  		return "", err
   297  	}
   298  
   299  	// Capture the container ID, which is alone on stdout
   300  	return strings.TrimSpace(stdout.String()), nil
   301  }
   302  
   303  func (d *DockerDriver) StopContainer(id string) error {
   304  	if err := exec.Command("docker", "kill", id).Run(); err != nil {
   305  		return err
   306  	}
   307  
   308  	return exec.Command("docker", "rm", id).Run()
   309  }
   310  
   311  func (d *DockerDriver) TagImage(id string, repo string, force bool) error {
   312  	args := []string{"tag"}
   313  
   314  	// detect running docker version before tagging
   315  	// flag `force` for docker tagging was removed after Docker 1.12.0
   316  	// to keep its backward compatibility, we are not going to remove `force`
   317  	// option, but to ignore it when Docker version >= 1.12.0
   318  	//
   319  	// for more detail, please refer to the following links:
   320  	// - https://docs.docker.com/engine/deprecated/#/f-flag-on-docker-tag
   321  	// - https://github.com/docker/docker/pull/23090
   322  	version_running, err := d.Version()
   323  	if err != nil {
   324  		return err
   325  	}
   326  
   327  	version_deprecated, err := version.NewVersion("1.12.0")
   328  	if err != nil {
   329  		// should never reach this line
   330  		return err
   331  	}
   332  
   333  	if force {
   334  		if version_running.LessThan(version_deprecated) {
   335  			args = append(args, "-f")
   336  		} else {
   337  			// do nothing if Docker version >= 1.12.0
   338  			log.Printf("[WARN] option: \"force\" will be ignored here")
   339  			log.Printf("since it was removed after Docker 1.12.0 released")
   340  		}
   341  	}
   342  	args = append(args, id, repo)
   343  
   344  	var stderr bytes.Buffer
   345  	cmd := exec.Command("docker", args...)
   346  	cmd.Stderr = &stderr
   347  
   348  	if err := cmd.Start(); err != nil {
   349  		return err
   350  	}
   351  
   352  	if err := cmd.Wait(); err != nil {
   353  		err = fmt.Errorf("Error tagging image: %s\nStderr: %s",
   354  			err, stderr.String())
   355  		return err
   356  	}
   357  
   358  	return nil
   359  }
   360  
   361  func (d *DockerDriver) Verify() error {
   362  	if _, err := exec.LookPath("docker"); err != nil {
   363  		return err
   364  	}
   365  
   366  	return nil
   367  }
   368  
   369  func (d *DockerDriver) Version() (*version.Version, error) {
   370  	output, err := exec.Command("docker", "-v").Output()
   371  	if err != nil {
   372  		return nil, err
   373  	}
   374  
   375  	match := regexp.MustCompile(version.VersionRegexpRaw).FindSubmatch(output)
   376  	if match == nil {
   377  		return nil, fmt.Errorf("unknown version: %s", output)
   378  	}
   379  
   380  	return version.NewVersion(string(match[0]))
   381  }