
Changes: https://github.com/containerd/typeurl/compare/7f6e6d160d67...v2.1.0 Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
342 lines
9.3 KiB
Go
342 lines
9.3 KiB
Go
/*
|
|
Copyright The containerd Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package image
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/containerd/containerd/api/types"
|
|
transfertypes "github.com/containerd/containerd/api/types/transfer"
|
|
"github.com/containerd/containerd/content"
|
|
"github.com/containerd/containerd/errdefs"
|
|
"github.com/containerd/containerd/images"
|
|
"github.com/containerd/containerd/images/archive"
|
|
"github.com/containerd/containerd/pkg/streaming"
|
|
"github.com/containerd/containerd/pkg/transfer/plugins"
|
|
"github.com/containerd/containerd/pkg/unpack"
|
|
"github.com/containerd/containerd/platforms"
|
|
"github.com/containerd/containerd/remotes"
|
|
"github.com/containerd/typeurl/v2"
|
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
|
)
|
|
|
|
func init() {
|
|
// TODO: Move this to separate package?
|
|
plugins.Register(&transfertypes.ImageStore{}, &Store{}) // TODO: Rename ImageStoreDestination
|
|
}
|
|
|
|
type Store struct {
|
|
imageName string
|
|
imageLabels map[string]string
|
|
platforms []ocispec.Platform
|
|
allMetadata bool
|
|
labelMap func(ocispec.Descriptor) []string
|
|
manifestLimit int
|
|
|
|
//import image options
|
|
namePrefix string
|
|
checkPrefix bool
|
|
digestRefs bool
|
|
alwaysDigest bool
|
|
|
|
unpacks []UnpackConfiguration
|
|
}
|
|
|
|
// UnpackConfiguration specifies the platform and snapshotter to use for resolving
|
|
// the unpack Platform, if snapshotter is not specified the platform default will
|
|
// be used.
|
|
type UnpackConfiguration struct {
|
|
Platform ocispec.Platform
|
|
Snapshotter string
|
|
}
|
|
|
|
// StoreOpt defines options when configuring an image store source or destination
|
|
type StoreOpt func(*Store)
|
|
|
|
// WithImageLabels are the image labels to apply to a new image
|
|
func WithImageLabels(labels map[string]string) StoreOpt {
|
|
return func(s *Store) {
|
|
s.imageLabels = labels
|
|
}
|
|
}
|
|
|
|
// WithPlatforms specifies which platforms to fetch content for
|
|
func WithPlatforms(p ...ocispec.Platform) StoreOpt {
|
|
return func(s *Store) {
|
|
s.platforms = append(s.platforms, p...)
|
|
}
|
|
}
|
|
|
|
// WithManifestLimit defines the max number of manifests to fetch
|
|
func WithManifestLimit(limit int) StoreOpt {
|
|
return func(s *Store) {
|
|
s.manifestLimit = limit
|
|
}
|
|
}
|
|
|
|
func WithAllMetadata(s *Store) {
|
|
s.allMetadata = true
|
|
}
|
|
|
|
// WithNamePrefix sets the name prefix for imported images, if
|
|
// check is enabled, then only images with the prefix are stored.
|
|
func WithNamePrefix(prefix string, check bool) StoreOpt {
|
|
return func(s *Store) {
|
|
s.namePrefix = prefix
|
|
s.checkPrefix = check
|
|
}
|
|
}
|
|
|
|
// WithDigestRefs sets digest refs for imported images, if
|
|
// always is enabled, then digest refs are added even if a
|
|
// non-digest image name is added for the same image.
|
|
func WithDigestRefs(always bool) StoreOpt {
|
|
return func(s *Store) {
|
|
s.digestRefs = true
|
|
s.alwaysDigest = always
|
|
}
|
|
}
|
|
|
|
// WithUnpack specifies a platform to unpack for and an optional snapshotter to use
|
|
func WithUnpack(p ocispec.Platform, snapshotter string) StoreOpt {
|
|
return func(s *Store) {
|
|
s.unpacks = append(s.unpacks, UnpackConfiguration{
|
|
Platform: p,
|
|
Snapshotter: snapshotter,
|
|
})
|
|
}
|
|
}
|
|
|
|
// NewStore creates a new image store source or Destination
|
|
func NewStore(image string, opts ...StoreOpt) *Store {
|
|
s := &Store{
|
|
imageName: image,
|
|
}
|
|
|
|
for _, opt := range opts {
|
|
opt(s)
|
|
}
|
|
|
|
return s
|
|
}
|
|
|
|
func (is *Store) String() string {
|
|
return fmt.Sprintf("Local Image Store (%s)", is.imageName)
|
|
}
|
|
|
|
func (is *Store) ImageFilter(h images.HandlerFunc, cs content.Store) images.HandlerFunc {
|
|
var p platforms.MatchComparer
|
|
if len(is.platforms) == 0 {
|
|
p = platforms.All
|
|
} else {
|
|
p = platforms.Ordered(is.platforms...)
|
|
}
|
|
h = images.SetChildrenMappedLabels(cs, h, is.labelMap)
|
|
if is.allMetadata {
|
|
// Filter manifests by platforms but allow to handle manifest
|
|
// and configuration for not-target platforms
|
|
h = remotes.FilterManifestByPlatformHandler(h, p)
|
|
} else {
|
|
// Filter children by platforms if specified.
|
|
h = images.FilterPlatforms(h, p)
|
|
}
|
|
|
|
// Sort and limit manifests if a finite number is needed
|
|
if is.manifestLimit > 0 {
|
|
h = images.LimitManifests(h, p, is.manifestLimit)
|
|
}
|
|
return h
|
|
}
|
|
|
|
func (is *Store) Store(ctx context.Context, desc ocispec.Descriptor, store images.Store) (images.Image, error) {
|
|
img := images.Image{
|
|
Name: is.imageName,
|
|
Target: desc,
|
|
Labels: is.imageLabels,
|
|
}
|
|
|
|
// Handle imported image names
|
|
if refType, ok := desc.Annotations["io.containerd.import.ref-type"]; ok {
|
|
var nameT func(string) string
|
|
if is.checkPrefix {
|
|
nameT = archive.FilterRefPrefix(is.namePrefix)
|
|
} else {
|
|
nameT = archive.AddRefPrefix(is.namePrefix)
|
|
}
|
|
name := imageName(desc.Annotations, nameT)
|
|
switch refType {
|
|
case "name":
|
|
if name == "" {
|
|
return images.Image{}, fmt.Errorf("no image name: %w", errdefs.ErrNotFound)
|
|
}
|
|
img.Name = name
|
|
case "digest":
|
|
if !is.digestRefs || (!is.alwaysDigest && name != "") {
|
|
return images.Image{}, fmt.Errorf("no digest refs: %w", errdefs.ErrNotFound)
|
|
}
|
|
img.Name = fmt.Sprintf("%s@%s", is.namePrefix, desc.Digest)
|
|
default:
|
|
return images.Image{}, fmt.Errorf("ref type not supported: %w", errdefs.ErrInvalidArgument)
|
|
}
|
|
delete(desc.Annotations, "io.containerd.import.ref-type")
|
|
} else if img.Name == "" {
|
|
// No valid image combination found
|
|
return images.Image{}, fmt.Errorf("no image name found: %w", errdefs.ErrNotFound)
|
|
}
|
|
|
|
for {
|
|
if created, err := store.Create(ctx, img); err != nil {
|
|
if !errdefs.IsAlreadyExists(err) {
|
|
return images.Image{}, err
|
|
}
|
|
|
|
updated, err := store.Update(ctx, img)
|
|
if err != nil {
|
|
// if image was removed, try create again
|
|
if errdefs.IsNotFound(err) {
|
|
continue
|
|
}
|
|
return images.Image{}, err
|
|
}
|
|
|
|
img = updated
|
|
} else {
|
|
img = created
|
|
}
|
|
|
|
return img, nil
|
|
}
|
|
}
|
|
|
|
func (is *Store) Get(ctx context.Context, store images.Store) (images.Image, error) {
|
|
return store.Get(ctx, is.imageName)
|
|
}
|
|
|
|
func (is *Store) UnpackPlatforms() []unpack.Platform {
|
|
unpacks := make([]unpack.Platform, len(is.unpacks))
|
|
for i, uc := range is.unpacks {
|
|
unpacks[i].SnapshotterKey = uc.Snapshotter
|
|
unpacks[i].Platform = platforms.Only(uc.Platform)
|
|
}
|
|
return unpacks
|
|
}
|
|
|
|
func (is *Store) MarshalAny(context.Context, streaming.StreamCreator) (typeurl.Any, error) {
|
|
//unpack.Platform
|
|
s := &transfertypes.ImageStore{
|
|
Name: is.imageName,
|
|
Labels: is.imageLabels,
|
|
ManifestLimit: uint32(is.manifestLimit),
|
|
AllMetadata: is.allMetadata,
|
|
Platforms: platformsToProto(is.platforms),
|
|
Prefix: is.namePrefix,
|
|
CheckPrefix: is.checkPrefix,
|
|
DigestRefs: is.digestRefs,
|
|
AlwaysDigest: is.alwaysDigest,
|
|
Unpacks: unpackToProto(is.unpacks),
|
|
}
|
|
return typeurl.MarshalAny(s)
|
|
}
|
|
|
|
func (is *Store) UnmarshalAny(ctx context.Context, sm streaming.StreamGetter, a typeurl.Any) error {
|
|
var s transfertypes.ImageStore
|
|
if err := typeurl.UnmarshalTo(a, &s); err != nil {
|
|
return err
|
|
}
|
|
|
|
is.imageName = s.Name
|
|
is.imageLabels = s.Labels
|
|
is.manifestLimit = int(s.ManifestLimit)
|
|
is.allMetadata = s.AllMetadata
|
|
is.platforms = platformFromProto(s.Platforms)
|
|
is.namePrefix = s.Prefix
|
|
is.checkPrefix = s.CheckPrefix
|
|
is.digestRefs = s.DigestRefs
|
|
is.alwaysDigest = s.AlwaysDigest
|
|
is.unpacks = unpackFromProto(s.Unpacks)
|
|
|
|
return nil
|
|
}
|
|
|
|
func platformsToProto(platforms []ocispec.Platform) []*types.Platform {
|
|
ap := make([]*types.Platform, len(platforms))
|
|
for i := range platforms {
|
|
p := types.Platform{
|
|
OS: platforms[i].OS,
|
|
Architecture: platforms[i].Architecture,
|
|
Variant: platforms[i].Variant,
|
|
}
|
|
|
|
ap[i] = &p
|
|
}
|
|
return ap
|
|
}
|
|
|
|
func platformFromProto(platforms []*types.Platform) []ocispec.Platform {
|
|
op := make([]ocispec.Platform, len(platforms))
|
|
for i := range platforms {
|
|
op[i].OS = platforms[i].OS
|
|
op[i].Architecture = platforms[i].Architecture
|
|
op[i].Variant = platforms[i].Variant
|
|
}
|
|
return op
|
|
}
|
|
|
|
func unpackToProto(uc []UnpackConfiguration) []*transfertypes.UnpackConfiguration {
|
|
auc := make([]*transfertypes.UnpackConfiguration, len(uc))
|
|
for i := range uc {
|
|
p := types.Platform{
|
|
OS: uc[i].Platform.OS,
|
|
Architecture: uc[i].Platform.Architecture,
|
|
Variant: uc[i].Platform.Variant,
|
|
}
|
|
auc[i] = &transfertypes.UnpackConfiguration{
|
|
Platform: &p,
|
|
Snapshotter: uc[i].Snapshotter,
|
|
}
|
|
}
|
|
return auc
|
|
}
|
|
|
|
func unpackFromProto(auc []*transfertypes.UnpackConfiguration) []UnpackConfiguration {
|
|
uc := make([]UnpackConfiguration, len(auc))
|
|
for i := range auc {
|
|
uc[i].Snapshotter = auc[i].Snapshotter
|
|
if auc[i].Platform != nil {
|
|
uc[i].Platform.OS = auc[i].Platform.OS
|
|
uc[i].Platform.Architecture = auc[i].Platform.Architecture
|
|
uc[i].Platform.Variant = auc[i].Platform.Variant
|
|
}
|
|
}
|
|
return uc
|
|
}
|
|
|
|
func imageName(annotations map[string]string, ociCleanup func(string) string) string {
|
|
name := annotations[images.AnnotationImageName]
|
|
if name != "" {
|
|
return name
|
|
}
|
|
name = annotations[ocispec.AnnotationRefName]
|
|
if name != "" {
|
|
if ociCleanup != nil {
|
|
name = ociCleanup(name)
|
|
}
|
|
}
|
|
return name
|
|
}
|