diff --git a/daemon/containerd/image_builder.go b/daemon/containerd/image_builder.go index 66fdad8b20..b2c410cd84 100644 --- a/daemon/containerd/image_builder.go +++ b/daemon/containerd/image_builder.go @@ -36,6 +36,8 @@ import ( ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) +const imageLabelClassicBuilderParent = "org.mobyproject.image.parent" + // GetImageAndReleasableLayer returns an image and releaseable layer for a // reference or ID. Every call to GetImageAndReleasableLayer MUST call // releasableLayer.Release() to prevent leaking of layers. @@ -392,6 +394,8 @@ func (i *ImageService) CreateImage(ctx context.Context, config []byte, parent st ociImgToCreate := dockerImageToDockerOCIImage(*imgToCreate) var layers []ocispec.Descriptor + + var parentDigest digest.Digest // if the image has a parent, we need to start with the parents layers descriptors if parent != "" { parentDesc, err := i.resolveDescriptor(ctx, parent) @@ -404,6 +408,7 @@ func (i *ImageService) CreateImage(ctx context.Context, config []byte, parent st } layers = parentImageManifest.Layers + parentDigest = parentDesc.Digest } // get the info for the new layers @@ -443,6 +448,9 @@ func (i *ImageService) CreateImage(ctx context.Context, config []byte, parent st Name: danglingImageName(commitManifestDesc.Digest), Target: commitManifestDesc, CreatedAt: time.Now(), + Labels: map[string]string{ + imageLabelClassicBuilderParent: parentDigest.String(), + }, } createdImage, err := i.client.ImageService().Update(ctx, img) diff --git a/daemon/containerd/image_commit.go b/daemon/containerd/image_commit.go index 10d56f401f..e9ba7983c6 100644 --- a/daemon/containerd/image_commit.go +++ b/daemon/containerd/image_commit.go @@ -106,6 +106,9 @@ func (i *ImageService) CommitImage(ctx context.Context, cc backend.CommitConfig) Name: danglingImageName(commitManifestDesc.Digest), Target: commitManifestDesc, CreatedAt: time.Now(), + Labels: map[string]string{ + imageLabelClassicBuilderParent: cc.ParentImageID, + }, } if _, err := i.client.ImageService().Update(ctx, img); err != nil { diff --git a/daemon/containerd/image_list.go b/daemon/containerd/image_list.go index 4a71c05fd9..3543d76303 100644 --- a/daemon/containerd/image_list.go +++ b/daemon/containerd/image_list.go @@ -98,8 +98,34 @@ func (i *ImageService) Images(ctx context.Context, opts types.ImageListOptions) contentStore := i.client.ContentStore() uniqueImages := map[digest.Digest]images.Image{} tagsByDigest := map[digest.Digest][]string{} + intermediateImages := map[digest.Digest]struct{}{} + + hideIntermediate := !opts.All + if hideIntermediate { + for _, img := range imgs { + parent, ok := img.Labels[imageLabelClassicBuilderParent] + if ok && parent != "" { + dgst, err := digest.Parse(parent) + if err != nil { + log.G(ctx).WithFields(log.Fields{ + "error": err, + "value": parent, + }).Warnf("invalid %s label value", imageLabelClassicBuilderParent) + } + intermediateImages[dgst] = struct{}{} + } + } + } for _, img := range imgs { + isDangling := isDanglingImage(img) + + if hideIntermediate && isDangling { + if _, ok := intermediateImages[img.Target.Digest]; ok { + continue + } + } + if !filter(img) { continue } @@ -107,7 +133,7 @@ func (i *ImageService) Images(ctx context.Context, opts types.ImageListOptions) dgst := img.Target.Digest uniqueImages[dgst] = img - if isDanglingImage(img) { + if isDangling { continue } diff --git a/daemon/containerd/image_tag.go b/daemon/containerd/image_tag.go index 944403c7ce..946cec904f 100644 --- a/daemon/containerd/image_tag.go +++ b/daemon/containerd/image_tag.go @@ -73,6 +73,17 @@ func (i *ImageService) TagImage(ctx context.Context, imageID image.ID, newTag re return nil } + builderLabel, ok := sourceDanglingImg.Labels[imageLabelClassicBuilderParent] + if ok { + newImg.Labels = map[string]string{ + imageLabelClassicBuilderParent: builderLabel, + } + + if _, err := is.Update(context.Background(), newImg, "labels"); err != nil { + logger.WithError(err).Warnf("failed to set %s label on the newly tagged image", imageLabelClassicBuilderParent) + } + } + // Delete the source dangling image, as it's no longer dangling. if err := is.Delete(context.Background(), sourceDanglingImg.Name); err != nil { logger.WithError(err).Warn("unexpected error when deleting dangling image") diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index 07184cfd1e..d97a94099e 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -4642,7 +4642,7 @@ func (s *DockerCLIBuildSuite) TestBuildMultiStageGlobalArg(c *testing.T) { imgName := "multifrombldargtest" dockerfile := `ARG tag=nosuchtag FROM busybox:${tag} - LABEL multifromtest=1 + LABEL multifromtest2=1 RUN env > /out FROM busybox:${tag} ARG tag @@ -4653,7 +4653,7 @@ func (s *DockerCLIBuildSuite) TestBuildMultiStageGlobalArg(c *testing.T) { cli.WithFlags("--build-arg", "tag=latest")) result.Assert(c, icmd.Success) - result = cli.DockerCmd(c, "images", "-q", "-f", "label=multifromtest=1") + result = cli.DockerCmd(c, "images", "-q", "-f", "label=multifromtest2=1") result.Assert(c, icmd.Success) imgs := strings.Split(strings.TrimSpace(result.Stdout()), "\n")