daemon/c8d: Use Docker imagespec

This makes the c8d code which creates/reads OCI types not lose
Docker-specific features like ONBUILD or Healthcheck.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
This commit is contained in:
Paweł Gronowski 2023-08-24 14:18:51 +02:00
parent 14af90b868
commit 0ffa3dd870
No known key found for this signature in database
GPG key ID: B85EFCFE26DEF92A
6 changed files with 180 additions and 138 deletions

View file

@ -14,14 +14,12 @@ import (
"github.com/containerd/containerd/log"
cplatforms "github.com/containerd/containerd/platforms"
"github.com/docker/distribution/reference"
containertypes "github.com/docker/docker/api/types/container"
imagetype "github.com/docker/docker/api/types/image"
"github.com/docker/docker/daemon/images"
"github.com/docker/docker/errdefs"
"github.com/docker/docker/image"
"github.com/docker/docker/layer"
imagespec "github.com/docker/docker/image/spec/specs-go/v1"
"github.com/docker/docker/pkg/platforms"
"github.com/docker/go-connections/nat"
"github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
@ -44,7 +42,7 @@ func (i *ImageService) GetImage(ctx context.Context, refOrID string, options ima
cs := i.client.ContentStore()
var presentImages []ocispec.Image
var presentImages []imagespec.DockerOCIImage
err = i.walkImageManifests(ctx, desc, func(img *ImageManifest) error {
conf, err := img.Config(ctx)
if err != nil {
@ -57,7 +55,7 @@ func (i *ImageService) GetImage(ctx context.Context, refOrID string, options ima
return errdefs.System(fmt.Errorf("failed to get config descriptor: %w", err))
}
var ociimage ocispec.Image
var ociimage imagespec.DockerOCIImage
if err := readConfig(ctx, cs, conf, &ociimage); err != nil {
if cerrdefs.IsNotFound(err) {
log.G(ctx).WithFields(log.Fields{
@ -84,38 +82,7 @@ func (i *ImageService) GetImage(ctx context.Context, refOrID string, options ima
})
ociimage := presentImages[0]
rootfs := image.NewRootFS()
for _, id := range ociimage.RootFS.DiffIDs {
rootfs.Append(layer.DiffID(id))
}
exposedPorts := make(nat.PortSet, len(ociimage.Config.ExposedPorts))
for k, v := range ociimage.Config.ExposedPorts {
exposedPorts[nat.Port(k)] = v
}
img := image.NewImage(image.ID(desc.Target.Digest))
img.V1Image = image.V1Image{
ID: string(desc.Target.Digest),
OS: ociimage.OS,
Architecture: ociimage.Architecture,
Variant: ociimage.Variant,
Created: ociimage.Created,
Config: &containertypes.Config{
Entrypoint: ociimage.Config.Entrypoint,
Env: ociimage.Config.Env,
Cmd: ociimage.Config.Cmd,
User: ociimage.Config.User,
WorkingDir: ociimage.Config.WorkingDir,
ExposedPorts: exposedPorts,
Volumes: ociimage.Config.Volumes,
Labels: ociimage.Config.Labels,
StopSignal: ociimage.Config.StopSignal,
},
}
img.RootFS = rootfs
img.History = ociimage.History
img := dockerOciImageToDockerImagePartial(image.ID(desc.Target.Digest), ociimage)
if options.Details {
lastUpdated := time.Unix(0, 0)
size, err := i.size(ctx, desc.Target, platform)

View file

@ -390,43 +390,7 @@ func (i *ImageService) CreateImage(ctx context.Context, config []byte, parent st
return nil, err
}
rootFS := ocispec.RootFS{
Type: imgToCreate.RootFS.Type,
DiffIDs: []digest.Digest{},
}
for _, diffId := range imgToCreate.RootFS.DiffIDs {
rootFS.DiffIDs = append(rootFS.DiffIDs, digest.Digest(diffId))
}
exposedPorts := make(map[string]struct{}, len(imgToCreate.Config.ExposedPorts))
for k, v := range imgToCreate.Config.ExposedPorts {
exposedPorts[string(k)] = v
}
// make an ocispec.Image from the docker/image.Image
ociImgToCreate := ocispec.Image{
Created: imgToCreate.Created,
Author: imgToCreate.Author,
Platform: ocispec.Platform{
Architecture: imgToCreate.Architecture,
Variant: imgToCreate.Variant,
OS: imgToCreate.OS,
OSVersion: imgToCreate.OSVersion,
OSFeatures: imgToCreate.OSFeatures,
},
Config: ocispec.ImageConfig{
User: imgToCreate.Config.User,
ExposedPorts: exposedPorts,
Env: imgToCreate.Config.Env,
Entrypoint: imgToCreate.Config.Entrypoint,
Cmd: imgToCreate.Config.Cmd,
Volumes: imgToCreate.Config.Volumes,
WorkingDir: imgToCreate.Config.WorkingDir,
Labels: imgToCreate.Config.Labels,
StopSignal: imgToCreate.Config.StopSignal,
},
RootFS: rootFS,
History: imgToCreate.History,
}
ociImgToCreate := dockerImageToDockerOCIImage(*imgToCreate)
var layers []ocispec.Descriptor
// if the image has a parent, we need to start with the parents layers descriptors

View file

@ -21,6 +21,7 @@ import (
"github.com/containerd/containerd/snapshots"
"github.com/docker/docker/api/types/backend"
"github.com/docker/docker/image"
imagespec "github.com/docker/docker/image/spec/specs-go/v1"
"github.com/docker/docker/pkg/archive"
"github.com/opencontainers/go-digest"
"github.com/opencontainers/image-spec/identity"
@ -40,7 +41,7 @@ func (i *ImageService) CommitImage(ctx context.Context, cc backend.CommitConfig)
cs := i.client.ContentStore()
var parentManifest ocispec.Manifest
var parentImage ocispec.Image
var parentImage imagespec.DockerOCIImage
// ImageManifest can be nil when committing an image with base FROM scratch
if container.ImageManifest != nil {
@ -121,7 +122,7 @@ func (i *ImageService) CommitImage(ctx context.Context, cc backend.CommitConfig)
// generateCommitImageConfig generates an OCI Image config based on the
// container's image and the CommitConfig options.
func generateCommitImageConfig(baseConfig ocispec.Image, diffID digest.Digest, opts backend.CommitConfig) ocispec.Image {
func generateCommitImageConfig(baseConfig imagespec.DockerOCIImage, diffID digest.Digest, opts backend.CommitConfig) imagespec.DockerOCIImage {
if opts.Author == "" {
opts.Author = baseConfig.Author
}
@ -144,31 +145,32 @@ func generateCommitImageConfig(baseConfig ocispec.Image, diffID digest.Digest, o
diffIds = append(diffIds, diffID)
}
return ocispec.Image{
Platform: ocispec.Platform{
Architecture: arch,
OS: os,
return imagespec.DockerOCIImage{
Image: ocispec.Image{
Platform: ocispec.Platform{
Architecture: arch,
OS: os,
},
Created: &createdTime,
Author: opts.Author,
RootFS: ocispec.RootFS{
Type: "layers",
DiffIDs: diffIds,
},
History: append(baseConfig.History, ocispec.History{
Created: &createdTime,
CreatedBy: strings.Join(opts.ContainerConfig.Cmd, " "),
Author: opts.Author,
Comment: opts.Comment,
EmptyLayer: diffID == "",
}),
},
Created: &createdTime,
Author: opts.Author,
Config: containerConfigToOciImageConfig(opts.Config),
RootFS: ocispec.RootFS{
Type: "layers",
DiffIDs: diffIds,
},
History: append(baseConfig.History, ocispec.History{
Created: &createdTime,
CreatedBy: strings.Join(opts.ContainerConfig.Cmd, " "),
Author: opts.Author,
Comment: opts.Comment,
// TODO(laurazard): this check might be incorrect
EmptyLayer: diffID == "",
}),
Config: containerConfigToDockerOCIImageConfig(opts.Config),
}
}
// writeContentsForImage will commit oci image config and manifest into containerd's content store.
func writeContentsForImage(ctx context.Context, snName string, cs content.Store, newConfig ocispec.Image, layers []ocispec.Descriptor) (ocispec.Descriptor, error) {
func writeContentsForImage(ctx context.Context, snName string, cs content.Store, newConfig imagespec.DockerOCIImage, layers []ocispec.Descriptor) (ocispec.Descriptor, error) {
newConfigJSON, err := json.Marshal(newConfig)
if err != nil {
return ocispec.Descriptor{}, err
@ -275,7 +277,7 @@ func createDiff(ctx context.Context, name string, sn snapshots.Snapshotter, cs c
}
// applyDiffLayer will apply diff layer content created by createDiff into the snapshotter.
func applyDiffLayer(ctx context.Context, name string, baseImg ocispec.Image, sn snapshots.Snapshotter, differ diff.Applier, diffDesc ocispec.Descriptor) (retErr error) {
func applyDiffLayer(ctx context.Context, name string, baseImg imagespec.DockerOCIImage, sn snapshots.Snapshotter, differ diff.Applier, diffDesc ocispec.Descriptor) (retErr error) {
var (
key = uniquePart() + "-" + name
parent = identity.ChainID(baseImg.RootFS.DiffIDs).String()

View file

@ -19,6 +19,7 @@ import (
"github.com/docker/docker/builder/dockerfile"
"github.com/docker/docker/errdefs"
"github.com/docker/docker/image"
imagespec "github.com/docker/docker/image/spec/specs-go/v1"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/pools"
"github.com/google/uuid"
@ -87,26 +88,28 @@ func (i *ImageService) ImportImage(ctx context.Context, ref reference.Named, pla
Size: size,
}
ociCfg := containerConfigToOciImageConfig(imageConfig)
dockerCfg := containerConfigToDockerOCIImageConfig(imageConfig)
createdAt := time.Now()
config := ocispec.Image{
Platform: *platform,
Created: &createdAt,
Author: "",
Config: ociCfg,
RootFS: ocispec.RootFS{
Type: "layers",
DiffIDs: []digest.Digest{uncompressedDigest},
},
History: []ocispec.History{
{
Created: &createdAt,
CreatedBy: "",
Author: "",
Comment: msg,
EmptyLayer: false,
config := imagespec.DockerOCIImage{
Image: ocispec.Image{
Platform: *platform,
Created: &createdAt,
Author: "",
RootFS: ocispec.RootFS{
Type: "layers",
DiffIDs: []digest.Digest{uncompressedDigest},
},
History: []ocispec.History{
{
Created: &createdAt,
CreatedBy: "",
Author: "",
Comment: msg,
EmptyLayer: false,
},
},
},
Config: dockerCfg,
}
configDesc, err := storeJson(ctx, cs, ocispec.MediaTypeImageConfig, config, nil)
if err != nil {
@ -383,25 +386,3 @@ func storeJson(ctx context.Context, cs content.Ingester, mt string, obj interfac
}
return desc, nil
}
func containerConfigToOciImageConfig(cfg *container.Config) ocispec.ImageConfig {
ociCfg := ocispec.ImageConfig{
User: cfg.User,
Env: cfg.Env,
Entrypoint: cfg.Entrypoint,
Cmd: cfg.Cmd,
Volumes: cfg.Volumes,
WorkingDir: cfg.WorkingDir,
Labels: cfg.Labels,
StopSignal: cfg.StopSignal,
ArgsEscaped: cfg.ArgsEscaped,
}
if len(cfg.ExposedPorts) > 0 {
ociCfg.ExposedPorts = map[string]struct{}{}
for k, v := range cfg.ExposedPorts {
ociCfg.ExposedPorts[string(k)] = v
}
}
return ociCfg
}

View file

@ -10,8 +10,8 @@ import (
)
// regression test for https://github.com/moby/moby/issues/45904
func TestContainerConfigToOciImageConfig(t *testing.T) {
ociCFG := containerConfigToOciImageConfig(&container.Config{
func TestContainerConfigToDockerImageConfig(t *testing.T) {
ociCFG := containerConfigToDockerOCIImageConfig(&container.Config{
ExposedPorts: nat.PortSet{
"80/tcp": struct{}{},
},

View file

@ -0,0 +1,128 @@
package containerd
import (
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/dockerversion"
"github.com/docker/docker/image"
imagespec "github.com/docker/docker/image/spec/specs-go/v1"
"github.com/docker/docker/layer"
"github.com/docker/go-connections/nat"
"github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
// dockerOciImageToDockerImagePartial creates an image.Image from the imagespec.DockerOCIImage
// It doesn't set:
// - V1Image.ContainerConfig
// - V1Image.Container
// - Details
func dockerOciImageToDockerImagePartial(id image.ID, img imagespec.DockerOCIImage) *image.Image {
v1Image := image.V1Image{
DockerVersion: dockerversion.Version,
Config: dockerOCIImageConfigToContainerConfig(img.Config),
Architecture: img.Platform.Architecture,
Variant: img.Platform.Variant,
OS: img.Platform.OS,
Author: img.Author,
Created: img.Created,
}
rootFS := &image.RootFS{
Type: img.RootFS.Type,
}
for _, diffId := range img.RootFS.DiffIDs {
rootFS.DiffIDs = append(rootFS.DiffIDs, layer.DiffID(diffId))
}
out := image.NewImage(id)
out.V1Image = v1Image
out.RootFS = rootFS
out.History = img.History
out.OSFeatures = img.OSFeatures
out.OSVersion = img.OSVersion
return out
}
func dockerImageToDockerOCIImage(img image.Image) imagespec.DockerOCIImage {
rootfs := ocispec.RootFS{
Type: img.RootFS.Type,
DiffIDs: []digest.Digest{},
}
for _, diffId := range img.RootFS.DiffIDs {
rootfs.DiffIDs = append(rootfs.DiffIDs, digest.Digest(diffId))
}
return imagespec.DockerOCIImage{
Image: ocispec.Image{
Created: img.Created,
Author: img.Author,
Platform: ocispec.Platform{
Architecture: img.Architecture,
Variant: img.Variant,
OS: img.OS,
OSVersion: img.OSVersion,
OSFeatures: img.OSFeatures,
},
RootFS: rootfs,
History: img.History,
},
Config: containerConfigToDockerOCIImageConfig(img.Config),
}
}
func containerConfigToDockerOCIImageConfig(cfg *container.Config) imagespec.DockerOCIImageConfig {
var ociCfg ocispec.ImageConfig
var ext imagespec.DockerOCIImageConfigExt
if cfg != nil {
ociCfg = ocispec.ImageConfig{
User: cfg.User,
Env: cfg.Env,
Entrypoint: cfg.Entrypoint,
Cmd: cfg.Cmd,
Volumes: cfg.Volumes,
WorkingDir: cfg.WorkingDir,
Labels: cfg.Labels,
StopSignal: cfg.StopSignal,
ArgsEscaped: cfg.ArgsEscaped, //nolint:staticcheck // Ignore SA1019. Need to keep it in image.
}
if len(cfg.ExposedPorts) > 0 {
ociCfg.ExposedPorts = map[string]struct{}{}
for k, v := range cfg.ExposedPorts {
ociCfg.ExposedPorts[string(k)] = v
}
}
ext.Healthcheck = cfg.Healthcheck
ext.OnBuild = cfg.OnBuild
ext.Shell = cfg.Shell
}
return imagespec.DockerOCIImageConfig{
ImageConfig: ociCfg,
DockerOCIImageConfigExt: ext,
}
}
func dockerOCIImageConfigToContainerConfig(cfg imagespec.DockerOCIImageConfig) *container.Config {
exposedPorts := make(nat.PortSet, len(cfg.ExposedPorts))
for k, v := range cfg.ExposedPorts {
exposedPorts[nat.Port(k)] = v
}
return &container.Config{
Entrypoint: cfg.Entrypoint,
Env: cfg.Env,
Cmd: cfg.Cmd,
User: cfg.User,
WorkingDir: cfg.WorkingDir,
ExposedPorts: exposedPorts,
Volumes: cfg.Volumes,
Labels: cfg.Labels,
ArgsEscaped: cfg.ArgsEscaped, //nolint:staticcheck // Ignore SA1019. Need to keep it in image.
StopSignal: cfg.StopSignal,
Healthcheck: cfg.Healthcheck,
OnBuild: cfg.OnBuild,
Shell: cfg.Shell,
}
}