package images import ( "context" "encoding/json" "time" "github.com/containerd/containerd/content" "github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/platforms" digest "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" ) // Image provides the model for how containerd views container images. type Image struct { Name string Labels map[string]string Target ocispec.Descriptor CreatedAt, UpdatedAt time.Time } type Store interface { Get(ctx context.Context, name string) (Image, error) List(ctx context.Context, filters ...string) ([]Image, error) Create(ctx context.Context, image Image) (Image, error) // Update will replace the data in the store with the provided image. If // one or more fieldpaths are provided, only those fields will be updated. Update(ctx context.Context, image Image, fieldpaths ...string) (Image, error) Delete(ctx context.Context, name string) error } // TODO(stevvooe): Many of these functions make strong platform assumptions, // which are untrue in a lot of cases. More refactoring must be done here to // make this work in all cases. // Config resolves the image configuration descriptor. // // The caller can then use the descriptor to resolve and process the // configuration of the image. func (image *Image) Config(ctx context.Context, provider content.Provider, platform string) (ocispec.Descriptor, error) { return Config(ctx, provider, image.Target, platform) } // RootFS returns the unpacked diffids that make up and images rootfs. // // These are used to verify that a set of layers unpacked to the expected // values. func (image *Image) RootFS(ctx context.Context, provider content.Provider, platform string) ([]digest.Digest, error) { desc, err := image.Config(ctx, provider, platform) if err != nil { return nil, err } return RootFS(ctx, provider, desc) } // Size returns the total size of an image's packed resources. func (image *Image) Size(ctx context.Context, provider content.Provider, platform string) (int64, error) { var size int64 return size, Walk(ctx, Handlers(HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { if desc.Size < 0 { return nil, errors.Errorf("invalid size %v in %v (%v)", desc.Size, desc.Digest, desc.MediaType) } size += desc.Size return nil, nil }), ChildrenHandler(provider, platform)), image.Target) } func Manifest(ctx context.Context, provider content.Provider, image ocispec.Descriptor, platform string) (ocispec.Manifest, error) { var ( matcher platforms.Matcher m *ocispec.Manifest err error ) if platform != "" { matcher, err = platforms.Parse(platform) if err != nil { return ocispec.Manifest{}, err } } if err := Walk(ctx, HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { switch desc.MediaType { case MediaTypeDockerSchema2Manifest, ocispec.MediaTypeImageManifest: p, err := content.ReadBlob(ctx, provider, desc.Digest) if err != nil { return nil, err } var manifest ocispec.Manifest if err := json.Unmarshal(p, &manifest); err != nil { return nil, err } if platform != "" { if desc.Platform != nil && !matcher.Match(*desc.Platform) { return nil, nil } if desc.Platform == nil { p, err := content.ReadBlob(ctx, provider, manifest.Config.Digest) if err != nil { return nil, err } var image ocispec.Image if err := json.Unmarshal(p, &image); err != nil { return nil, err } if !matcher.Match(platforms.Normalize(ocispec.Platform{OS: image.OS, Architecture: image.Architecture})) { return nil, nil } } } m = &manifest return nil, nil case MediaTypeDockerSchema2ManifestList, ocispec.MediaTypeImageIndex: p, err := content.ReadBlob(ctx, provider, desc.Digest) if err != nil { return nil, err } var idx ocispec.Index if err := json.Unmarshal(p, &idx); err != nil { return nil, err } if platform == "" { return idx.Manifests, nil } var descs []ocispec.Descriptor for _, d := range idx.Manifests { if d.Platform == nil || matcher.Match(*d.Platform) { descs = append(descs, d) } } return descs, nil } return nil, errors.New("could not resolve manifest") }), image); err != nil { return ocispec.Manifest{}, err } if m == nil { return ocispec.Manifest{}, errors.Wrap(errdefs.ErrNotFound, "manifest not found") } return *m, nil } // Config resolves the image configuration descriptor using a content provided // to resolve child resources on the image. // // The caller can then use the descriptor to resolve and process the // configuration of the image. func Config(ctx context.Context, provider content.Provider, image ocispec.Descriptor, platform string) (ocispec.Descriptor, error) { manifest, err := Manifest(ctx, provider, image, platform) if err != nil { return ocispec.Descriptor{}, err } return manifest.Config, err } // Platforms returns one or more platforms supported by the image. func Platforms(ctx context.Context, provider content.Provider, image ocispec.Descriptor) ([]ocispec.Platform, error) { var platformSpecs []ocispec.Platform return platformSpecs, Walk(ctx, Handlers(HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { if desc.Platform != nil { platformSpecs = append(platformSpecs, *desc.Platform) return nil, SkipDesc } switch desc.MediaType { case MediaTypeDockerSchema2Config, ocispec.MediaTypeImageConfig: p, err := content.ReadBlob(ctx, provider, desc.Digest) if err != nil { return nil, err } var image ocispec.Image if err := json.Unmarshal(p, &image); err != nil { return nil, err } platformSpecs = append(platformSpecs, platforms.Normalize(ocispec.Platform{OS: image.OS, Architecture: image.Architecture})) } return nil, nil }), ChildrenHandler(provider, "")), image) } // RootFS returns the unpacked diffids that make up and images rootfs. // // These are used to verify that a set of layers unpacked to the expected // values. func RootFS(ctx context.Context, provider content.Provider, configDesc ocispec.Descriptor) ([]digest.Digest, error) { p, err := content.ReadBlob(ctx, provider, configDesc.Digest) if err != nil { return nil, err } var config ocispec.Image if err := json.Unmarshal(p, &config); err != nil { return nil, err } // TODO(stevvooe): Remove this bit when OCI structure uses correct type for // rootfs.DiffIDs. var diffIDs []digest.Digest for _, diffID := range config.RootFS.DiffIDs { diffIDs = append(diffIDs, digest.Digest(diffID)) } return diffIDs, nil }