github.com/containerd/containerd@v22.0.0-20200918172823-438c87b8e050+incompatible/import.go (about)

     1  /*
     2     Copyright The containerd Authors.
     3  
     4     Licensed under the Apache License, Version 2.0 (the "License");
     5     you may not use this file except in compliance with the License.
     6     You may obtain a copy of the License at
     7  
     8         http://www.apache.org/licenses/LICENSE-2.0
     9  
    10     Unless required by applicable law or agreed to in writing, software
    11     distributed under the License is distributed on an "AS IS" BASIS,
    12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13     See the License for the specific language governing permissions and
    14     limitations under the License.
    15  */
    16  
    17  package containerd
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"io"
    23  
    24  	"github.com/containerd/containerd/content"
    25  	"github.com/containerd/containerd/errdefs"
    26  	"github.com/containerd/containerd/images"
    27  	"github.com/containerd/containerd/images/archive"
    28  	"github.com/containerd/containerd/platforms"
    29  	digest "github.com/opencontainers/go-digest"
    30  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    31  )
    32  
    33  type importOpts struct {
    34  	indexName    string
    35  	imageRefT    func(string) string
    36  	dgstRefT     func(digest.Digest) string
    37  	allPlatforms bool
    38  	compress     bool
    39  }
    40  
    41  // ImportOpt allows the caller to specify import specific options
    42  type ImportOpt func(*importOpts) error
    43  
    44  // WithImageRefTranslator is used to translate the index reference
    45  // to an image reference for the image store.
    46  func WithImageRefTranslator(f func(string) string) ImportOpt {
    47  	return func(c *importOpts) error {
    48  		c.imageRefT = f
    49  		return nil
    50  	}
    51  }
    52  
    53  // WithDigestRef is used to create digest images for each
    54  // manifest in the index.
    55  func WithDigestRef(f func(digest.Digest) string) ImportOpt {
    56  	return func(c *importOpts) error {
    57  		c.dgstRefT = f
    58  		return nil
    59  	}
    60  }
    61  
    62  // WithIndexName creates a tag pointing to the imported index
    63  func WithIndexName(name string) ImportOpt {
    64  	return func(c *importOpts) error {
    65  		c.indexName = name
    66  		return nil
    67  	}
    68  }
    69  
    70  // WithAllPlatforms is used to import content for all platforms.
    71  func WithAllPlatforms(allPlatforms bool) ImportOpt {
    72  	return func(c *importOpts) error {
    73  		c.allPlatforms = allPlatforms
    74  		return nil
    75  	}
    76  }
    77  
    78  // WithImportCompression compresses uncompressed layers on import.
    79  // This is used for import formats which do not include the manifest.
    80  func WithImportCompression() ImportOpt {
    81  	return func(c *importOpts) error {
    82  		c.compress = true
    83  		return nil
    84  	}
    85  }
    86  
    87  // Import imports an image from a Tar stream using reader.
    88  // Caller needs to specify importer. Future version may use oci.v1 as the default.
    89  // Note that unreferenced blobs may be imported to the content store as well.
    90  func (c *Client) Import(ctx context.Context, reader io.Reader, opts ...ImportOpt) ([]images.Image, error) {
    91  	var iopts importOpts
    92  	for _, o := range opts {
    93  		if err := o(&iopts); err != nil {
    94  			return nil, err
    95  		}
    96  	}
    97  
    98  	ctx, done, err := c.WithLease(ctx)
    99  	if err != nil {
   100  		return nil, err
   101  	}
   102  	defer done(ctx)
   103  
   104  	var aio []archive.ImportOpt
   105  	if iopts.compress {
   106  		aio = append(aio, archive.WithImportCompression())
   107  	}
   108  
   109  	index, err := archive.ImportIndex(ctx, c.ContentStore(), reader, aio...)
   110  	if err != nil {
   111  		return nil, err
   112  	}
   113  
   114  	var (
   115  		imgs []images.Image
   116  		cs   = c.ContentStore()
   117  		is   = c.ImageService()
   118  	)
   119  
   120  	if iopts.indexName != "" {
   121  		imgs = append(imgs, images.Image{
   122  			Name:   iopts.indexName,
   123  			Target: index,
   124  		})
   125  	}
   126  	var platformMatcher = platforms.All
   127  	if !iopts.allPlatforms {
   128  		platformMatcher = c.platform
   129  	}
   130  
   131  	var handler images.HandlerFunc = func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
   132  		// Only save images at top level
   133  		if desc.Digest != index.Digest {
   134  			return images.Children(ctx, cs, desc)
   135  		}
   136  
   137  		p, err := content.ReadBlob(ctx, cs, desc)
   138  		if err != nil {
   139  			return nil, err
   140  		}
   141  
   142  		var idx ocispec.Index
   143  		if err := json.Unmarshal(p, &idx); err != nil {
   144  			return nil, err
   145  		}
   146  
   147  		for _, m := range idx.Manifests {
   148  			name := imageName(m.Annotations, iopts.imageRefT)
   149  			if name != "" {
   150  				imgs = append(imgs, images.Image{
   151  					Name:   name,
   152  					Target: m,
   153  				})
   154  			}
   155  			if iopts.dgstRefT != nil {
   156  				ref := iopts.dgstRefT(m.Digest)
   157  				if ref != "" {
   158  					imgs = append(imgs, images.Image{
   159  						Name:   ref,
   160  						Target: m,
   161  					})
   162  				}
   163  			}
   164  		}
   165  
   166  		return idx.Manifests, nil
   167  	}
   168  
   169  	handler = images.FilterPlatforms(handler, platformMatcher)
   170  	handler = images.SetChildrenLabels(cs, handler)
   171  	if err := images.Walk(ctx, handler, index); err != nil {
   172  		return nil, err
   173  	}
   174  
   175  	for i := range imgs {
   176  		img, err := is.Update(ctx, imgs[i], "target")
   177  		if err != nil {
   178  			if !errdefs.IsNotFound(err) {
   179  				return nil, err
   180  			}
   181  
   182  			img, err = is.Create(ctx, imgs[i])
   183  			if err != nil {
   184  				return nil, err
   185  			}
   186  		}
   187  		imgs[i] = img
   188  	}
   189  
   190  	return imgs, nil
   191  }
   192  
   193  func imageName(annotations map[string]string, ociCleanup func(string) string) string {
   194  	name := annotations[images.AnnotationImageName]
   195  	if name != "" {
   196  		return name
   197  	}
   198  	name = annotations[ocispec.AnnotationRefName]
   199  	if name != "" {
   200  		if ociCleanup != nil {
   201  			name = ociCleanup(name)
   202  		}
   203  	}
   204  	return name
   205  }