diff --git a/client/driver/docker.go b/client/driver/docker.go index cc31e547e..ff4545223 100644 --- a/client/driver/docker.go +++ b/client/driver/docker.go @@ -1500,10 +1500,7 @@ func (d *DockerDriver) Periodic() (bool, time.Duration) { // loading it from the file system func (d *DockerDriver) createImage(driverConfig *DockerDriverConfig, client *docker.Client, taskDir *allocdir.TaskDir) (string, error) { image := driverConfig.ImageName - repo, tag := docker.ParseRepositoryTag(image) - if tag == "" { - tag = "latest" - } + repo, tag := parseDockerImage(image) coordinator, callerID := d.getDockerCoordinator(client) @@ -1511,7 +1508,7 @@ func (d *DockerDriver) createImage(driverConfig *DockerDriverConfig, client *doc // is "latest", or ForcePull is set, we have to check for a new version every time so we don't // bother to check and cache the id here. We'll download first, then cache. if driverConfig.ForcePull { - d.logger.Printf("[DEBUG] driver.docker: force pull image '%s:%s' instead of inspecting local", repo, tag) + d.logger.Printf("[DEBUG] driver.docker: force pull image '%s' instead of inspecting local", dockerImageRef(repo, tag)) } else if tag != "latest" { if dockerImage, _ := client.InspectImage(image); dockerImage != nil { // Image exists so just increment its reference count @@ -1544,7 +1541,7 @@ func (d *DockerDriver) pullImage(driverConfig *DockerDriverConfig, client *docke d.logger.Printf("[DEBUG] driver.docker: did not find docker auth for repo %q", repo) } - d.emitEvent("Downloading image %s:%s", repo, tag) + d.emitEvent("Downloading image %s", dockerImageRef(repo, tag)) coordinator, callerID := d.getDockerCoordinator(client) return coordinator.PullImage(driverConfig.ImageName, authOptions, callerID, d.emitEvent) @@ -2183,3 +2180,24 @@ type createContainerClient interface { ListContainers(docker.ListContainersOptions) ([]docker.APIContainers, error) RemoveContainer(opts docker.RemoveContainerOptions) error } + +func parseDockerImage(image string) (repo, tag string) { + repo, tag = docker.ParseRepositoryTag(image) + if tag == "" { + if i := strings.IndexRune(image, '@'); i > -1 { // Has digest (@sha256:...) + // when pulling images with a digest, the repository contains the sha hash, and the tag is empty + // see: https://github.com/fsouza/go-dockerclient/blob/master/image_test.go#L471 + repo = image + } else { + tag = "latest" + } + } + return +} + +func dockerImageRef(repo string, tag string) string { + if tag == "" { + return repo + } + return fmt.Sprintf("%s:%s", repo, tag) +} diff --git a/client/driver/docker_coordinator.go b/client/driver/docker_coordinator.go index 7f975bae5..bbf09de3e 100644 --- a/client/driver/docker_coordinator.go +++ b/client/driver/docker_coordinator.go @@ -178,10 +178,7 @@ func (d *dockerCoordinator) PullImage(image string, authOptions *docker.AuthConf func (d *dockerCoordinator) pullImageImpl(image string, authOptions *docker.AuthConfiguration, future *pullFuture) { defer d.clearPullLogger(image) // Parse the repo and tag - repo, tag := docker.ParseRepositoryTag(image) - if tag == "" { - tag = "latest" - } + repo, tag := parseDockerImage(image) ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -206,18 +203,18 @@ func (d *dockerCoordinator) pullImageImpl(image string, authOptions *docker.Auth err := d.client.PullImage(pullOptions, auth) if ctxErr := ctx.Err(); ctxErr == context.DeadlineExceeded { - d.logger.Printf("[ERR] driver.docker: timeout pulling container %s:%s", repo, tag) + d.logger.Printf("[ERR] driver.docker: timeout pulling container %s", dockerImageRef(repo, tag)) future.set("", recoverablePullError(ctxErr, image)) return } if err != nil { - d.logger.Printf("[ERR] driver.docker: failed pulling container %s:%s: %s", repo, tag, err) + d.logger.Printf("[ERR] driver.docker: failed pulling container %s: %s", dockerImageRef(repo, tag), err) future.set("", recoverablePullError(err, image)) return } - d.logger.Printf("[DEBUG] driver.docker: docker pull %s:%s succeeded", repo, tag) + d.logger.Printf("[DEBUG] driver.docker: docker pull %s succeeded", dockerImageRef(repo, tag)) dockerImage, err := d.client.InspectImage(image) if err != nil { diff --git a/client/driver/docker_test.go b/client/driver/docker_test.go index 5595236c7..7e4b16e1e 100644 --- a/client/driver/docker_test.go +++ b/client/driver/docker_test.go @@ -1092,6 +1092,30 @@ func TestDockerDriver_ForcePull(t *testing.T) { } } +func TestDockerDriver_ForcePull_RepoDigest(t *testing.T) { + if !tu.IsTravis() { + t.Parallel() + } + if !testutil.DockerIsConnected(t) { + t.Skip("Docker not connected") + } + + task, _, _ := dockerTask(t) + task.Config["load"] = "" + task.Config["image"] = "library/busybox@sha256:58ac43b2cc92c687a32c8be6278e50a063579655fe3090125dcb2af0ff9e1a64" + task.Config["force_pull"] = "true" + + client, handle, cleanup := dockerSetup(t, task) + defer cleanup() + + waitForExist(t, client, handle) + + _, err := client.InspectContainer(handle.ContainerID()) + if err != nil { + t.Fatalf("err: %v", err) + } +} + func TestDockerDriver_SecurityOpt(t *testing.T) { if !tu.IsTravis() { t.Parallel() @@ -2458,3 +2482,27 @@ func TestDockerDriver_AdvertiseIPv6Address(t *testing.T) { t.Fatalf("Got GlobalIPv6address %s want GlobalIPv6address with prefix %s", expectedPrefix, container.NetworkSettings.GlobalIPv6Address) } } + +func TestParseDockerImage(t *testing.T) { + tests := []struct { + Image string + Repo string + Tag string + }{ + {"library/hello-world:1.0", "library/hello-world", "1.0"}, + {"library/hello-world", "library/hello-world", "latest"}, + {"library/hello-world:latest", "library/hello-world", "latest"}, + {"library/hello-world@sha256:f5233545e43561214ca4891fd1157e1c3c563316ed8e237750d59bde73361e77", "library/hello-world@sha256:f5233545e43561214ca4891fd1157e1c3c563316ed8e237750d59bde73361e77", ""}, + } + for _, test := range tests { + t.Run(test.Image, func(t *testing.T) { + repo, tag := parseDockerImage(test.Image) + if repo != test.Repo { + t.Errorf("expected repo '%s' but got '%s'", test.Repo, repo) + } + if repo != test.Repo { + t.Errorf("expected tag '%s' but got '%s'", test.Tag, tag) + } + }) + } +}