github.com/lalkh/containerd@v1.4.3/import_test.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 "bytes" 21 "context" 22 "encoding/json" 23 "io" 24 25 "io/ioutil" 26 "math/rand" 27 "reflect" 28 "runtime" 29 "testing" 30 31 "github.com/containerd/containerd/archive/compression" 32 "github.com/containerd/containerd/archive/tartest" 33 "github.com/containerd/containerd/images" 34 "github.com/containerd/containerd/images/archive" 35 digest "github.com/opencontainers/go-digest" 36 specs "github.com/opencontainers/image-spec/specs-go" 37 ocispec "github.com/opencontainers/image-spec/specs-go/v1" 38 ) 39 40 // TestExportAndImport exports testImage as a tar stream, 41 // and import the tar stream as a new image. 42 func TestExportAndImport(t *testing.T) { 43 // TODO: support windows 44 if testing.Short() || runtime.GOOS == "windows" { 45 t.Skip() 46 } 47 ctx, cancel := testContext(t) 48 defer cancel() 49 50 client, err := New(address) 51 if err != nil { 52 t.Fatal(err) 53 } 54 defer client.Close() 55 56 _, err = client.Fetch(ctx, testImage) 57 if err != nil { 58 t.Fatal(err) 59 } 60 61 wb := bytes.NewBuffer(nil) 62 err = client.Export(ctx, wb, archive.WithAllPlatforms(), archive.WithImage(client.ImageService(), testImage)) 63 if err != nil { 64 t.Fatal(err) 65 } 66 67 opts := []ImportOpt{ 68 WithImageRefTranslator(archive.AddRefPrefix("foo/bar")), 69 } 70 imgrecs, err := client.Import(ctx, bytes.NewReader(wb.Bytes()), opts...) 71 if err != nil { 72 t.Fatalf("Import failed: %+v", err) 73 } 74 75 for _, imgrec := range imgrecs { 76 if imgrec.Name == testImage { 77 continue 78 } 79 err = client.ImageService().Delete(ctx, imgrec.Name) 80 if err != nil { 81 t.Fatal(err) 82 } 83 } 84 } 85 86 func TestImport(t *testing.T) { 87 ctx, cancel := testContext(t) 88 defer cancel() 89 90 client, err := New(address) 91 if err != nil { 92 t.Fatal(err) 93 } 94 defer client.Close() 95 96 tc := tartest.TarContext{} 97 98 b1, d1 := createContent(256, 1) 99 empty := []byte("{}") 100 version := []byte("1.0") 101 102 c1, d2 := createConfig() 103 104 m1, d3, expManifest := createManifest(c1, [][]byte{b1}) 105 106 provider := client.ContentStore() 107 108 checkManifest := func(ctx context.Context, t *testing.T, d ocispec.Descriptor, expManifest *ocispec.Manifest) { 109 m, err := images.Manifest(ctx, provider, d, nil) 110 if err != nil { 111 t.Fatalf("unable to read target blob: %+v", err) 112 } 113 114 if m.Config.Digest != d2 { 115 t.Fatalf("unexpected digest hash %s, expected %s", m.Config.Digest, d2) 116 } 117 118 if len(m.Layers) != 1 { 119 t.Fatalf("expected 1 layer, has %d", len(m.Layers)) 120 } 121 122 if m.Layers[0].Digest != d1 { 123 t.Fatalf("unexpected layer hash %s, expected %s", m.Layers[0].Digest, d1) 124 } 125 126 if expManifest != nil { 127 if !reflect.DeepEqual(m.Layers, expManifest.Layers) { 128 t.Fatalf("DeepEqual on Layers failed: %v vs. %v", m.Layers, expManifest.Layers) 129 } 130 if !reflect.DeepEqual(m.Config, expManifest.Config) { 131 t.Fatalf("DeepEqual on Config failed: %v vs. %v", m.Config, expManifest.Config) 132 } 133 } 134 } 135 136 for _, tc := range []struct { 137 Name string 138 Writer tartest.WriterToTar 139 Check func(*testing.T, []images.Image) 140 Opts []ImportOpt 141 }{ 142 { 143 Name: "DockerV2.0", 144 Writer: tartest.TarAll( 145 tc.Dir("bd765cd43e95212f7aa2cab51d0a", 0755), 146 tc.File("bd765cd43e95212f7aa2cab51d0a/json", empty, 0644), 147 tc.File("bd765cd43e95212f7aa2cab51d0a/layer.tar", b1, 0644), 148 tc.File("bd765cd43e95212f7aa2cab51d0a/VERSION", version, 0644), 149 tc.File("repositories", []byte(`{"any":{"1":"bd765cd43e95212f7aa2cab51d0a"}}`), 0644), 150 ), 151 }, 152 { 153 Name: "DockerV2.1", 154 Writer: tartest.TarAll( 155 tc.Dir("bd765cd43e95212f7aa2cab51d0a", 0755), 156 tc.File("bd765cd43e95212f7aa2cab51d0a/json", empty, 0644), 157 tc.File("bd765cd43e95212f7aa2cab51d0a/layer.tar", b1, 0644), 158 tc.File("bd765cd43e95212f7aa2cab51d0a/VERSION", version, 0644), 159 tc.File("e95212f7aa2cab51d0abd765cd43.json", c1, 0644), 160 tc.File("manifest.json", []byte(`[{"Config":"e95212f7aa2cab51d0abd765cd43.json","RepoTags":["test-import:notlatest", "another/repo:tag"],"Layers":["bd765cd43e95212f7aa2cab51d0a/layer.tar"]}]`), 0644), 161 ), 162 Check: func(t *testing.T, imgs []images.Image) { 163 if len(imgs) == 0 { 164 t.Fatalf("no images") 165 } 166 167 names := []string{ 168 "docker.io/library/test-import:notlatest", 169 "docker.io/another/repo:tag", 170 } 171 172 checkImages(t, imgs[0].Target.Digest, imgs, names...) 173 checkManifest(ctx, t, imgs[0].Target, nil) 174 }, 175 }, 176 { 177 Name: "OCI-BadFormat", 178 Writer: tartest.TarAll( 179 tc.File("oci-layout", []byte(`{"imageLayoutVersion":"2.0.0"}`), 0644), 180 ), 181 }, 182 { 183 Name: "OCI", 184 Writer: tartest.TarAll( 185 tc.Dir("blobs", 0755), 186 tc.Dir("blobs/sha256", 0755), 187 tc.File("blobs/sha256/"+d1.Encoded(), b1, 0644), 188 tc.File("blobs/sha256/"+d2.Encoded(), c1, 0644), 189 tc.File("blobs/sha256/"+d3.Encoded(), m1, 0644), 190 tc.File("index.json", createIndex(m1, "latest", "docker.io/lib/img:ok"), 0644), 191 tc.File("oci-layout", []byte(`{"imageLayoutVersion":"1.0.0"}`), 0644), 192 ), 193 Check: func(t *testing.T, imgs []images.Image) { 194 names := []string{ 195 "latest", 196 "docker.io/lib/img:ok", 197 } 198 199 checkImages(t, d3, imgs, names...) 200 checkManifest(ctx, t, imgs[0].Target, expManifest) 201 }, 202 }, 203 { 204 Name: "OCIPrefixName", 205 Writer: tartest.TarAll( 206 tc.Dir("blobs", 0755), 207 tc.Dir("blobs/sha256", 0755), 208 tc.File("blobs/sha256/"+d1.Encoded(), b1, 0644), 209 tc.File("blobs/sha256/"+d2.Encoded(), c1, 0644), 210 tc.File("blobs/sha256/"+d3.Encoded(), m1, 0644), 211 tc.File("index.json", createIndex(m1, "latest", "docker.io/lib/img:ok"), 0644), 212 tc.File("oci-layout", []byte(`{"imageLayoutVersion":"1.0.0"}`), 0644), 213 ), 214 Check: func(t *testing.T, imgs []images.Image) { 215 names := []string{ 216 "localhost:5000/myimage:latest", 217 "docker.io/lib/img:ok", 218 } 219 220 checkImages(t, d3, imgs, names...) 221 checkManifest(ctx, t, imgs[0].Target, expManifest) 222 }, 223 Opts: []ImportOpt{ 224 WithImageRefTranslator(archive.AddRefPrefix("localhost:5000/myimage")), 225 }, 226 }, 227 { 228 Name: "OCIPrefixName2", 229 Writer: tartest.TarAll( 230 tc.Dir("blobs", 0755), 231 tc.Dir("blobs/sha256", 0755), 232 tc.File("blobs/sha256/"+d1.Encoded(), b1, 0644), 233 tc.File("blobs/sha256/"+d2.Encoded(), c1, 0644), 234 tc.File("blobs/sha256/"+d3.Encoded(), m1, 0644), 235 tc.File("index.json", createIndex(m1, "latest", "localhost:5000/myimage:old", "docker.io/lib/img:ok"), 0644), 236 tc.File("oci-layout", []byte(`{"imageLayoutVersion":"1.0.0"}`), 0644), 237 ), 238 Check: func(t *testing.T, imgs []images.Image) { 239 names := []string{ 240 "localhost:5000/myimage:latest", 241 "localhost:5000/myimage:old", 242 } 243 244 checkImages(t, d3, imgs, names...) 245 checkManifest(ctx, t, imgs[0].Target, expManifest) 246 }, 247 Opts: []ImportOpt{ 248 WithImageRefTranslator(archive.FilterRefPrefix("localhost:5000/myimage")), 249 }, 250 }, 251 } { 252 t.Run(tc.Name, func(t *testing.T) { 253 images, err := client.Import(ctx, tartest.TarFromWriterTo(tc.Writer), tc.Opts...) 254 if err != nil { 255 if tc.Check != nil { 256 t.Errorf("unexpected import error: %+v", err) 257 } 258 return 259 } else if tc.Check == nil { 260 t.Fatalf("expected error on import") 261 } 262 263 tc.Check(t, images) 264 }) 265 } 266 } 267 268 func checkImages(t *testing.T, target digest.Digest, actual []images.Image, names ...string) { 269 if len(names) != len(actual) { 270 t.Fatalf("expected %d images, got %d", len(names), len(actual)) 271 } 272 273 for i, n := range names { 274 if actual[i].Target.Digest != target { 275 t.Fatalf("image(%d) unexpected target %s, expected %s", i, actual[i].Target.Digest, target) 276 } 277 if actual[i].Name != n { 278 t.Fatalf("image(%d) unexpected name %q, expected %q", i, actual[i].Name, n) 279 } 280 281 if actual[i].Target.MediaType != ocispec.MediaTypeImageManifest && 282 actual[i].Target.MediaType != images.MediaTypeDockerSchema2Manifest { 283 t.Fatalf("image(%d) unexpected media type: %s", i, actual[i].Target.MediaType) 284 } 285 } 286 } 287 288 func createContent(size int64, seed int64) ([]byte, digest.Digest) { 289 b, err := ioutil.ReadAll(io.LimitReader(rand.New(rand.NewSource(seed)), size)) 290 if err != nil { 291 panic(err) 292 } 293 wb := bytes.NewBuffer(nil) 294 cw, err := compression.CompressStream(wb, compression.Gzip) 295 if err != nil { 296 panic(err) 297 } 298 299 if _, err := cw.Write(b); err != nil { 300 panic(err) 301 } 302 b = wb.Bytes() 303 return b, digest.FromBytes(b) 304 } 305 306 func createConfig() ([]byte, digest.Digest) { 307 image := ocispec.Image{ 308 OS: "any", 309 Architecture: "any", 310 Author: "test", 311 } 312 b, _ := json.Marshal(image) 313 314 return b, digest.FromBytes(b) 315 } 316 317 func createManifest(config []byte, layers [][]byte) ([]byte, digest.Digest, *ocispec.Manifest) { 318 manifest := ocispec.Manifest{ 319 Versioned: specs.Versioned{ 320 SchemaVersion: 2, 321 }, 322 Config: ocispec.Descriptor{ 323 MediaType: ocispec.MediaTypeImageConfig, 324 Digest: digest.FromBytes(config), 325 Size: int64(len(config)), 326 Annotations: map[string]string{ 327 "ocispec": "manifest.config.descriptor", 328 }, 329 }, 330 } 331 for _, l := range layers { 332 manifest.Layers = append(manifest.Layers, ocispec.Descriptor{ 333 MediaType: ocispec.MediaTypeImageLayer, 334 Digest: digest.FromBytes(l), 335 Size: int64(len(l)), 336 Annotations: map[string]string{ 337 "ocispec": "manifest.layers.descriptor", 338 }, 339 }) 340 } 341 342 b, _ := json.Marshal(manifest) 343 344 return b, digest.FromBytes(b), &manifest 345 } 346 347 func createIndex(manifest []byte, tags ...string) []byte { 348 idx := ocispec.Index{ 349 Versioned: specs.Versioned{ 350 SchemaVersion: 2, 351 }, 352 } 353 d := ocispec.Descriptor{ 354 MediaType: ocispec.MediaTypeImageManifest, 355 Digest: digest.FromBytes(manifest), 356 Size: int64(len(manifest)), 357 } 358 359 if len(tags) == 0 { 360 idx.Manifests = append(idx.Manifests, d) 361 } else { 362 for _, t := range tags { 363 dt := d 364 dt.Annotations = map[string]string{ 365 ocispec.AnnotationRefName: t, 366 } 367 idx.Manifests = append(idx.Manifests, dt) 368 } 369 } 370 371 b, _ := json.Marshal(idx) 372 373 return b 374 }