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 }