github.phpd.cn/hashicorp/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 }