github.com/opencontainers/umoci@v0.4.8-0.20240508124516-656e4836fb0d/oci/casext/refname_dir_test.go (about) 1 /* 2 * umoci: Umoci Modifies Open Containers' Images 3 * Copyright (C) 2016-2020 SUSE LLC 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package casext 19 20 import ( 21 "archive/tar" 22 "bytes" 23 "context" 24 crand "crypto/rand" 25 "fmt" 26 "io" 27 "io/ioutil" 28 "math/rand" 29 "os" 30 "path/filepath" 31 "reflect" 32 "runtime" 33 "testing" 34 "time" 35 36 "github.com/opencontainers/go-digest" 37 ispecs "github.com/opencontainers/image-spec/specs-go" 38 ispec "github.com/opencontainers/image-spec/specs-go/v1" 39 "github.com/opencontainers/umoci/oci/cas/dir" 40 "github.com/opencontainers/umoci/oci/casext/mediatype" 41 "github.com/opencontainers/umoci/pkg/testutils" 42 ) 43 44 const ( 45 customMediaType = "org.opensuse.our-new-type" 46 customTargetMediaType = "org.opensuse.our-new-TARGET-type" 47 unknownMediaType = "org.opensuse.fake-manifest" 48 ) 49 50 type fakeManifest struct { 51 Descriptor ispec.Descriptor `json:"descriptor"` 52 Data []byte `json:"data"` 53 } 54 55 func init() { 56 fakeManifestParser := mediatype.CustomJSONParser(fakeManifest{}) 57 58 mediatype.RegisterParser(customMediaType, fakeManifestParser) 59 mediatype.RegisterTarget(customTargetMediaType) 60 mediatype.RegisterParser(customTargetMediaType, fakeManifestParser) 61 } 62 63 type descriptorMap struct { 64 index ispec.Descriptor 65 result ispec.Descriptor 66 } 67 68 func randomTarData(t *testing.T, tw *tar.Writer) error { 69 // Add some files with random contents and random names. 70 for n := 0; n < 32; n++ { 71 size := rand.Intn(512 * 1024) 72 73 if err := tw.WriteHeader(&tar.Header{ 74 Name: testutils.RandomString(16), 75 Mode: 0755, 76 Uid: rand.Intn(1337), 77 Gid: rand.Intn(1337), 78 Size: int64(size), 79 Typeflag: tar.TypeReg, 80 }); err != nil { 81 return fmt.Errorf("randomTarData WriteHeader %d", n) 82 } 83 if _, err := io.CopyN(tw, crand.Reader, int64(size)); err != nil { 84 return fmt.Errorf("randomTarData Write %d", n) 85 } 86 } 87 return nil 88 } 89 90 // fakeSetupEngine injects a variety of "fake" blobs which may not include a 91 // full blob tree to test whether Walk and ResolveReference act sanely in the 92 // face of unknown media types as well as arbitrary nesting of known media 93 // types. The returned 94 func fakeSetupEngine(t *testing.T, engineExt Engine) ([]descriptorMap, error) { 95 ctx := context.Background() 96 mapping := []descriptorMap{} 97 98 // Add some "normal" images that contain some layers and also have some 99 // index indirects. The multiple layers makes sure that we don't break the 100 // multi-level resolution. 101 // XXX: In future we'll have to make tests for platform matching. 102 for k := 0; k < 5; k++ { 103 n := 3 104 name := fmt.Sprintf("normal_img_%d", k) 105 106 layerData := make([]bytes.Buffer, n) 107 108 // Generate layer data. 109 for idx := range layerData { 110 tw := tar.NewWriter(&layerData[idx]) 111 if err := randomTarData(t, tw); err != nil { 112 t.Fatalf("%s: error generating layer%d data: %+v", name, idx, err) 113 } 114 tw.Close() 115 } 116 117 // Insert all of the layers. 118 layerDescriptors := make([]ispec.Descriptor, n) 119 for idx, layer := range layerData { 120 digest, size, err := engineExt.PutBlob(ctx, &layer) 121 if err != nil { 122 t.Fatalf("%s: error putting layer%d blob: %+v", name, idx, err) 123 } 124 layerDescriptors[idx] = ispec.Descriptor{ 125 MediaType: ispec.MediaTypeImageLayer, 126 Digest: digest, 127 Size: size, 128 } 129 } 130 131 // Create our config and insert it. 132 created := time.Now() 133 configDigest, configSize, err := engineExt.PutBlobJSON(ctx, ispec.Image{ 134 Created: &created, 135 Author: "Jane Author <janesmith@example.com>", 136 Architecture: runtime.GOARCH, 137 OS: runtime.GOOS, 138 RootFS: ispec.RootFS{ 139 Type: "unknown", 140 }, 141 }) 142 if err != nil { 143 t.Fatalf("%s: error putting config blob: %+v", name, err) 144 } 145 configDescriptor := ispec.Descriptor{ 146 MediaType: ispec.MediaTypeImageConfig, 147 Digest: configDigest, 148 Size: configSize, 149 } 150 151 // Create our manifest and insert it. 152 manifest := ispec.Manifest{ 153 Versioned: ispecs.Versioned{ 154 SchemaVersion: 2, 155 }, 156 MediaType: ispec.MediaTypeImageManifest, 157 Config: configDescriptor, 158 } 159 manifest.Layers = append(manifest.Layers, layerDescriptors...) 160 161 manifestDigest, manifestSize, err := engineExt.PutBlobJSON(ctx, manifest) 162 if err != nil { 163 t.Fatalf("%s: error putting manifest blob: %+v", name, err) 164 } 165 manifestDescriptor := ispec.Descriptor{ 166 MediaType: ispec.MediaTypeImageManifest, 167 Digest: manifestDigest, 168 Size: manifestSize, 169 Annotations: map[string]string{ 170 "name": name, 171 }, 172 } 173 174 // Add extra index layers. 175 indexDescriptor := manifestDescriptor 176 for i := 0; i < k; i++ { 177 newIndex := ispec.Index{ 178 Versioned: ispecs.Versioned{ 179 SchemaVersion: 2, 180 }, 181 MediaType: ispec.MediaTypeImageIndex, 182 Manifests: []ispec.Descriptor{indexDescriptor}, 183 } 184 indexDigest, indexSize, err := engineExt.PutBlobJSON(ctx, newIndex) 185 if err != nil { 186 t.Fatalf("%s: error putting index-%d blob: %+v", name, i, err) 187 } 188 indexDescriptor = ispec.Descriptor{ 189 MediaType: ispec.MediaTypeImageIndex, 190 Digest: indexDigest, 191 Size: indexSize, 192 } 193 } 194 195 mapping = append(mapping, descriptorMap{ 196 index: indexDescriptor, 197 result: manifestDescriptor, 198 }) 199 } 200 201 // Add some blobs that have custom mediaTypes. This is loosely based on 202 // the previous section. 203 for k := 0; k < 5; k++ { 204 name := fmt.Sprintf("custom_img_%d", k) 205 206 // Create a fake customTargetMediaType (will be masked by a different 207 // target media-type above). 208 notTargetDigest, notTargetSize, err := engineExt.PutBlobJSON(ctx, fakeManifest{ 209 Data: []byte("Hello, world!"), 210 }) 211 if err != nil { 212 t.Fatalf("%s: error putting custom-manifest blob: %+v", name, err) 213 } 214 notTargetDescriptor := ispec.Descriptor{ 215 MediaType: customTargetMediaType, 216 Digest: notTargetDigest, 217 Size: notTargetSize, 218 Annotations: map[string]string{ 219 "name": name, 220 }, 221 } 222 223 // Add extra custom non-target layers. 224 currentDescriptor := notTargetDescriptor 225 for i := 0; i < k; i++ { 226 newDigest, newSize, err := engineExt.PutBlobJSON(ctx, fakeManifest{ 227 Descriptor: currentDescriptor, 228 Data: []byte("intermediate non-target"), 229 }) 230 if err != nil { 231 t.Fatalf("%s: error putting custom-(non)target-%d blob: %+v", name, i, err) 232 } 233 currentDescriptor = ispec.Descriptor{ 234 MediaType: customMediaType, 235 Digest: newDigest, 236 Size: newSize, 237 } 238 } 239 240 // Add the *real* customTargetMediaType. 241 targetDigest, targetSize, err := engineExt.PutBlobJSON(ctx, fakeManifest{ 242 Descriptor: currentDescriptor, 243 Data: []byte("I am the real target!"), 244 }) 245 if err != nil { 246 t.Fatalf("%s: error putting custom-manifest blob: %+v", name, err) 247 } 248 targetDescriptor := ispec.Descriptor{ 249 MediaType: customTargetMediaType, 250 Digest: targetDigest, 251 Size: targetSize, 252 Annotations: map[string]string{ 253 "name": name, 254 }, 255 } 256 257 // Add extra custom non-target layers. 258 currentDescriptor = targetDescriptor 259 for i := 0; i < k; i++ { 260 newDigest, newSize, err := engineExt.PutBlobJSON(ctx, fakeManifest{ 261 Descriptor: currentDescriptor, 262 Data: []byte("intermediate non-target"), 263 }) 264 if err != nil { 265 t.Fatalf("%s: error putting custom-(non)target-%d blob: %+v", name, i, err) 266 } 267 currentDescriptor = ispec.Descriptor{ 268 MediaType: customMediaType, 269 Digest: newDigest, 270 Size: newSize, 271 } 272 } 273 274 // Add extra index layers. 275 indexDescriptor := currentDescriptor 276 for i := 0; i < k; i++ { 277 newIndex := ispec.Index{ 278 Versioned: ispecs.Versioned{ 279 SchemaVersion: 2, 280 }, 281 MediaType: ispec.MediaTypeImageIndex, 282 Manifests: []ispec.Descriptor{indexDescriptor}, 283 } 284 indexDigest, indexSize, err := engineExt.PutBlobJSON(ctx, newIndex) 285 if err != nil { 286 t.Fatalf("%s: error putting index-%d blob: %+v", name, i, err) 287 } 288 indexDescriptor = ispec.Descriptor{ 289 MediaType: ispec.MediaTypeImageIndex, 290 Digest: indexDigest, 291 Size: indexSize, 292 } 293 } 294 295 mapping = append(mapping, descriptorMap{ 296 index: indexDescriptor, 297 result: targetDescriptor, 298 }) 299 } 300 301 // Add some blobs that have unknown mediaTypes. This is loosely based on 302 // the previous section. 303 for k := 0; k < 5; k++ { 304 name := fmt.Sprintf("unknown_img_%d", k) 305 306 manifestDigest, manifestSize, err := engineExt.PutBlobJSON(ctx, fakeManifest{ 307 Descriptor: ispec.Descriptor{ 308 MediaType: "org.opensuse.fake-data", 309 Digest: digest.SHA256.FromString("Hello, world!"), 310 Size: 0, 311 }, 312 Data: []byte("Hello, world!"), 313 }) 314 if err != nil { 315 t.Fatalf("%s: error putting manifest blob: %+v", name, err) 316 } 317 manifestDescriptor := ispec.Descriptor{ 318 MediaType: unknownMediaType, 319 Digest: manifestDigest, 320 Size: manifestSize, 321 Annotations: map[string]string{ 322 "name": name, 323 }, 324 } 325 326 // Add extra index layers. 327 indexDescriptor := manifestDescriptor 328 for i := 0; i < k; i++ { 329 newIndex := ispec.Index{ 330 Versioned: ispecs.Versioned{ 331 SchemaVersion: 2, 332 }, 333 MediaType: ispec.MediaTypeImageIndex, 334 Manifests: []ispec.Descriptor{indexDescriptor}, 335 } 336 indexDigest, indexSize, err := engineExt.PutBlobJSON(ctx, newIndex) 337 if err != nil { 338 t.Fatalf("%s: error putting index-%d blob: %+v", name, i, err) 339 } 340 indexDescriptor = ispec.Descriptor{ 341 MediaType: ispec.MediaTypeImageIndex, 342 Digest: indexDigest, 343 Size: indexSize, 344 } 345 } 346 347 mapping = append(mapping, descriptorMap{ 348 index: indexDescriptor, 349 result: manifestDescriptor, 350 }) 351 } 352 353 return mapping, nil 354 } 355 356 func TestEngineReference(t *testing.T) { 357 ctx := context.Background() 358 359 root, err := ioutil.TempDir("", "umoci-TestEngineReference") 360 if err != nil { 361 t.Fatal(err) 362 } 363 defer os.RemoveAll(root) 364 365 image := filepath.Join(root, "image") 366 if err := dir.Create(image); err != nil { 367 t.Fatalf("unexpected error creating image: %+v", err) 368 } 369 370 engine, err := dir.Open(image) 371 if err != nil { 372 t.Fatalf("unexpected error opening image: %+v", err) 373 } 374 engineExt := NewEngine(engine) 375 defer engine.Close() 376 377 descMap, err := fakeSetupEngine(t, engineExt) 378 if err != nil { 379 t.Fatalf("unexpected error doing fakeSetupEngine: %+v", err) 380 } 381 382 for idx, test := range descMap { 383 name := fmt.Sprintf("new_tag_%d", idx) 384 385 if err := engineExt.UpdateReference(ctx, name, test.index); err != nil { 386 t.Errorf("UpdateReference: unexpected error: %+v", err) 387 } 388 389 gotDescriptorPaths, err := engineExt.ResolveReference(ctx, name) 390 if err != nil { 391 t.Errorf("ResolveReference: unexpected error: %+v", err) 392 } 393 if len(gotDescriptorPaths) != 1 { 394 t.Errorf("ResolveReference: expected %q to get %d descriptors, got %d: %+v", name, 1, len(gotDescriptorPaths), gotDescriptorPaths) 395 continue 396 } 397 gotDescriptor := gotDescriptorPaths[0].Descriptor() 398 399 if !reflect.DeepEqual(test.result, gotDescriptor) { 400 t.Errorf("ResolveReference: got different descriptor to original: expected=%v got=%v", test.result, gotDescriptor) 401 } 402 403 if err := engineExt.DeleteReference(ctx, name); err != nil { 404 t.Errorf("DeleteReference: unexpected error: %+v", err) 405 } 406 407 if gotDescriptorPaths, err := engineExt.ResolveReference(ctx, name); err != nil { 408 t.Errorf("ResolveReference: unexpected error: %+v", err) 409 } else if len(gotDescriptorPaths) > 0 { 410 t.Errorf("ResolveReference: still got reference descriptors after DeleteReference!") 411 } 412 413 // DeleteBlob is idempotent. It shouldn't cause an error. 414 if err := engineExt.DeleteReference(ctx, name); err != nil { 415 t.Errorf("DeleteReference: unexpected error on double-delete: %+v", err) 416 } 417 } 418 } 419 420 func TestEngineReferenceReadonly(t *testing.T) { 421 ctx := context.Background() 422 423 root, err := ioutil.TempDir("", "umoci-TestEngineReferenceReadonly") 424 if err != nil { 425 t.Fatal(err) 426 } 427 defer os.RemoveAll(root) 428 429 image := filepath.Join(root, "image") 430 if err := dir.Create(image); err != nil { 431 t.Fatalf("unexpected error creating image: %+v", err) 432 } 433 434 engine, err := dir.Open(image) 435 if err != nil { 436 t.Fatalf("unexpected error opening image: %+v", err) 437 } 438 engineExt := NewEngine(engine) 439 440 descMap, err := fakeSetupEngine(t, engineExt) 441 if err != nil { 442 t.Fatalf("unexpected error doing fakeSetupEngine: %+v", err) 443 } 444 445 if err := engine.Close(); err != nil { 446 t.Fatalf("unexpected error closing image: %+v", err) 447 } 448 449 for idx, test := range descMap { 450 name := fmt.Sprintf("new_tag_%d", idx) 451 452 engine, err := dir.Open(image) 453 if err != nil { 454 t.Fatalf("unexpected error opening image: %+v", err) 455 } 456 engineExt := NewEngine(engine) 457 458 if err := engineExt.UpdateReference(ctx, name, test.index); err != nil { 459 t.Errorf("UpdateReference: unexpected error: %+v", err) 460 } 461 462 if err := engine.Close(); err != nil { 463 t.Errorf("Close: unexpected error encountered: %+v", err) 464 } 465 466 // make it readonly 467 testutils.MakeReadOnly(t, image) 468 469 newEngine, err := dir.Open(image) 470 if err != nil { 471 t.Errorf("unexpected error opening ro image: %+v", err) 472 } 473 newEngineExt := NewEngine(newEngine) 474 475 gotDescriptorPaths, err := newEngineExt.ResolveReference(ctx, name) 476 if err != nil { 477 t.Errorf("ResolveReference: unexpected error: %+v", err) 478 } 479 if len(gotDescriptorPaths) != 1 { 480 t.Errorf("ResolveReference: expected to get %d descriptors, got %d: %+v", 1, len(gotDescriptorPaths), gotDescriptorPaths) 481 } 482 gotDescriptor := gotDescriptorPaths[0].Descriptor() 483 484 if !reflect.DeepEqual(test.result, gotDescriptor) { 485 t.Errorf("ResolveReference: got different descriptor to original: expected=%v got=%v", test.result, gotDescriptor) 486 } 487 488 // Make sure that writing will FAIL. 489 if err := newEngineExt.UpdateReference(ctx, name+"new", test.index); err == nil { 490 t.Errorf("UpdateReference: expected error on ro image!") 491 } 492 493 if err := newEngine.Close(); err != nil { 494 t.Errorf("Close: unexpected error encountered on ro: %+v", err) 495 } 496 497 // make it readwrite again. 498 testutils.MakeReadWrite(t, image) 499 } 500 }