diff --git a/api/handlers.go b/api/handlers.go index 941af22db8..86e82e6c59 100644 --- a/api/handlers.go +++ b/api/handlers.go @@ -147,6 +147,7 @@ func getImagesJSON(c *context, w http.ResponseWriter, r *http.Request) { // grouping images by Id, and concat their RepoTags if entry, existed := groupImages[image.Id]; existed { entry.RepoTags = append(entry.RepoTags, image.RepoTags...) + entry.RepoDigests = append(entry.RepoDigests, image.RepoDigests...) groupImages[image.Id] = entry } else { groupImages[image.Id] = image.Image @@ -166,6 +167,18 @@ func getImagesJSON(c *context, w http.ResponseWriter, r *http.Request) { } } image.RepoTags = result + + // de-duplicate RepoDigests + result = []string{} + seen = map[string]bool{} + for _, val := range image.RepoDigests { + if _, ok := seen[val]; !ok { + result = append(result, val) + seen[val] = true + } + } + image.RepoDigests = result + images = append(images, image) } w.Header().Set("Content-Type", "application/json") @@ -504,7 +517,11 @@ func postImagesCreate(c *context, w http.ResponseWriter, r *http.Request) { } if tag := r.Form.Get("tag"); tag != "" { - image += ":" + tag + if tagHasDigest(tag) { + image += "@" + tag + } else { + image += ":" + tag + } } var errorMessage string diff --git a/api/utils.go b/api/utils.go index 2c1a2852d0..0268552ad3 100644 --- a/api/utils.go +++ b/api/utils.go @@ -219,3 +219,7 @@ func int64ValueOrZero(r *http.Request, k string) int64 { } return val } + +func tagHasDigest(tag string) bool { + return strings.Contains(tag, ":") +} diff --git a/cluster/image.go b/cluster/image.go index 4f32448a91..ea23588caf 100644 --- a/cluster/image.go +++ b/cluster/image.go @@ -26,6 +26,7 @@ func (image *Image) Match(IDOrName string, matchTag bool) bool { repoName, tag := parsers.ParseRepositoryTag(IDOrName) + // match repotag for _, imageRepoTag := range image.RepoTags { imageRepoName, imageTag := parsers.ParseRepositoryTag(imageRepoTag) @@ -36,6 +37,18 @@ func (image *Image) Match(IDOrName string, matchTag bool) bool { return true } } + + // match repodigests + for _, imageDigest := range image.RepoDigests { + imageRepoName, imageDigest := parsers.ParseRepositoryTag(imageDigest) + + if matchTag == false && imageRepoName == repoName { + return true + } + if imageRepoName == repoName && (imageDigest == tag || tag == "") { + return true + } + } return false } @@ -55,7 +68,9 @@ type Images []*Image func (images Images) Filter(opts ImageFilterOptions) Images { includeAll := func(image *Image) bool { // TODO: this is wrong if RepoTags == [] - return opts.All || (len(image.RepoTags) != 0 && image.RepoTags[0] != ":") + return opts.All || + (len(image.RepoTags) != 0 && image.RepoTags[0] != ":") || + (len(image.RepoDigests) != 0 && image.RepoDigests[0] != "@") } includeFilter := func(image *Image) bool { diff --git a/cluster/image_test.go b/cluster/image_test.go index 816521aee3..41f472f9f5 100644 --- a/cluster/image_test.go +++ b/cluster/image_test.go @@ -13,6 +13,7 @@ func TestMatch(t *testing.T) { img.Id = "378954456789" img.RepoTags = []string{"name:latest"} + img.RepoDigests = []string{"name@sha256:a973f1415c489a934bf56dd653079d36b4ec717760215645726439de9705911d"} assert.True(t, img.Match("378954456789", true)) assert.True(t, img.Match("3789", true)) @@ -33,6 +34,9 @@ func TestMatch(t *testing.T) { assert.True(t, img.Match("name", false)) assert.False(t, img.Match("nam", false)) assert.False(t, img.Match("na", false)) + + assert.True(t, img.Match("name@sha256:a973f1415c489a934bf56dd653079d36b4ec717760215645726439de9705911d", true)) + assert.False(t, img.Match("name@sha256:111111415c489a934bf56dd653079d36b4ec717760215645726439de9705911d", true)) } func TestMatchPrivateRepo(t *testing.T) { diff --git a/test/integration/api/pull.bats b/test/integration/api/pull.bats index 9d9e7c87cc..bcf58bc2fc 100644 --- a/test/integration/api/pull.bats +++ b/test/integration/api/pull.bats @@ -36,6 +36,22 @@ function teardown() { done } +@test "docker pull with image digest" { + start_docker 2 + swarm_manage + + # make sure no image exists + run docker_swarm images -q + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 0 ] + + docker_swarm pull jimmyxian/busybox@sha256:649374debd26307573564fcf9748d39db33ef61fbf88ee84c3af10fd7e08765d + + run docker_swarm images --digests + [ "$status" -eq 0 ] + [[ "${output}" == *"sha256:649374debd26307573564fcf9748d39db33ef61fbf88ee84c3af10fd7e08765d"* ]] +} + @test "docker pull -check error code" { start_docker 2 swarm_manage diff --git a/test/integration/api/rmi.bats b/test/integration/api/rmi.bats index c8872438c3..ceb4e2f399 100644 --- a/test/integration/api/rmi.bats +++ b/test/integration/api/rmi.bats @@ -103,6 +103,28 @@ function teardown() { [[ "${output}" != *"testimage"* ]] } +@test "docker rmi with image digest" { + start_docker 2 + swarm_manage + + # make sure no image exists + run docker_swarm images -q + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 0 ] + + docker_swarm pull jimmyxian/busybox@sha256:649374debd26307573564fcf9748d39db33ef61fbf88ee84c3af10fd7e08765d + + run docker_swarm images --digests + [ "$status" -eq 0 ] + [[ "${output}" == *"sha256:649374debd26307573564fcf9748d39db33ef61fbf88ee84c3af10fd7e08765d"* ]] + + docker_swarm rmi jimmyxian/busybox@sha256:649374debd26307573564fcf9748d39db33ef61fbf88ee84c3af10fd7e08765d + + run docker_swarm images --digests + [[ "${output}" != *"busybox"* ]] + [[ "${output}" != *"sha256:649374debd26307573564fcf9748d39db33ef61fbf88ee84c3af10fd7e08765d"* ]] +} + @test "docker rmi --force with image tag" { start_docker_with_busybox 1 start_docker 1 diff --git a/test/integration/api/run.bats b/test/integration/api/run.bats index 037abb7be6..9603fd4e03 100644 --- a/test/integration/api/run.bats +++ b/test/integration/api/run.bats @@ -26,6 +26,22 @@ function teardown() { [[ "${output}" == *"cannot specify 64-byte hexadecimal strings"* ]] } +@test "docker run with image digest" { + start_docker 2 + swarm_manage + + docker_swarm pull jimmyxian/busybox@sha256:649374debd26307573564fcf9748d39db33ef61fbf88ee84c3af10fd7e08765d + + # make sure no container exist + run docker_swarm ps -qa + [ "${#lines[@]}" -eq 0 ] + + docker_swarm run -d --name test_container jimmyxian/busybox@sha256:649374debd26307573564fcf9748d39db33ef61fbf88ee84c3af10fd7e08765d sleep 100 + + # verify, container is running + [ -n $(docker_swarm ps -q --filter=name=test_container --filter=status=running) ] +} + @test "docker run with resources" { start_docker_with_busybox 2 swarm_manage