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:
parent
14af90b868
commit
0ffa3dd870
6 changed files with 180 additions and 138 deletions
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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{}{},
|
||||
},
|
||||
|
|
128
daemon/containerd/imagespec.go
Normal file
128
daemon/containerd/imagespec.go
Normal 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,
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue