diff --git a/daemon/containerd/image_builder.go b/daemon/containerd/image_builder.go index ff985ea626..d2ca826439 100644 --- a/daemon/containerd/image_builder.go +++ b/daemon/containerd/image_builder.go @@ -22,6 +22,7 @@ import ( "github.com/containerd/log" "github.com/distribution/reference" "github.com/docker/docker/api/types/backend" + "github.com/docker/docker/api/types/container" imagetypes "github.com/docker/docker/api/types/image" "github.com/docker/docker/api/types/registry" "github.com/docker/docker/builder" @@ -47,6 +48,19 @@ const ( // "1" means that the image was created directly from the "FROM scratch". imageLabelClassicBuilderFromScratch = "org.mobyproject.image.fromscratch" + + // digest of the ContainerConfig stored in the content store. + imageLabelClassicBuilderContainerConfig = "org.mobyproject.image.containerconfig" +) + +const ( + // gc.ref label that associates the ContainerConfig content blob with the + // corresponding Config content. + contentLabelGcRefContainerConfig = "containerd.io/gc.ref.content.moby/container.config" + + // Digest of the image this ContainerConfig blobs describes. + // Only ContainerConfig content should be labelled with it. + contentLabelClassicBuilderImage = "org.mobyproject.content.image" ) // GetImageAndReleasableLayer returns an image and releaseable layer for a @@ -451,7 +465,7 @@ func (i *ImageService) CreateImage(ctx context.Context, config []byte, parent st }) } - createdImageId, err := i.createImageOCI(ctx, ociImgToCreate, parentDigest, layers) + createdImageId, err := i.createImageOCI(ctx, ociImgToCreate, parentDigest, layers, imgToCreate.ContainerConfig) if err != nil { return nil, err } @@ -461,6 +475,7 @@ func (i *ImageService) CreateImage(ctx context.Context, config []byte, parent st func (i *ImageService) createImageOCI(ctx context.Context, imgToCreate imagespec.DockerOCIImage, parentDigest digest.Digest, layers []ocispec.Descriptor, + containerConfig container.Config, ) (dimage.ID, error) { // Necessary to prevent the contents from being GC'd // between writing them here and creating an image @@ -474,7 +489,7 @@ func (i *ImageService) createImageOCI(ctx context.Context, imgToCreate imagespec } }() - manifestDesc, err := writeContentsForImage(ctx, i.snapshotter, i.client.ContentStore(), imgToCreate, layers) + manifestDesc, ccDesc, err := writeContentsForImage(ctx, i.snapshotter, i.client.ContentStore(), imgToCreate, layers, containerConfig) if err != nil { return "", err } @@ -484,7 +499,8 @@ func (i *ImageService) createImageOCI(ctx context.Context, imgToCreate imagespec Target: manifestDesc, CreatedAt: time.Now(), Labels: map[string]string{ - imageLabelClassicBuilderParent: parentDigest.String(), + imageLabelClassicBuilderParent: parentDigest.String(), + imageLabelClassicBuilderContainerConfig: ccDesc.Digest.String(), }, } @@ -511,10 +527,17 @@ func (i *ImageService) createImageOCI(ctx context.Context, imgToCreate imagespec } // writeContentsForImage will commit oci image config and manifest into containerd's content store. -func writeContentsForImage(ctx context.Context, snName string, cs content.Store, newConfig imagespec.DockerOCIImage, layers []ocispec.Descriptor) (ocispec.Descriptor, error) { +func writeContentsForImage(ctx context.Context, snName string, cs content.Store, + newConfig imagespec.DockerOCIImage, layers []ocispec.Descriptor, + containerConfig container.Config, +) ( + manifestDesc ocispec.Descriptor, + containerConfigDesc ocispec.Descriptor, + _ error, +) { newConfigJSON, err := json.Marshal(newConfig) if err != nil { - return ocispec.Descriptor{}, err + return ocispec.Descriptor{}, ocispec.Descriptor{}, err } configDesc := ocispec.Descriptor{ @@ -539,7 +562,7 @@ func writeContentsForImage(ctx context.Context, snName string, cs content.Store, newMfstJSON, err := json.MarshalIndent(newMfst, "", " ") if err != nil { - return ocispec.Descriptor{}, err + return ocispec.Descriptor{}, ocispec.Descriptor{}, err } newMfstDesc := ocispec.Descriptor{ @@ -558,17 +581,37 @@ func writeContentsForImage(ctx context.Context, snName string, cs content.Store, err = content.WriteBlob(ctx, cs, newMfstDesc.Digest.String(), bytes.NewReader(newMfstJSON), newMfstDesc, content.WithLabels(labels)) if err != nil { - return ocispec.Descriptor{}, err + return ocispec.Descriptor{}, ocispec.Descriptor{}, err } - // config should reference to snapshotter + ccDesc, err := saveContainerConfig(ctx, cs, newMfstDesc.Digest, containerConfig) + if err != nil { + return ocispec.Descriptor{}, ocispec.Descriptor{}, err + } + + // config should reference to snapshotter and container config labelOpt := content.WithLabels(map[string]string{ fmt.Sprintf("containerd.io/gc.ref.snapshot.%s", snName): identity.ChainID(newConfig.RootFS.DiffIDs).String(), + contentLabelGcRefContainerConfig: ccDesc.Digest.String(), }) err = content.WriteBlob(ctx, cs, configDesc.Digest.String(), bytes.NewReader(newConfigJSON), configDesc, labelOpt) + if err != nil { + return ocispec.Descriptor{}, ocispec.Descriptor{}, err + } + + return newMfstDesc, ccDesc, nil +} + +// saveContainerConfig serializes the given ContainerConfig into a json and +// stores it in the content store and returns its descriptor. +func saveContainerConfig(ctx context.Context, content content.Ingester, imgID digest.Digest, containerConfig container.Config) (ocispec.Descriptor, error) { + containerConfigDesc, err := storeJson(ctx, content, + "application/vnd.docker.container.image.v1+json", containerConfig, + map[string]string{contentLabelClassicBuilderImage: imgID.String()}, + ) if err != nil { return ocispec.Descriptor{}, err } - return newMfstDesc, nil + return containerConfigDesc, nil } diff --git a/daemon/containerd/image_commit.go b/daemon/containerd/image_commit.go index 0bb370b3b5..8345fb5182 100644 --- a/daemon/containerd/image_commit.go +++ b/daemon/containerd/image_commit.go @@ -96,7 +96,7 @@ func (i *ImageService) CommitImage(ctx context.Context, cc backend.CommitConfig) layers = append(layers, *diffLayerDesc) } - return i.createImageOCI(ctx, imageConfig, digest.Digest(cc.ParentImageID), layers) + return i.createImageOCI(ctx, imageConfig, digest.Digest(cc.ParentImageID), layers, *cc.ContainerConfig) } // generateCommitImageConfig generates an OCI Image config based on the