zotregistry.dev/zot@v1.4.4-0.20240314164342-eec277e14d20/pkg/storage/local/local_test.go (about) 1 package local_test 2 3 import ( 4 "bytes" 5 "context" 6 "crypto/rand" 7 _ "crypto/sha256" 8 "encoding/json" 9 "errors" 10 "fmt" 11 "io" 12 "io/fs" 13 "math/big" 14 "os" 15 "path" 16 "strings" 17 "syscall" 18 "testing" 19 "time" 20 21 godigest "github.com/opencontainers/go-digest" 22 imeta "github.com/opencontainers/image-spec/specs-go" 23 ispec "github.com/opencontainers/image-spec/specs-go/v1" 24 "github.com/rs/zerolog" 25 . "github.com/smartystreets/goconvey/convey" 26 27 zerr "zotregistry.dev/zot/errors" 28 "zotregistry.dev/zot/pkg/api/config" 29 "zotregistry.dev/zot/pkg/common" 30 "zotregistry.dev/zot/pkg/extensions/monitoring" 31 zlog "zotregistry.dev/zot/pkg/log" 32 "zotregistry.dev/zot/pkg/scheduler" 33 "zotregistry.dev/zot/pkg/storage" 34 "zotregistry.dev/zot/pkg/storage/cache" 35 storageConstants "zotregistry.dev/zot/pkg/storage/constants" 36 "zotregistry.dev/zot/pkg/storage/gc" 37 "zotregistry.dev/zot/pkg/storage/local" 38 storageTypes "zotregistry.dev/zot/pkg/storage/types" 39 . "zotregistry.dev/zot/pkg/test/image-utils" 40 "zotregistry.dev/zot/pkg/test/mocks" 41 "zotregistry.dev/zot/pkg/test/signature" 42 ) 43 44 const ( 45 tag = "1.0" 46 repoName = "test" 47 ) 48 49 var trueVal bool = true //nolint: gochecknoglobals 50 51 var DeleteReferrers = config.ImageRetention{ //nolint: gochecknoglobals 52 Delay: storageConstants.DefaultRetentionDelay, 53 Policies: []config.RetentionPolicy{ 54 { 55 Repositories: []string{"**"}, 56 DeleteReferrers: true, 57 DeleteUntagged: &trueVal, 58 }, 59 }, 60 } 61 62 var errCache = errors.New("new cache error") 63 64 func runAndGetScheduler() *scheduler.Scheduler { 65 log := zlog.Logger{} 66 metrics := monitoring.NewMetricsServer(true, log) 67 taskScheduler := scheduler.NewScheduler(config.New(), metrics, log) 68 taskScheduler.RateLimit = 50 * time.Millisecond 69 70 taskScheduler.RunScheduler() 71 72 return taskScheduler 73 } 74 75 func TestStorageFSAPIs(t *testing.T) { 76 dir := t.TempDir() 77 78 log := zlog.Logger{Logger: zerolog.New(os.Stdout)} 79 metrics := monitoring.NewMetricsServer(false, log) 80 cacheDriver, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{ 81 RootDir: dir, 82 Name: "cache", 83 UseRelPaths: true, 84 }, log) 85 86 imgStore := local.NewImageStore(dir, true, true, log, metrics, nil, cacheDriver) 87 88 Convey("Repo layout", t, func(c C) { 89 Convey("Bad image manifest", func() { 90 upload, err := imgStore.NewBlobUpload(repoName) 91 So(err, ShouldBeNil) 92 So(upload, ShouldNotBeEmpty) 93 94 content := []byte("test-data1") 95 buf := bytes.NewBuffer(content) 96 buflen := buf.Len() 97 digest := godigest.FromBytes(content) 98 99 blob, err := imgStore.PutBlobChunk(repoName, upload, 0, int64(buflen), buf) 100 So(err, ShouldBeNil) 101 So(blob, ShouldEqual, buflen) 102 103 err = imgStore.FinishBlobUpload(repoName, upload, buf, digest) 104 So(err, ShouldBeNil) 105 106 annotationsMap := make(map[string]string) 107 annotationsMap[ispec.AnnotationRefName] = tag 108 109 cblob, cdigest := GetRandomImageConfig() 110 _, clen, err := imgStore.FullBlobUpload(repoName, bytes.NewReader(cblob), cdigest) 111 So(err, ShouldBeNil) 112 So(clen, ShouldEqual, len(cblob)) 113 hasBlob, _, err := imgStore.CheckBlob(repoName, cdigest) 114 So(err, ShouldBeNil) 115 So(hasBlob, ShouldEqual, true) 116 117 manifest := ispec.Manifest{ 118 Config: ispec.Descriptor{ 119 MediaType: "application/vnd.oci.image.config.v1+json", 120 Digest: cdigest, 121 Size: int64(len(cblob)), 122 }, 123 Layers: []ispec.Descriptor{ 124 { 125 MediaType: "application/vnd.oci.image.layer.v1.tar", 126 Digest: digest, 127 Size: int64(buflen), 128 }, 129 }, 130 Annotations: annotationsMap, 131 } 132 133 manifest.SchemaVersion = 2 134 manifestBuf, err := json.Marshal(manifest) 135 So(err, ShouldBeNil) 136 digest = godigest.FromBytes(manifestBuf) 137 138 err = os.Chmod(path.Join(imgStore.RootDir(), repoName, "index.json"), 0o000) 139 if err != nil { 140 panic(err) 141 } 142 143 _, _, err = imgStore.PutImageManifest(repoName, "1.0", ispec.MediaTypeImageManifest, manifestBuf) 144 So(err, ShouldNotBeNil) 145 146 err = os.Chmod(path.Join(imgStore.RootDir(), repoName, "index.json"), 0o755) 147 if err != nil { 148 panic(err) 149 } 150 151 _, _, err = imgStore.PutImageManifest(repoName, "1.0", ispec.MediaTypeImageManifest, manifestBuf) 152 So(err, ShouldBeNil) 153 154 manifestPath := path.Join(imgStore.RootDir(), repoName, "blobs", digest.Algorithm().String(), digest.Encoded()) 155 156 err = os.Chmod(manifestPath, 0o000) 157 if err != nil { 158 panic(err) 159 } 160 161 _, _, _, err = imgStore.GetImageManifest(repoName, digest.String()) 162 So(err, ShouldNotBeNil) 163 164 err = os.Remove(manifestPath) 165 if err != nil { 166 panic(err) 167 } 168 169 _, _, _, err = imgStore.GetImageManifest(repoName, digest.String()) 170 So(err, ShouldNotBeNil) 171 172 err = os.Chmod(path.Join(imgStore.RootDir(), repoName), 0o000) 173 if err != nil { 174 panic(err) 175 } 176 177 _, _, err = imgStore.PutImageManifest(repoName, "2.0", ispec.MediaTypeImageManifest, manifestBuf) 178 So(err, ShouldNotBeNil) 179 err = os.Chmod(path.Join(imgStore.RootDir(), repoName), 0o755) 180 if err != nil { 181 panic(err) 182 } 183 184 // invalid DeleteImageManifest 185 indexPath := path.Join(imgStore.RootDir(), repoName, "index.json") 186 err = os.Chmod(indexPath, 0o000) 187 if err != nil { 188 panic(err) 189 } 190 191 err = imgStore.DeleteImageManifest(repoName, digest.String(), false) 192 So(err, ShouldNotBeNil) 193 194 err = os.RemoveAll(path.Join(imgStore.RootDir(), repoName)) 195 if err != nil { 196 panic(err) 197 } 198 }) 199 }) 200 } 201 202 func FuzzNewBlobUpload(f *testing.F) { 203 f.Fuzz(func(t *testing.T, data string) { 204 dir := t.TempDir() 205 defer os.RemoveAll(dir) 206 t.Logf("Input argument is %s", data) 207 log := zlog.Logger{Logger: zerolog.New(os.Stdout)} 208 metrics := monitoring.NewMetricsServer(false, log) 209 cacheDriver, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{ 210 RootDir: dir, 211 Name: "cache", 212 UseRelPaths: true, 213 }, log) 214 215 imgStore := local.NewImageStore(dir, true, true, log, metrics, nil, cacheDriver) 216 217 _, err := imgStore.NewBlobUpload(data) 218 if err != nil { 219 if isKnownErr(err) { 220 return 221 } 222 223 t.Error(err) 224 } 225 }) 226 } 227 228 func FuzzPutBlobChunk(f *testing.F) { 229 f.Fuzz(func(t *testing.T, data string) { 230 dir := t.TempDir() 231 defer os.RemoveAll(dir) 232 t.Logf("Input argument is %s", data) 233 log := zlog.Logger{Logger: zerolog.New(os.Stdout)} 234 235 metrics := monitoring.NewMetricsServer(false, log) 236 cacheDriver, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{ 237 RootDir: dir, 238 Name: "cache", 239 UseRelPaths: true, 240 }, log) 241 imgStore := local.NewImageStore(dir, true, true, log, metrics, nil, cacheDriver) 242 243 repoName := data 244 uuid, err := imgStore.NewBlobUpload(repoName) 245 if err != nil { 246 if isKnownErr(err) { 247 return 248 } 249 250 t.Error(err) 251 } 252 253 buf := bytes.NewBufferString(data) 254 buflen := buf.Len() 255 _, err = imgStore.PutBlobChunk(repoName, uuid, 0, int64(buflen), buf) 256 if err != nil { 257 t.Error(err) 258 } 259 }) 260 } 261 262 func FuzzPutBlobChunkStreamed(f *testing.F) { 263 f.Fuzz(func(t *testing.T, data string) { 264 dir := t.TempDir() 265 defer os.RemoveAll(dir) 266 t.Logf("Input argument is %s", data) 267 log := zlog.Logger{Logger: zerolog.New(os.Stdout)} 268 metrics := monitoring.NewMetricsServer(false, log) 269 cacheDriver, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{ 270 RootDir: dir, 271 Name: "cache", 272 UseRelPaths: true, 273 }, log) 274 imgStore := local.NewImageStore(dir, true, true, log, metrics, nil, cacheDriver) 275 276 repoName := data 277 278 uuid, err := imgStore.NewBlobUpload(repoName) 279 if err != nil { 280 if isKnownErr(err) { 281 return 282 } 283 284 t.Error(err) 285 } 286 287 buf := bytes.NewBufferString(data) 288 _, err = imgStore.PutBlobChunkStreamed(repoName, uuid, buf) 289 if err != nil { 290 t.Error(err) 291 } 292 }) 293 } 294 295 func FuzzGetBlobUpload(f *testing.F) { 296 f.Fuzz(func(t *testing.T, data1 string, data2 string) { 297 dir := t.TempDir() 298 defer os.RemoveAll(dir) 299 log := zlog.Logger{Logger: zerolog.New(os.Stdout)} 300 metrics := monitoring.NewMetricsServer(false, log) 301 cacheDriver, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{ 302 RootDir: dir, 303 Name: "cache", 304 UseRelPaths: true, 305 }, log) 306 imgStore := local.NewImageStore(dir, true, true, log, metrics, nil, 307 cacheDriver) 308 309 _, err := imgStore.GetBlobUpload(data1, data2) 310 if err != nil { 311 if errors.Is(err, zerr.ErrUploadNotFound) || isKnownErr(err) { 312 return 313 } 314 t.Error(err) 315 } 316 }) 317 } 318 319 func FuzzTestPutGetImageManifest(f *testing.F) { 320 f.Fuzz(func(t *testing.T, data []byte) { 321 log := &zlog.Logger{Logger: zerolog.New(os.Stdout)} 322 metrics := monitoring.NewMetricsServer(false, *log) 323 324 dir := t.TempDir() 325 defer os.RemoveAll(dir) 326 327 cacheDriver, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{ 328 RootDir: dir, 329 Name: "cache", 330 UseRelPaths: true, 331 }, *log) 332 imgStore := local.NewImageStore(dir, true, true, *log, metrics, nil, cacheDriver) 333 334 cblob, cdigest := GetRandomImageConfig() 335 336 ldigest, lblob, err := newRandomBlobForFuzz(data) 337 if err != nil { 338 t.Errorf("error occurred while generating random blob, %v", err) 339 } 340 341 _, _, err = imgStore.FullBlobUpload(repoName, bytes.NewReader(cblob), cdigest) 342 if err != nil { 343 t.Error(err) 344 } 345 _, _, err = imgStore.FullBlobUpload(repoName, bytes.NewReader(lblob), ldigest) 346 if err != nil { 347 t.Error(err) 348 } 349 350 manifest, err := NewRandomImgManifest(data, cdigest, ldigest, cblob, lblob) 351 if err != nil { 352 t.Error(err) 353 } 354 manifestBuf, err := json.Marshal(manifest) 355 if err != nil { 356 t.Errorf("Error %v occurred while marshaling manifest", err) 357 } 358 mdigest := godigest.FromBytes(manifestBuf) 359 _, _, err = imgStore.PutImageManifest(repoName, mdigest.String(), ispec.MediaTypeImageManifest, manifestBuf) 360 if err != nil && errors.Is(err, zerr.ErrBadManifest) { 361 t.Errorf("the error that occurred is %v \n", err) 362 } 363 _, _, _, err = imgStore.GetImageManifest(repoName, mdigest.String()) 364 if err != nil { 365 t.Errorf("the error that occurred is %v \n", err) 366 } 367 }) 368 } 369 370 func FuzzTestPutDeleteImageManifest(f *testing.F) { 371 f.Fuzz(func(t *testing.T, data []byte) { 372 log := &zlog.Logger{Logger: zerolog.New(os.Stdout)} 373 metrics := monitoring.NewMetricsServer(false, *log) 374 375 dir := t.TempDir() 376 defer os.RemoveAll(dir) 377 378 cacheDriver, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{ 379 RootDir: dir, 380 Name: "cache", 381 UseRelPaths: true, 382 }, *log) 383 imgStore := local.NewImageStore(dir, true, true, *log, metrics, nil, cacheDriver) 384 385 cblob, cdigest := GetRandomImageConfig() 386 387 ldigest, lblob, err := newRandomBlobForFuzz(data) 388 if err != nil { 389 t.Errorf("error occurred while generating random blob, %v", err) 390 } 391 392 _, _, err = imgStore.FullBlobUpload(repoName, bytes.NewReader(cblob), cdigest) 393 if err != nil { 394 t.Error(err) 395 } 396 397 _, _, err = imgStore.FullBlobUpload(repoName, bytes.NewReader(lblob), ldigest) 398 if err != nil { 399 t.Error(err) 400 } 401 402 manifest, err := NewRandomImgManifest(data, cdigest, ldigest, cblob, lblob) 403 if err != nil { 404 t.Error(err) 405 } 406 407 manifestBuf, err := json.Marshal(manifest) 408 if err != nil { 409 t.Errorf("Error %v occurred while marshaling manifest", err) 410 } 411 mdigest := godigest.FromBytes(manifestBuf) 412 _, _, err = imgStore.PutImageManifest(repoName, mdigest.String(), ispec.MediaTypeImageManifest, manifestBuf) 413 if err != nil && errors.Is(err, zerr.ErrBadManifest) { 414 t.Errorf("the error that occurred is %v \n", err) 415 } 416 417 err = imgStore.DeleteImageManifest(repoName, mdigest.String(), false) 418 if err != nil { 419 if isKnownErr(err) { 420 return 421 } 422 t.Errorf("the error that occurred is %v \n", err) 423 } 424 }) 425 } 426 427 // no integration with PutImageManifest, just throw fuzz data. 428 func FuzzTestDeleteImageManifest(f *testing.F) { 429 f.Fuzz(func(t *testing.T, data []byte) { 430 log := &zlog.Logger{Logger: zerolog.New(os.Stdout)} 431 metrics := monitoring.NewMetricsServer(false, *log) 432 433 dir := t.TempDir() 434 defer os.RemoveAll(dir) 435 436 cacheDriver, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{ 437 RootDir: dir, 438 Name: "cache", 439 UseRelPaths: true, 440 }, *log) 441 imgStore := local.NewImageStore(dir, true, true, *log, metrics, nil, cacheDriver) 442 443 digest, _, err := newRandomBlobForFuzz(data) 444 if err != nil { 445 return 446 } 447 err = imgStore.DeleteImageManifest(string(data), digest.String(), false) 448 if err != nil { 449 if errors.Is(err, zerr.ErrRepoNotFound) || isKnownErr(err) { 450 return 451 } 452 t.Error(err) 453 } 454 }) 455 } 456 457 func FuzzDirExists(f *testing.F) { 458 f.Fuzz(func(t *testing.T, data string) { 459 _ = common.DirExists(data) 460 }) 461 } 462 463 func FuzzInitRepo(f *testing.F) { 464 f.Fuzz(func(t *testing.T, data string) { 465 log := &zlog.Logger{Logger: zerolog.New(os.Stdout)} 466 metrics := monitoring.NewMetricsServer(false, *log) 467 468 dir := t.TempDir() 469 defer os.RemoveAll(dir) 470 471 cacheDriver, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{ 472 RootDir: dir, 473 Name: "cache", 474 UseRelPaths: true, 475 }, *log) 476 imgStore := local.NewImageStore(dir, true, true, *log, metrics, nil, cacheDriver) 477 err := imgStore.InitRepo(data) 478 if err != nil { 479 if isKnownErr(err) { 480 return 481 } 482 t.Error(err) 483 } 484 }) 485 } 486 487 func FuzzInitValidateRepo(f *testing.F) { 488 f.Fuzz(func(t *testing.T, data string) { 489 log := &zlog.Logger{Logger: zerolog.New(os.Stdout)} 490 metrics := monitoring.NewMetricsServer(false, *log) 491 492 dir := t.TempDir() 493 defer os.RemoveAll(dir) 494 495 cacheDriver, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{ 496 RootDir: dir, 497 Name: "cache", 498 UseRelPaths: true, 499 }, *log) 500 imgStore := local.NewImageStore(dir, true, true, *log, metrics, nil, cacheDriver) 501 err := imgStore.InitRepo(data) 502 if err != nil { 503 if isKnownErr(err) { 504 return 505 } 506 t.Error(err) 507 } 508 _, err = imgStore.ValidateRepo(data) 509 if err != nil { 510 if errors.Is(err, zerr.ErrRepoNotFound) || errors.Is(err, zerr.ErrRepoBadVersion) || isKnownErr(err) { 511 return 512 } 513 t.Error(err) 514 } 515 }) 516 } 517 518 func FuzzGetImageTags(f *testing.F) { 519 f.Fuzz(func(t *testing.T, data string) { 520 log := &zlog.Logger{Logger: zerolog.New(os.Stdout)} 521 metrics := monitoring.NewMetricsServer(false, *log) 522 523 dir := t.TempDir() 524 defer os.RemoveAll(dir) 525 526 cacheDriver, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{ 527 RootDir: dir, 528 Name: "cache", 529 UseRelPaths: true, 530 }, *log) 531 imgStore := local.NewImageStore(dir, true, true, *log, metrics, nil, cacheDriver) 532 _, err := imgStore.GetImageTags(data) 533 if err != nil { 534 if errors.Is(err, zerr.ErrRepoNotFound) || isKnownErr(err) { 535 return 536 } 537 t.Error(err) 538 } 539 }) 540 } 541 542 func FuzzBlobUploadPath(f *testing.F) { 543 f.Fuzz(func(t *testing.T, repo, uuid string) { 544 log := &zlog.Logger{Logger: zerolog.New(os.Stdout)} 545 metrics := monitoring.NewMetricsServer(false, *log) 546 547 dir := t.TempDir() 548 defer os.RemoveAll(dir) 549 550 cacheDriver, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{ 551 RootDir: dir, 552 Name: "cache", 553 UseRelPaths: true, 554 }, *log) 555 imgStore := local.NewImageStore(dir, true, true, *log, metrics, nil, cacheDriver) 556 557 _ = imgStore.BlobUploadPath(repo, uuid) 558 }) 559 } 560 561 func FuzzBlobUploadInfo(f *testing.F) { 562 f.Fuzz(func(t *testing.T, data string, uuid string) { 563 log := &zlog.Logger{Logger: zerolog.New(os.Stdout)} 564 metrics := monitoring.NewMetricsServer(false, *log) 565 566 dir := t.TempDir() 567 defer os.RemoveAll(dir) 568 569 cacheDriver, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{ 570 RootDir: dir, 571 Name: "cache", 572 UseRelPaths: true, 573 }, *log) 574 imgStore := local.NewImageStore(dir, true, true, *log, metrics, nil, cacheDriver) 575 repo := data 576 577 _, err := imgStore.BlobUploadInfo(repo, uuid) 578 if err != nil { 579 if isKnownErr(err) { 580 return 581 } 582 t.Error(err) 583 } 584 }) 585 } 586 587 func FuzzTestGetImageManifest(f *testing.F) { 588 f.Fuzz(func(t *testing.T, data string) { 589 dir := t.TempDir() 590 defer os.RemoveAll(dir) 591 592 log := zlog.Logger{Logger: zerolog.New(os.Stdout)} 593 metrics := monitoring.NewMetricsServer(false, log) 594 cacheDriver, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{ 595 RootDir: dir, 596 Name: "cache", 597 UseRelPaths: true, 598 }, log) 599 imgStore := local.NewImageStore(dir, true, true, log, metrics, nil, cacheDriver) 600 601 repoName := data 602 603 digest := godigest.FromBytes([]byte(data)) 604 605 _, _, _, err := imgStore.GetImageManifest(repoName, digest.String()) 606 if err != nil { 607 if isKnownErr(err) { 608 return 609 } 610 t.Error(err) 611 } 612 }) 613 } 614 615 func FuzzFinishBlobUpload(f *testing.F) { 616 f.Fuzz(func(t *testing.T, data string) { 617 dir := t.TempDir() 618 defer os.RemoveAll(dir) 619 620 log := zlog.Logger{Logger: zerolog.New(os.Stdout)} 621 metrics := monitoring.NewMetricsServer(false, log) 622 cacheDriver, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{ 623 RootDir: dir, 624 Name: "cache", 625 UseRelPaths: true, 626 }, log) 627 imgStore := local.NewImageStore(dir, true, true, log, metrics, nil, cacheDriver) 628 629 repoName := data 630 631 upload, err := imgStore.NewBlobUpload(repoName) 632 if err != nil { 633 if isKnownErr(err) { 634 return 635 } 636 t.Error(err) 637 } 638 639 content := []byte(data) 640 buf := bytes.NewBuffer(content) 641 buflen := buf.Len() 642 digest := godigest.FromBytes(content) 643 644 _, err = imgStore.PutBlobChunk(repoName, upload, 0, int64(buflen), buf) 645 if err != nil { 646 if isKnownErr(err) { 647 return 648 } 649 t.Error(err) 650 } 651 652 err = imgStore.FinishBlobUpload(repoName, upload, buf, digest) 653 if err != nil { 654 if isKnownErr(err) { 655 return 656 } 657 t.Error(err) 658 } 659 }) 660 } 661 662 func FuzzFullBlobUpload(f *testing.F) { 663 f.Fuzz(func(t *testing.T, data []byte) { 664 log := &zlog.Logger{Logger: zerolog.New(os.Stdout)} 665 metrics := monitoring.NewMetricsServer(false, *log) 666 repoName := "test" 667 668 dir := t.TempDir() 669 defer os.RemoveAll(dir) 670 671 cacheDriver, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{ 672 RootDir: dir, 673 Name: "cache", 674 UseRelPaths: true, 675 }, *log) 676 imgStore := local.NewImageStore(dir, true, true, *log, metrics, nil, cacheDriver) 677 678 ldigest, lblob, err := newRandomBlobForFuzz(data) 679 if err != nil { 680 t.Errorf("error occurred while generating random blob, %v", err) 681 } 682 683 _, _, err = imgStore.FullBlobUpload(repoName, bytes.NewReader(lblob), ldigest) 684 if err != nil { 685 if isKnownErr(err) { 686 return 687 } 688 t.Error(err) 689 } 690 }) 691 } 692 693 func TestStorageCacheErrors(t *testing.T) { 694 Convey("get error in DedupeBlob() when cache.Put() deduped blob", t, func() { 695 log := zlog.Logger{Logger: zerolog.New(os.Stdout)} 696 metrics := monitoring.NewMetricsServer(false, log) 697 698 dir := t.TempDir() 699 700 originRepo := "dedupe1" 701 dedupedRepo := "dedupe2" 702 703 cblob, cdigest := GetRandomImageConfig() 704 705 getBlobPath := "" 706 707 imgStore := local.NewImageStore(dir, true, true, log, metrics, nil, &mocks.CacheMock{ 708 PutBlobFn: func(digest godigest.Digest, path string) error { 709 if strings.Contains(path, dedupedRepo) { 710 return errCache 711 } 712 713 return nil 714 }, 715 GetBlobFn: func(digest godigest.Digest) (string, error) { 716 return getBlobPath, nil 717 }, 718 }) 719 720 err := imgStore.InitRepo(originRepo) 721 So(err, ShouldBeNil) 722 723 err = imgStore.InitRepo(dedupedRepo) 724 So(err, ShouldBeNil) 725 726 _, _, err = imgStore.FullBlobUpload(originRepo, bytes.NewReader(cblob), cdigest) 727 So(err, ShouldBeNil) 728 729 getBlobPath = imgStore.BlobPath(originRepo, cdigest) 730 _, _, err = imgStore.FullBlobUpload(dedupedRepo, bytes.NewReader(cblob), cdigest) 731 So(err, ShouldNotBeNil) 732 }) 733 } 734 735 func FuzzDedupeBlob(f *testing.F) { 736 f.Fuzz(func(t *testing.T, data string) { 737 log := &zlog.Logger{Logger: zerolog.New(os.Stdout)} 738 metrics := monitoring.NewMetricsServer(false, *log) 739 740 dir := t.TempDir() 741 defer os.RemoveAll(dir) 742 743 cacheDriver, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{ 744 RootDir: dir, 745 Name: "cache", 746 UseRelPaths: true, 747 }, *log) 748 imgStore := local.NewImageStore(dir, true, true, *log, metrics, nil, cacheDriver) 749 750 blobDigest := godigest.FromString(data) 751 752 // replacement for .uploads folder, usually retrieved from BlobUploadPath 753 src := path.Join(imgStore.RootDir(), "src") 754 blob := bytes.NewReader([]byte(data)) 755 756 _, _, err := imgStore.FullBlobUpload("repoName", blob, blobDigest) 757 if err != nil { 758 t.Error(err) 759 } 760 761 dst := imgStore.BlobPath("repoName", blobDigest) 762 763 err = os.MkdirAll(src, 0o755) 764 if err != nil { 765 t.Error(err) 766 } 767 768 err = imgStore.DedupeBlob(src, blobDigest, "repoName", dst) 769 if err != nil { 770 t.Error(err) 771 } 772 }) 773 } 774 775 func FuzzDeleteBlobUpload(f *testing.F) { 776 f.Fuzz(func(t *testing.T, data string) { 777 log := &zlog.Logger{Logger: zerolog.New(os.Stdout)} 778 metrics := monitoring.NewMetricsServer(false, *log) 779 repoName := data 780 781 dir := t.TempDir() 782 defer os.RemoveAll(dir) 783 784 cacheDriver, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{ 785 RootDir: dir, 786 Name: "cache", 787 UseRelPaths: true, 788 }, *log) 789 imgStore := local.NewImageStore(dir, true, true, *log, metrics, nil, cacheDriver) 790 791 uuid, err := imgStore.NewBlobUpload(repoName) 792 if err != nil { 793 if isKnownErr(err) { 794 return 795 } 796 t.Error(err) 797 } 798 799 err = imgStore.DeleteBlobUpload(repoName, uuid) 800 if err != nil { 801 t.Error(err) 802 } 803 }) 804 } 805 806 func FuzzBlobPath(f *testing.F) { 807 f.Fuzz(func(t *testing.T, data string) { 808 log := &zlog.Logger{Logger: zerolog.New(os.Stdout)} 809 metrics := monitoring.NewMetricsServer(false, *log) 810 repoName := data 811 812 dir := t.TempDir() 813 defer os.RemoveAll(dir) 814 815 cacheDriver, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{ 816 RootDir: dir, 817 Name: "cache", 818 UseRelPaths: true, 819 }, *log) 820 imgStore := local.NewImageStore(dir, true, true, *log, metrics, nil, cacheDriver) 821 digest := godigest.FromString(data) 822 823 _ = imgStore.BlobPath(repoName, digest) 824 }) 825 } 826 827 func FuzzCheckBlob(f *testing.F) { 828 f.Fuzz(func(t *testing.T, data string) { 829 log := &zlog.Logger{Logger: zerolog.New(os.Stdout)} 830 metrics := monitoring.NewMetricsServer(false, *log) 831 repoName := data 832 833 dir := t.TempDir() 834 defer os.RemoveAll(dir) 835 836 cacheDriver, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{ 837 RootDir: dir, 838 Name: "cache", 839 UseRelPaths: true, 840 }, *log) 841 imgStore := local.NewImageStore(dir, true, true, *log, metrics, nil, cacheDriver) 842 digest := godigest.FromString(data) 843 844 _, _, err := imgStore.FullBlobUpload(repoName, bytes.NewReader([]byte(data)), digest) 845 if err != nil { 846 if isKnownErr(err) { 847 return 848 } 849 t.Error(err) 850 } 851 _, _, err = imgStore.CheckBlob(repoName, digest) 852 if err != nil { 853 t.Error(err) 854 } 855 }) 856 } 857 858 func FuzzGetBlob(f *testing.F) { 859 f.Fuzz(func(t *testing.T, data string) { 860 log := &zlog.Logger{Logger: zerolog.New(os.Stdout)} 861 metrics := monitoring.NewMetricsServer(false, *log) 862 repoName := data 863 864 dir := t.TempDir() 865 defer os.RemoveAll(dir) 866 867 cacheDriver, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{ 868 RootDir: dir, 869 Name: "cache", 870 UseRelPaths: true, 871 }, *log) 872 imgStore := local.NewImageStore(dir, true, true, *log, metrics, nil, cacheDriver) 873 digest := godigest.FromString(data) 874 875 _, _, err := imgStore.FullBlobUpload(repoName, bytes.NewReader([]byte(data)), digest) 876 if err != nil { 877 if isKnownErr(err) { 878 return 879 } 880 t.Error(err) 881 } 882 883 blobReadCloser, _, err := imgStore.GetBlob(repoName, digest, "application/vnd.oci.image.layer.v1.tar+gzip") 884 if err != nil { 885 if isKnownErr(err) { 886 return 887 } 888 t.Error(err) 889 } 890 if err = blobReadCloser.Close(); err != nil { 891 t.Error(err) 892 } 893 }) 894 } 895 896 func FuzzDeleteBlob(f *testing.F) { 897 f.Fuzz(func(t *testing.T, data string) { 898 log := &zlog.Logger{Logger: zerolog.New(os.Stdout)} 899 metrics := monitoring.NewMetricsServer(false, *log) 900 repoName := data 901 902 dir := t.TempDir() 903 defer os.RemoveAll(dir) 904 905 cacheDriver, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{ 906 RootDir: dir, 907 Name: "cache", 908 UseRelPaths: true, 909 }, *log) 910 imgStore := local.NewImageStore(dir, true, true, *log, metrics, nil, cacheDriver) 911 digest := godigest.FromString(data) 912 913 _, _, err := imgStore.FullBlobUpload(repoName, bytes.NewReader([]byte(data)), digest) 914 if err != nil { 915 if isKnownErr(err) { 916 return 917 } 918 t.Error(err) 919 } 920 921 err = imgStore.DeleteBlob(repoName, digest) 922 if err != nil { 923 if isKnownErr(err) { 924 return 925 } 926 t.Error(err) 927 } 928 }) 929 } 930 931 func FuzzGetIndexContent(f *testing.F) { 932 f.Fuzz(func(t *testing.T, data string) { 933 log := &zlog.Logger{Logger: zerolog.New(os.Stdout)} 934 metrics := monitoring.NewMetricsServer(false, *log) 935 repoName := data 936 937 dir := t.TempDir() 938 defer os.RemoveAll(dir) 939 940 cacheDriver, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{ 941 RootDir: dir, 942 Name: "cache", 943 UseRelPaths: true, 944 }, *log) 945 imgStore := local.NewImageStore(dir, true, true, *log, metrics, nil, cacheDriver) 946 digest := godigest.FromString(data) 947 948 _, _, err := imgStore.FullBlobUpload(repoName, bytes.NewReader([]byte(data)), digest) 949 if err != nil { 950 if isKnownErr(err) { 951 return 952 } 953 t.Error(err) 954 } 955 956 _, err = imgStore.GetIndexContent(repoName) 957 if err != nil { 958 if isKnownErr(err) { 959 return 960 } 961 t.Error(err) 962 } 963 }) 964 } 965 966 func FuzzGetBlobContent(f *testing.F) { 967 f.Fuzz(func(t *testing.T, data string) { 968 log := &zlog.Logger{Logger: zerolog.New(os.Stdout)} 969 metrics := monitoring.NewMetricsServer(false, *log) 970 repoName := data 971 972 dir := t.TempDir() 973 defer os.RemoveAll(dir) 974 975 cacheDriver, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{ 976 RootDir: dir, 977 Name: "cache", 978 UseRelPaths: true, 979 }, *log) 980 imgStore := local.NewImageStore(dir, true, true, *log, metrics, nil, cacheDriver) 981 digest := godigest.FromString(data) 982 983 _, _, err := imgStore.FullBlobUpload(repoName, bytes.NewReader([]byte(data)), digest) 984 if err != nil { 985 if isKnownErr(err) { 986 return 987 } 988 t.Error(err) 989 } 990 991 _, err = imgStore.GetBlobContent(repoName, digest) 992 if err != nil { 993 if isKnownErr(err) { 994 return 995 } 996 t.Error(err) 997 } 998 }) 999 } 1000 1001 func FuzzRunGCRepo(f *testing.F) { 1002 f.Fuzz(func(t *testing.T, data string) { 1003 log := zlog.NewLogger("debug", "") 1004 audit := zlog.NewAuditLogger("debug", "") 1005 1006 metrics := monitoring.NewMetricsServer(false, log) 1007 dir := t.TempDir() 1008 defer os.RemoveAll(dir) 1009 1010 cacheDriver, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{ 1011 RootDir: dir, 1012 Name: "cache", 1013 UseRelPaths: true, 1014 }, log) 1015 imgStore := local.NewImageStore(dir, true, true, log, metrics, nil, cacheDriver) 1016 1017 gc := gc.NewGarbageCollect(imgStore, mocks.MetaDBMock{}, gc.Options{ 1018 Delay: storageConstants.DefaultGCDelay, 1019 ImageRetention: DeleteReferrers, 1020 }, audit, log) 1021 1022 if err := gc.CleanRepo(context.Background(), data); err != nil { 1023 t.Error(err) 1024 } 1025 }) 1026 } 1027 1028 func TestDedupeLinks(t *testing.T) { 1029 testCases := []struct { 1030 dedupe bool 1031 expected bool 1032 }{ 1033 { 1034 dedupe: true, 1035 expected: true, 1036 }, 1037 { 1038 dedupe: false, 1039 expected: false, 1040 }, 1041 } 1042 1043 log := zlog.Logger{Logger: zerolog.New(os.Stdout)} 1044 metrics := monitoring.NewMetricsServer(false, log) 1045 1046 for _, testCase := range testCases { 1047 Convey(fmt.Sprintf("Dedupe %t", testCase.dedupe), t, func(c C) { 1048 dir := t.TempDir() 1049 1050 cacheDriver, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{ 1051 RootDir: dir, 1052 Name: "cache", 1053 UseRelPaths: true, 1054 }, log) 1055 1056 var imgStore storageTypes.ImageStore 1057 1058 if testCase.dedupe { 1059 imgStore = local.NewImageStore(dir, testCase.dedupe, true, log, metrics, nil, cacheDriver) 1060 } else { 1061 imgStore = local.NewImageStore(dir, testCase.dedupe, true, log, metrics, nil, nil) 1062 } 1063 1064 // run on empty image store 1065 // switch dedupe to true from false 1066 taskScheduler := runAndGetScheduler() 1067 defer taskScheduler.Shutdown() 1068 1069 // rebuild with dedupe true 1070 imgStore.RunDedupeBlobs(time.Duration(0), taskScheduler) 1071 // wait until rebuild finishes 1072 time.Sleep(1 * time.Second) 1073 1074 taskScheduler.Shutdown() 1075 1076 // manifest1 1077 upload, err := imgStore.NewBlobUpload("dedupe1") 1078 So(err, ShouldBeNil) 1079 So(upload, ShouldNotBeEmpty) 1080 1081 content := []byte("test-data3") 1082 buf := bytes.NewBuffer(content) 1083 buflen := buf.Len() 1084 digest := godigest.FromBytes(content) 1085 blob, err := imgStore.PutBlobChunkStreamed("dedupe1", upload, buf) 1086 So(err, ShouldBeNil) 1087 So(blob, ShouldEqual, buflen) 1088 blobDigest1 := strings.Split(digest.String(), ":")[1] 1089 So(blobDigest1, ShouldNotBeEmpty) 1090 1091 err = imgStore.FinishBlobUpload("dedupe1", upload, buf, digest) 1092 So(err, ShouldBeNil) 1093 So(blob, ShouldEqual, buflen) 1094 1095 _, _, err = imgStore.CheckBlob("dedupe1", digest) 1096 So(err, ShouldBeNil) 1097 1098 blobrc, _, err := imgStore.GetBlob("dedupe1", digest, "application/vnd.oci.image.layer.v1.tar+gzip") 1099 So(err, ShouldBeNil) 1100 err = blobrc.Close() 1101 So(err, ShouldBeNil) 1102 1103 cblob, cdigest := GetRandomImageConfig() 1104 _, clen, err := imgStore.FullBlobUpload("dedupe1", bytes.NewReader(cblob), cdigest) 1105 So(err, ShouldBeNil) 1106 So(clen, ShouldEqual, len(cblob)) 1107 hasBlob, _, err := imgStore.CheckBlob("dedupe1", cdigest) 1108 So(err, ShouldBeNil) 1109 So(hasBlob, ShouldEqual, true) 1110 1111 manifest := ispec.Manifest{ 1112 Config: ispec.Descriptor{ 1113 MediaType: "application/vnd.oci.image.config.v1+json", 1114 Digest: cdigest, 1115 Size: int64(len(cblob)), 1116 }, 1117 Layers: []ispec.Descriptor{ 1118 { 1119 MediaType: "application/vnd.oci.image.layer.v1.tar", 1120 Digest: digest, 1121 Size: int64(buflen), 1122 }, 1123 }, 1124 } 1125 manifest.SchemaVersion = 2 1126 manifestBuf, err := json.Marshal(manifest) 1127 So(err, ShouldBeNil) 1128 manifestDigest := godigest.FromBytes(manifestBuf) 1129 _, _, err = imgStore.PutImageManifest("dedupe1", manifestDigest.String(), 1130 ispec.MediaTypeImageManifest, manifestBuf) 1131 So(err, ShouldBeNil) 1132 1133 _, _, _, err = imgStore.GetImageManifest("dedupe1", manifestDigest.String()) 1134 So(err, ShouldBeNil) 1135 1136 // manifest2 1137 upload, err = imgStore.NewBlobUpload("dedupe2") 1138 So(err, ShouldBeNil) 1139 So(upload, ShouldNotBeEmpty) 1140 1141 content = []byte("test-data3") 1142 buf = bytes.NewBuffer(content) 1143 buflen = buf.Len() 1144 digest = godigest.FromBytes(content) 1145 blob, err = imgStore.PutBlobChunkStreamed("dedupe2", upload, buf) 1146 So(err, ShouldBeNil) 1147 So(blob, ShouldEqual, buflen) 1148 blobDigest2 := strings.Split(digest.String(), ":")[1] 1149 So(blobDigest2, ShouldNotBeEmpty) 1150 1151 err = imgStore.FinishBlobUpload("dedupe2", upload, buf, digest) 1152 So(err, ShouldBeNil) 1153 So(blob, ShouldEqual, buflen) 1154 1155 _, _, err = imgStore.CheckBlob("dedupe2", digest) 1156 So(err, ShouldBeNil) 1157 1158 blobrc, _, err = imgStore.GetBlob("dedupe2", digest, "application/vnd.oci.image.layer.v1.tar+gzip") 1159 So(err, ShouldBeNil) 1160 err = blobrc.Close() 1161 So(err, ShouldBeNil) 1162 1163 cblob, cdigest = GetRandomImageConfig() 1164 _, clen, err = imgStore.FullBlobUpload("dedupe2", bytes.NewReader(cblob), cdigest) 1165 So(err, ShouldBeNil) 1166 So(clen, ShouldEqual, len(cblob)) 1167 hasBlob, _, err = imgStore.CheckBlob("dedupe2", cdigest) 1168 So(err, ShouldBeNil) 1169 So(hasBlob, ShouldEqual, true) 1170 1171 manifest = ispec.Manifest{ 1172 Config: ispec.Descriptor{ 1173 MediaType: "application/vnd.oci.image.config.v1+json", 1174 Digest: cdigest, 1175 Size: int64(len(cblob)), 1176 }, 1177 Layers: []ispec.Descriptor{ 1178 { 1179 MediaType: "application/vnd.oci.image.layer.v1.tar", 1180 Digest: digest, 1181 Size: int64(buflen), 1182 }, 1183 }, 1184 } 1185 manifest.SchemaVersion = 2 1186 manifestBuf, err = json.Marshal(manifest) 1187 So(err, ShouldBeNil) 1188 digest = godigest.FromBytes(manifestBuf) 1189 _, _, err = imgStore.PutImageManifest("dedupe2", "1.0", ispec.MediaTypeImageManifest, manifestBuf) 1190 So(err, ShouldBeNil) 1191 1192 _, _, _, err = imgStore.GetImageManifest("dedupe2", digest.String()) 1193 So(err, ShouldBeNil) 1194 1195 // verify that dedupe with hard links happened 1196 fi1, err := os.Stat(path.Join(dir, "dedupe1", "blobs", "sha256", blobDigest1)) 1197 So(err, ShouldBeNil) 1198 fi2, err := os.Stat(path.Join(dir, "dedupe2", "blobs", "sha256", blobDigest2)) 1199 So(err, ShouldBeNil) 1200 So(os.SameFile(fi1, fi2), ShouldEqual, testCase.expected) 1201 1202 if !testCase.dedupe { 1203 Convey("delete blobs from storage/cache should work when dedupe is false", func() { 1204 So(blobDigest1, ShouldEqual, blobDigest2) 1205 1206 // to not trigger BlobInUse err, delete manifest first 1207 err = imgStore.DeleteImageManifest("dedupe1", manifestDigest.String(), false) 1208 So(err, ShouldBeNil) 1209 1210 err = imgStore.DeleteImageManifest("dedupe2", "1.0", false) 1211 So(err, ShouldBeNil) 1212 1213 err = imgStore.DeleteBlob("dedupe1", godigest.NewDigestFromEncoded(godigest.SHA256, blobDigest1)) 1214 So(err, ShouldBeNil) 1215 1216 err = imgStore.DeleteBlob("dedupe2", godigest.NewDigestFromEncoded(godigest.SHA256, blobDigest2)) 1217 So(err, ShouldBeNil) 1218 }) 1219 1220 Convey("test RunDedupeForDigest directly, trigger stat error on original blob", func() { 1221 // rebuild with dedupe true 1222 imgStore := local.NewImageStore(dir, true, true, log, metrics, nil, cacheDriver) 1223 1224 duplicateBlobs := []string{ 1225 path.Join(dir, "dedupe1", "blobs", "sha256", blobDigest1), 1226 path.Join(dir, "dedupe1", "blobs", "sha256", blobDigest2), 1227 } 1228 1229 // remove original blob so that it can not be statted 1230 err := os.Remove(path.Join(dir, "dedupe1", "blobs", "sha256", blobDigest1)) 1231 So(err, ShouldBeNil) 1232 1233 err = imgStore.RunDedupeForDigest(context.TODO(), godigest.Digest(blobDigest1), true, duplicateBlobs) 1234 So(err, ShouldNotBeNil) 1235 }) 1236 1237 Convey("Intrerrupt rebuilding and restart, checking idempotency", func() { 1238 for i := 0; i < 10; i++ { 1239 taskScheduler := runAndGetScheduler() 1240 defer taskScheduler.Shutdown() 1241 1242 // rebuild with dedupe true 1243 imgStore := local.NewImageStore(dir, true, true, log, metrics, nil, cacheDriver) 1244 1245 imgStore.RunDedupeBlobs(time.Duration(0), taskScheduler) 1246 sleepValue := i * 5 1247 time.Sleep(time.Duration(sleepValue) * time.Millisecond) 1248 1249 taskScheduler.Shutdown() 1250 } 1251 1252 taskScheduler := runAndGetScheduler() 1253 defer taskScheduler.Shutdown() 1254 1255 // rebuild with dedupe true 1256 imgStore := local.NewImageStore(dir, true, true, log, metrics, nil, cacheDriver) 1257 imgStore.RunDedupeBlobs(time.Duration(0), taskScheduler) 1258 1259 // wait until rebuild finishes 1260 time.Sleep(10 * time.Second) 1261 1262 taskScheduler.Shutdown() 1263 1264 fi1, err := os.Stat(path.Join(dir, "dedupe1", "blobs", "sha256", blobDigest1)) 1265 So(err, ShouldBeNil) 1266 fi2, err := os.Stat(path.Join(dir, "dedupe2", "blobs", "sha256", blobDigest2)) 1267 So(err, ShouldBeNil) 1268 So(os.SameFile(fi1, fi2), ShouldEqual, true) 1269 }) 1270 1271 Convey("rebuild dedupe index error cache nil", func() { 1272 // switch dedupe to true from false 1273 taskScheduler := runAndGetScheduler() 1274 defer taskScheduler.Shutdown() 1275 1276 imgStore := local.NewImageStore(dir, true, true, log, metrics, nil, nil) 1277 1278 // rebuild with dedupe true 1279 imgStore.RunDedupeBlobs(time.Duration(0), taskScheduler) 1280 // wait until rebuild finishes 1281 1282 time.Sleep(3 * time.Second) 1283 1284 taskScheduler.Shutdown() 1285 1286 fi1, err := os.Stat(path.Join(dir, "dedupe1", "blobs", "sha256", blobDigest1)) 1287 So(err, ShouldBeNil) 1288 fi2, err := os.Stat(path.Join(dir, "dedupe2", "blobs", "sha256", blobDigest2)) 1289 So(err, ShouldBeNil) 1290 1291 So(os.SameFile(fi1, fi2), ShouldEqual, false) 1292 }) 1293 1294 Convey("rebuild dedupe index cache error on original blob", func() { 1295 // switch dedupe to true from false 1296 taskScheduler := runAndGetScheduler() 1297 defer taskScheduler.Shutdown() 1298 1299 imgStore := local.NewImageStore(dir, true, true, log, metrics, nil, &mocks.CacheMock{ 1300 HasBlobFn: func(digest godigest.Digest, path string) bool { 1301 return false 1302 }, 1303 PutBlobFn: func(digest godigest.Digest, path string) error { 1304 return errCache 1305 }, 1306 }) 1307 // rebuild with dedupe true, should have samefile blobs 1308 imgStore.RunDedupeBlobs(time.Duration(0), taskScheduler) 1309 // wait until rebuild finishes 1310 1311 time.Sleep(10 * time.Second) 1312 1313 taskScheduler.Shutdown() 1314 1315 fi1, err := os.Stat(path.Join(dir, "dedupe1", "blobs", "sha256", blobDigest1)) 1316 So(err, ShouldBeNil) 1317 fi2, err := os.Stat(path.Join(dir, "dedupe2", "blobs", "sha256", blobDigest2)) 1318 So(err, ShouldBeNil) 1319 1320 So(os.SameFile(fi1, fi2), ShouldEqual, false) 1321 }) 1322 1323 Convey("rebuild dedupe index cache error on duplicate blob", func() { 1324 // switch dedupe to true from false 1325 taskScheduler := runAndGetScheduler() 1326 defer taskScheduler.Shutdown() 1327 1328 imgStore := local.NewImageStore(dir, true, true, log, metrics, nil, &mocks.CacheMock{ 1329 HasBlobFn: func(digest godigest.Digest, path string) bool { 1330 return false 1331 }, 1332 PutBlobFn: func(digest godigest.Digest, path string) error { 1333 if strings.Contains(path, "dedupe2") { 1334 return errCache 1335 } 1336 1337 return nil 1338 }, 1339 }) 1340 // rebuild with dedupe true, should have samefile blobs 1341 imgStore.RunDedupeBlobs(time.Duration(0), taskScheduler) 1342 // wait until rebuild finishes 1343 1344 time.Sleep(15 * time.Second) 1345 1346 taskScheduler.Shutdown() 1347 1348 fi1, err := os.Stat(path.Join(dir, "dedupe1", "blobs", "sha256", blobDigest1)) 1349 So(err, ShouldBeNil) 1350 fi2, err := os.Stat(path.Join(dir, "dedupe2", "blobs", "sha256", blobDigest2)) 1351 So(err, ShouldBeNil) 1352 1353 So(os.SameFile(fi1, fi2), ShouldEqual, true) 1354 }) 1355 } 1356 1357 Convey("delete blobs from storage/cache should work when dedupe is true", func() { 1358 So(blobDigest1, ShouldEqual, blobDigest2) 1359 1360 // to not trigger BlobInUse err, delete manifest first 1361 err = imgStore.DeleteImageManifest("dedupe1", manifestDigest.String(), false) 1362 So(err, ShouldBeNil) 1363 1364 err = imgStore.DeleteImageManifest("dedupe2", "1.0", false) 1365 So(err, ShouldBeNil) 1366 1367 err = imgStore.DeleteBlob("dedupe1", godigest.NewDigestFromEncoded(godigest.SHA256, blobDigest1)) 1368 So(err, ShouldBeNil) 1369 1370 err = imgStore.DeleteBlob("dedupe2", godigest.NewDigestFromEncoded(godigest.SHA256, blobDigest2)) 1371 So(err, ShouldBeNil) 1372 }) 1373 1374 Convey("storage and cache inconsistency", func() { 1375 // delete blobs 1376 err = os.Remove(path.Join(dir, "dedupe1", "blobs", "sha256", blobDigest1)) 1377 So(err, ShouldBeNil) 1378 1379 err := os.Remove(path.Join(dir, "dedupe2", "blobs", "sha256", blobDigest2)) 1380 So(err, ShouldBeNil) 1381 1382 // now cache is inconsistent with storage (blobs present in cache but not in storage) 1383 upload, err = imgStore.NewBlobUpload("dedupe3") 1384 So(err, ShouldBeNil) 1385 So(upload, ShouldNotBeEmpty) 1386 1387 content = []byte("test-data3") 1388 buf = bytes.NewBuffer(content) 1389 buflen = buf.Len() 1390 digest = godigest.FromBytes(content) 1391 blob, err = imgStore.PutBlobChunkStreamed("dedupe3", upload, buf) 1392 So(err, ShouldBeNil) 1393 So(blob, ShouldEqual, buflen) 1394 blobDigest2 := strings.Split(digest.String(), ":")[1] 1395 So(blobDigest2, ShouldNotBeEmpty) 1396 1397 err = imgStore.FinishBlobUpload("dedupe3", upload, buf, digest) 1398 So(err, ShouldBeNil) 1399 So(blob, ShouldEqual, buflen) 1400 }) 1401 }) 1402 } 1403 } 1404 1405 func TestDedupe(t *testing.T) { 1406 Convey("Dedupe", t, func(c C) { 1407 Convey("Nil ImageStore", func() { 1408 var is storageTypes.ImageStore 1409 So(func() { _ = is.DedupeBlob("", "", "", "") }, ShouldPanic) 1410 }) 1411 1412 Convey("Valid ImageStore", func() { 1413 dir := t.TempDir() 1414 1415 log := zlog.Logger{Logger: zerolog.New(os.Stdout)} 1416 metrics := monitoring.NewMetricsServer(false, log) 1417 cacheDriver, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{ 1418 RootDir: dir, 1419 Name: "cache", 1420 UseRelPaths: true, 1421 }, log) 1422 1423 il := local.NewImageStore(dir, true, true, log, metrics, nil, cacheDriver) 1424 1425 So(il.DedupeBlob("", "", "", ""), ShouldNotBeNil) 1426 }) 1427 }) 1428 } 1429 1430 //nolint:gocyclo 1431 func TestNegativeCases(t *testing.T) { 1432 Convey("Invalid root dir", t, func(c C) { 1433 dir := t.TempDir() 1434 1435 log := zlog.Logger{Logger: zerolog.New(os.Stdout)} 1436 metrics := monitoring.NewMetricsServer(false, log) 1437 cacheDriver, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{ 1438 RootDir: dir, 1439 Name: "cache", 1440 UseRelPaths: true, 1441 }, log) 1442 1443 So(local.NewImageStore(dir, true, 1444 true, log, metrics, nil, cacheDriver), ShouldNotBeNil) 1445 if os.Geteuid() != 0 { 1446 cacheDriver, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{ 1447 RootDir: "/deadBEEF", 1448 Name: "cache", 1449 UseRelPaths: true, 1450 }, log) 1451 So(local.NewImageStore("/deadBEEF", true, true, log, metrics, nil, cacheDriver), ShouldBeNil) 1452 } 1453 }) 1454 1455 Convey("Invalid init repo", t, func(c C) { 1456 dir := t.TempDir() 1457 1458 log := zlog.Logger{Logger: zerolog.New(os.Stdout)} 1459 metrics := monitoring.NewMetricsServer(false, log) 1460 cacheDriver, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{ 1461 RootDir: dir, 1462 Name: "cache", 1463 UseRelPaths: true, 1464 }, log) 1465 1466 imgStore := local.NewImageStore(dir, true, true, log, metrics, nil, cacheDriver) 1467 1468 err := os.Chmod(dir, 0o000) // remove all perms 1469 if err != nil { 1470 panic(err) 1471 } 1472 1473 if os.Geteuid() != 0 { 1474 err = imgStore.InitRepo("test") 1475 So(err, ShouldNotBeNil) 1476 } 1477 1478 err = os.Chmod(dir, 0o755) 1479 if err != nil { 1480 panic(err) 1481 } 1482 1483 // Init repo should fail if repo is a file. 1484 err = os.WriteFile(path.Join(dir, "file-test"), []byte("this is test file"), 0o755) //nolint:gosec 1485 So(err, ShouldBeNil) 1486 err = imgStore.InitRepo("file-test") 1487 So(err, ShouldNotBeNil) 1488 1489 err = os.Mkdir(path.Join(dir, "test-dir"), 0o755) 1490 So(err, ShouldBeNil) 1491 1492 err = imgStore.InitRepo("test-dir") 1493 So(err, ShouldBeNil) 1494 1495 // Init repo should fail if repo is invalid UTF-8 1496 err = imgStore.InitRepo("hi \255") 1497 So(err, ShouldNotBeNil) 1498 1499 // Init repo should fail if repo name does not match spec 1500 err = imgStore.InitRepo("_trivy") 1501 So(err, ShouldNotBeNil) 1502 So(errors.Is(err, zerr.ErrInvalidRepositoryName), ShouldBeTrue) 1503 }) 1504 1505 Convey("Invalid validate repo", t, func(c C) { 1506 dir := t.TempDir() 1507 1508 log := zlog.Logger{Logger: zerolog.New(os.Stdout)} 1509 metrics := monitoring.NewMetricsServer(false, log) 1510 cacheDriver, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{ 1511 RootDir: dir, 1512 Name: "cache", 1513 UseRelPaths: true, 1514 }, log) 1515 1516 imgStore := local.NewImageStore(dir, true, true, log, metrics, nil, cacheDriver) 1517 1518 So(imgStore, ShouldNotBeNil) 1519 So(imgStore.InitRepo("test"), ShouldBeNil) 1520 1521 err := os.MkdirAll(path.Join(dir, "invalid-test"), 0o755) 1522 So(err, ShouldBeNil) 1523 1524 err = os.Chmod(path.Join(dir, "invalid-test"), 0o000) // remove all perms 1525 if err != nil { 1526 panic(err) 1527 } 1528 _, err = imgStore.ValidateRepo("invalid-test") 1529 So(err, ShouldNotBeNil) 1530 So(err, ShouldEqual, zerr.ErrRepoNotFound) 1531 1532 err = os.Chmod(path.Join(dir, "invalid-test"), 0o755) // remove all perms 1533 if err != nil { 1534 panic(err) 1535 } 1536 1537 err = os.WriteFile(path.Join(dir, "invalid-test", "blobs"), []byte{}, 0o755) //nolint: gosec 1538 if err != nil { 1539 panic(err) 1540 } 1541 1542 err = os.WriteFile(path.Join(dir, "invalid-test", "index.json"), []byte{}, 0o755) //nolint: gosec 1543 if err != nil { 1544 panic(err) 1545 } 1546 1547 err = os.WriteFile(path.Join(dir, "invalid-test", ispec.ImageLayoutFile), []byte{}, 0o755) //nolint: gosec 1548 if err != nil { 1549 panic(err) 1550 } 1551 1552 isValid, err := imgStore.ValidateRepo("invalid-test") 1553 So(err, ShouldBeNil) 1554 So(isValid, ShouldEqual, false) 1555 1556 err = os.Remove(path.Join(dir, "invalid-test", "blobs")) 1557 if err != nil { 1558 panic(err) 1559 } 1560 err = os.Mkdir(path.Join(dir, "invalid-test", "blobs"), 0o755) 1561 if err != nil { 1562 panic(err) 1563 } 1564 isValid, err = imgStore.ValidateRepo("invalid-test") 1565 So(err, ShouldNotBeNil) 1566 So(isValid, ShouldEqual, false) 1567 1568 err = os.WriteFile(path.Join(dir, "invalid-test", ispec.ImageLayoutFile), []byte("{}"), 0o755) //nolint: gosec 1569 if err != nil { 1570 panic(err) 1571 } 1572 1573 isValid, err = imgStore.ValidateRepo("invalid-test") 1574 So(err, ShouldNotBeNil) 1575 So(err, ShouldEqual, zerr.ErrRepoBadVersion) 1576 So(isValid, ShouldEqual, false) 1577 1578 files, err := os.ReadDir(path.Join(dir, "test")) 1579 if err != nil { 1580 panic(err) 1581 } 1582 1583 for _, f := range files { 1584 os.Remove(path.Join(dir, "test", f.Name())) 1585 } 1586 1587 _, err = imgStore.ValidateRepo("test") 1588 So(err, ShouldNotBeNil) 1589 1590 err = os.RemoveAll(path.Join(dir, "test")) 1591 if err != nil { 1592 panic(err) 1593 } 1594 1595 _, err = imgStore.ValidateRepo("test") 1596 So(err, ShouldNotBeNil) 1597 1598 err = os.Chmod(dir, 0o000) // remove all perms 1599 if err != nil { 1600 panic(err) 1601 } 1602 1603 _, err = imgStore.GetRepositories() 1604 So(err, ShouldNotBeNil) 1605 1606 err = os.Chmod(dir, 0o755) // add perms 1607 if err != nil { 1608 panic(err) 1609 } 1610 1611 err = os.RemoveAll(dir) 1612 if err != nil { 1613 panic(err) 1614 } 1615 }) 1616 1617 Convey("Invalid get image tags", t, func(c C) { 1618 dir := t.TempDir() 1619 1620 log := zlog.Logger{Logger: zerolog.New(os.Stdout)} 1621 metrics := monitoring.NewMetricsServer(false, log) 1622 cacheDriver, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{ 1623 RootDir: dir, 1624 Name: "cache", 1625 UseRelPaths: true, 1626 }, log) 1627 1628 imgStore := local.NewImageStore(dir, true, true, log, metrics, nil, cacheDriver) 1629 1630 So(imgStore, ShouldNotBeNil) 1631 So(imgStore.InitRepo("test"), ShouldBeNil) 1632 So(os.Remove(path.Join(dir, "test", "index.json")), ShouldBeNil) 1633 _, err := imgStore.GetImageTags("test") 1634 So(err, ShouldNotBeNil) 1635 So(os.RemoveAll(path.Join(dir, "test")), ShouldBeNil) 1636 So(imgStore.InitRepo("test"), ShouldBeNil) 1637 So(os.WriteFile(path.Join(dir, "test", "index.json"), []byte{}, 0o600), ShouldBeNil) 1638 _, err = imgStore.GetImageTags("test") 1639 So(err, ShouldNotBeNil) 1640 }) 1641 1642 Convey("Invalid get image manifest", t, func(c C) { 1643 dir := t.TempDir() 1644 1645 log := zlog.Logger{Logger: zerolog.New(os.Stdout)} 1646 metrics := monitoring.NewMetricsServer(false, log) 1647 cacheDriver, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{ 1648 RootDir: dir, 1649 Name: "cache", 1650 UseRelPaths: true, 1651 }, log) 1652 1653 imgStore := local.NewImageStore(dir, true, true, log, metrics, nil, cacheDriver) 1654 1655 So(imgStore, ShouldNotBeNil) 1656 So(imgStore.InitRepo("test"), ShouldBeNil) 1657 1658 err := os.Chmod(path.Join(dir, "test", "index.json"), 0o000) 1659 if err != nil { 1660 panic(err) 1661 } 1662 1663 _, _, _, err = imgStore.GetImageManifest("test", "") 1664 So(err, ShouldNotBeNil) 1665 1666 err = os.Remove(path.Join(dir, "test", "index.json")) 1667 if err != nil { 1668 panic(err) 1669 } 1670 1671 _, _, _, err = imgStore.GetImageManifest("test", "") 1672 So(err, ShouldNotBeNil) 1673 1674 err = os.RemoveAll(path.Join(dir, "test")) 1675 if err != nil { 1676 panic(err) 1677 } 1678 1679 So(imgStore.InitRepo("test"), ShouldBeNil) 1680 1681 err = os.WriteFile(path.Join(dir, "test", "index.json"), []byte{}, 0o600) 1682 if err != nil { 1683 panic(err) 1684 } 1685 _, _, _, err = imgStore.GetImageManifest("test", "") 1686 So(err, ShouldNotBeNil) 1687 }) 1688 1689 Convey("Invalid new blob upload", t, func(c C) { 1690 dir := t.TempDir() 1691 1692 log := zlog.Logger{Logger: zerolog.New(os.Stdout)} 1693 metrics := monitoring.NewMetricsServer(false, log) 1694 cacheDriver, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{ 1695 RootDir: dir, 1696 Name: "cache", 1697 UseRelPaths: true, 1698 }, log) 1699 1700 imgStore := local.NewImageStore(dir, true, true, log, metrics, nil, cacheDriver) 1701 1702 So(imgStore, ShouldNotBeNil) 1703 So(imgStore.InitRepo("test"), ShouldBeNil) 1704 1705 err := os.Chmod(path.Join(dir, "test", ".uploads"), 0o000) 1706 if err != nil { 1707 panic(err) 1708 } 1709 _, err = imgStore.NewBlobUpload("test") 1710 So(err, ShouldNotBeNil) 1711 1712 err = os.Chmod(path.Join(dir, "test"), 0o000) 1713 if err != nil { 1714 panic(err) 1715 } 1716 1717 _, err = imgStore.NewBlobUpload("test") 1718 So(err, ShouldNotBeNil) 1719 1720 err = os.Chmod(path.Join(dir, "test"), 0o755) 1721 if err != nil { 1722 panic(err) 1723 } 1724 1725 So(imgStore.InitRepo("test"), ShouldBeNil) 1726 1727 _, err = imgStore.NewBlobUpload("test") 1728 So(err, ShouldNotBeNil) 1729 1730 err = os.Chmod(path.Join(dir, "test", ".uploads"), 0o755) 1731 if err != nil { 1732 panic(err) 1733 } 1734 1735 upload, err := imgStore.NewBlobUpload("test") 1736 So(err, ShouldBeNil) 1737 1738 err = os.Chmod(path.Join(dir, "test", ".uploads"), 0o000) 1739 if err != nil { 1740 panic(err) 1741 } 1742 t.Cleanup(func() { 1743 err = os.Chmod(path.Join(dir, "test", ".uploads"), 0o700) 1744 if err != nil { 1745 panic(err) 1746 } 1747 }) 1748 1749 content := []byte("test-data3") 1750 buf := bytes.NewBuffer(content) 1751 l := buf.Len() 1752 _, err = imgStore.PutBlobChunkStreamed("test", upload, buf) 1753 So(err, ShouldNotBeNil) 1754 1755 _, err = imgStore.PutBlobChunk("test", upload, 0, int64(l), buf) 1756 So(err, ShouldNotBeNil) 1757 }) 1758 1759 Convey("DirExists call with a filename as argument", t, func(c C) { 1760 dir := t.TempDir() 1761 1762 filePath := path.Join(dir, "file.txt") 1763 err := os.WriteFile(filePath, []byte("some dummy file content"), 0o644) //nolint: gosec 1764 if err != nil { 1765 panic(err) 1766 } 1767 1768 ok := common.DirExists(filePath) 1769 So(ok, ShouldBeFalse) 1770 }) 1771 1772 Convey("DirExists call with invalid UTF-8 as argument", t, func(c C) { 1773 dir := t.TempDir() 1774 1775 filePath := path.Join(dir, "hi \255") 1776 ok := common.DirExists(filePath) 1777 So(ok, ShouldBeFalse) 1778 }) 1779 1780 Convey("DirExists call with name too long as argument", t, func(c C) { 1781 var builder strings.Builder 1782 for i := 0; i < 1025; i++ { 1783 _, err := builder.WriteString("0") 1784 if err != nil { 1785 t.Fatal(err) 1786 } 1787 } 1788 path := builder.String() 1789 ok := common.DirExists(path) 1790 So(ok, ShouldBeFalse) 1791 }) 1792 } 1793 1794 func TestHardLink(t *testing.T) { 1795 Convey("Test that ValidateHardLink creates rootDir if it does not exist", t, func() { 1796 var randomDir string 1797 1798 for { 1799 nBig, err := rand.Int(rand.Reader, big.NewInt(100)) 1800 if err != nil { 1801 panic(err) 1802 } 1803 randomDir = "/tmp/" + randSeq(int(nBig.Int64())) 1804 1805 if _, err := os.Stat(randomDir); os.IsNotExist(err) { 1806 break 1807 } 1808 } 1809 defer os.RemoveAll(randomDir) 1810 1811 err := local.ValidateHardLink(randomDir) 1812 So(err, ShouldBeNil) 1813 }) 1814 Convey("Test that ValidateHardLink returns error if rootDir is a file", t, func() { 1815 dir := t.TempDir() 1816 1817 filePath := path.Join(dir, "file.txt") 1818 err := os.WriteFile(filePath, []byte("some dummy file content"), 0o644) //nolint: gosec 1819 if err != nil { 1820 panic(err) 1821 } 1822 1823 err = local.ValidateHardLink(filePath) 1824 So(err, ShouldNotBeNil) 1825 }) 1826 Convey("Test if filesystem supports hardlink", t, func() { 1827 dir := t.TempDir() 1828 1829 err := local.ValidateHardLink(dir) 1830 So(err, ShouldBeNil) 1831 1832 err = os.WriteFile(path.Join(dir, "hardtest.txt"), []byte("testing hard link code"), 0o644) //nolint: gosec 1833 if err != nil { 1834 panic(err) 1835 } 1836 1837 err = os.Chmod(dir, 0o400) 1838 if err != nil { 1839 panic(err) 1840 } 1841 // Allow hardtest.txt to be cleaned up by t.TempDir() 1842 t.Cleanup(func() { 1843 err = os.Chmod(dir, 0o700) 1844 if err != nil { 1845 t.Fatal(err) 1846 } 1847 }) 1848 1849 err = os.Link(path.Join(dir, "hardtest.txt"), path.Join(dir, "duphardtest.txt")) 1850 So(err, ShouldNotBeNil) 1851 1852 err = os.Chmod(dir, 0o644) 1853 if err != nil { 1854 panic(err) 1855 } 1856 }) 1857 } 1858 1859 func TestInjectWriteFile(t *testing.T) { 1860 Convey("writeFile without commit", t, func() { 1861 dir := t.TempDir() 1862 1863 log := zlog.Logger{Logger: zerolog.New(os.Stdout)} 1864 metrics := monitoring.NewMetricsServer(false, log) 1865 cacheDriver, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{ 1866 RootDir: dir, 1867 Name: "cache", 1868 UseRelPaths: true, 1869 }, log) 1870 1871 imgStore := local.NewImageStore(dir, true, false, log, metrics, nil, cacheDriver) 1872 1873 Convey("Failure path not reached", func() { 1874 err := imgStore.InitRepo("repo1") 1875 So(err, ShouldBeNil) 1876 }) 1877 }) 1878 } 1879 1880 func TestGarbageCollectForImageStore(t *testing.T) { 1881 //nolint: contextcheck 1882 Convey("Garbage collect for a specific repo from an ImageStore", t, func(c C) { 1883 dir := t.TempDir() 1884 1885 ctx := context.Background() 1886 1887 Convey("Garbage collect error for repo with config removed", func() { 1888 logFile, _ := os.CreateTemp("", "zot-log*.txt") 1889 1890 defer os.Remove(logFile.Name()) // clean up 1891 1892 log := zlog.NewLogger("debug", logFile.Name()) 1893 audit := zlog.NewAuditLogger("debug", "") 1894 1895 metrics := monitoring.NewMetricsServer(false, log) 1896 cacheDriver, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{ 1897 RootDir: dir, 1898 Name: "cache", 1899 UseRelPaths: true, 1900 }, log) 1901 1902 imgStore := local.NewImageStore(dir, true, true, log, metrics, nil, cacheDriver) 1903 repoName := "gc-all-repos-short" 1904 1905 gc := gc.NewGarbageCollect(imgStore, mocks.MetaDBMock{}, gc.Options{ 1906 Delay: 1 * time.Second, 1907 ImageRetention: DeleteReferrers, 1908 }, audit, log) 1909 1910 image := CreateDefaultVulnerableImage() 1911 err := WriteImageToFileSystem(image, repoName, "0.0.1", storage.StoreController{ 1912 DefaultStore: imgStore, 1913 }) 1914 So(err, ShouldBeNil) 1915 1916 manifestDigest := image.ManifestDescriptor.Digest 1917 err = os.Remove(path.Join(dir, repoName, "blobs/sha256", manifestDigest.Encoded())) 1918 if err != nil { 1919 panic(err) 1920 } 1921 1922 err = gc.CleanRepo(ctx, repoName) 1923 So(err, ShouldNotBeNil) 1924 1925 time.Sleep(500 * time.Millisecond) 1926 1927 data, err := os.ReadFile(logFile.Name()) 1928 So(err, ShouldBeNil) 1929 So(string(data), ShouldContainSubstring, 1930 fmt.Sprintf("failed to run GC for %s", path.Join(imgStore.RootDir(), repoName))) 1931 }) 1932 1933 Convey("Garbage collect error - not enough permissions to access index.json", func() { 1934 logFile, _ := os.CreateTemp("", "zot-log*.txt") 1935 1936 defer os.Remove(logFile.Name()) // clean up 1937 1938 log := zlog.NewLogger("debug", logFile.Name()) 1939 audit := zlog.NewAuditLogger("debug", "") 1940 1941 metrics := monitoring.NewMetricsServer(false, log) 1942 cacheDriver, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{ 1943 RootDir: dir, 1944 Name: "cache", 1945 UseRelPaths: true, 1946 }, log) 1947 1948 imgStore := local.NewImageStore(dir, true, true, log, metrics, nil, cacheDriver) 1949 repoName := "gc-all-repos-short" 1950 1951 gc := gc.NewGarbageCollect(imgStore, mocks.MetaDBMock{}, gc.Options{ 1952 Delay: 1 * time.Second, 1953 ImageRetention: DeleteReferrers, 1954 }, audit, log) 1955 1956 image := CreateDefaultVulnerableImage() 1957 err := WriteImageToFileSystem(image, repoName, "0.0.1", storage.StoreController{ 1958 DefaultStore: imgStore, 1959 }) 1960 So(err, ShouldBeNil) 1961 1962 So(os.Chmod(path.Join(dir, repoName, "index.json"), 0o000), ShouldBeNil) 1963 1964 err = gc.CleanRepo(ctx, repoName) 1965 So(err, ShouldNotBeNil) 1966 1967 time.Sleep(500 * time.Millisecond) 1968 1969 data, err := os.ReadFile(logFile.Name()) 1970 So(err, ShouldBeNil) 1971 So(string(data), ShouldContainSubstring, 1972 fmt.Sprintf("failed to run GC for %s", path.Join(imgStore.RootDir(), repoName))) 1973 So(os.Chmod(path.Join(dir, repoName, "index.json"), 0o755), ShouldBeNil) 1974 }) 1975 1976 Convey("Garbage collect - the manifest which the reference points to can be found", func() { 1977 logFile, _ := os.CreateTemp("", "zot-log*.txt") 1978 1979 defer os.Remove(logFile.Name()) // clean up 1980 1981 log := zlog.NewLogger("debug", logFile.Name()) 1982 audit := zlog.NewAuditLogger("debug", "") 1983 1984 metrics := monitoring.NewMetricsServer(false, log) 1985 cacheDriver, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{ 1986 RootDir: dir, 1987 Name: "cache", 1988 UseRelPaths: true, 1989 }, log) 1990 imgStore := local.NewImageStore(dir, true, true, log, metrics, nil, cacheDriver) 1991 repoName := "gc-sig" 1992 1993 gc := gc.NewGarbageCollect(imgStore, mocks.MetaDBMock{}, gc.Options{ 1994 Delay: 1 * time.Second, 1995 ImageRetention: DeleteReferrers, 1996 }, audit, log) 1997 1998 storeController := storage.StoreController{DefaultStore: imgStore} 1999 img := CreateRandomImage() 2000 2001 err := WriteImageToFileSystem(img, repoName, "tag1", storeController) 2002 So(err, ShouldBeNil) 2003 2004 // add fake signature for tag1 2005 cosignTag, err := signature.GetCosignSignatureTagForManifest(img.Manifest) 2006 So(err, ShouldBeNil) 2007 2008 cosignSig := CreateRandomImage() 2009 So(err, ShouldBeNil) 2010 2011 err = WriteImageToFileSystem(cosignSig, repoName, cosignTag, storeController) 2012 So(err, ShouldBeNil) 2013 2014 // add sbom 2015 manifestBlob, err := json.Marshal(img.Manifest) 2016 So(err, ShouldBeNil) 2017 2018 manifestDigest := godigest.FromBytes(manifestBlob) 2019 sbomTag := fmt.Sprintf("sha256-%s.%s", manifestDigest.Encoded(), "sbom") 2020 So(err, ShouldBeNil) 2021 2022 sbomImg := CreateRandomImage() 2023 So(err, ShouldBeNil) 2024 2025 err = WriteImageToFileSystem(sbomImg, repoName, sbomTag, storeController) 2026 So(err, ShouldBeNil) 2027 2028 // add fake signature for tag1 2029 notationSig := CreateImageWith(). 2030 RandomLayers(1, 10). 2031 ArtifactConfig("application/vnd.cncf.notary.signature"). 2032 Subject(img.DescriptorRef()).Build() 2033 2034 err = WriteImageToFileSystem(notationSig, repoName, "notation", storeController) 2035 So(err, ShouldBeNil) 2036 2037 // add fake signature for tag1 2038 cosignWithReferrersSig := CreateImageWith(). 2039 RandomLayers(1, 10). 2040 ArtifactConfig(common.ArtifactTypeCosign). 2041 Subject(img.DescriptorRef()).Build() 2042 2043 err = WriteImageToFileSystem(cosignWithReferrersSig, repoName, "cosign", storeController) 2044 So(err, ShouldBeNil) 2045 2046 err = gc.CleanRepo(ctx, repoName) 2047 So(err, ShouldBeNil) 2048 }) 2049 }) 2050 } 2051 2052 func TestGarbageCollectImageUnknownManifest(t *testing.T) { 2053 Convey("Garbage collect with short delay", t, func() { 2054 ctx := context.Background() 2055 2056 dir := t.TempDir() 2057 2058 log := zlog.NewLogger("debug", "") 2059 audit := zlog.NewAuditLogger("debug", "") 2060 2061 metrics := monitoring.NewMetricsServer(false, log) 2062 cacheDriver, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{ 2063 RootDir: dir, 2064 Name: "cache", 2065 UseRelPaths: true, 2066 }, log) 2067 2068 imgStore := local.NewImageStore(dir, true, true, log, metrics, nil, cacheDriver) 2069 2070 storeController := storage.StoreController{ 2071 DefaultStore: imgStore, 2072 } 2073 2074 gc := gc.NewGarbageCollect(imgStore, mocks.MetaDBMock{}, gc.Options{ 2075 Delay: 1 * time.Second, 2076 ImageRetention: DeleteReferrers, 2077 }, audit, log) 2078 2079 unsupportedMediaType := "application/vnd.oci.artifact.manifest.v1+json" 2080 2081 img := CreateRandomImage() 2082 2083 err := WriteImageToFileSystem(img, repoName, "v1", storeController) 2084 So(err, ShouldBeNil) 2085 2086 // add image with unsupported media type 2087 artifact := CreateRandomImage() 2088 2089 err = WriteImageToFileSystem(artifact, repoName, "artifact", storeController) 2090 So(err, ShouldBeNil) 2091 2092 // add referrer with unsupported media type 2093 subjectDesc := img.Descriptor() 2094 referrer := CreateRandomImageWith().Subject(&subjectDesc).Build() 2095 2096 err = WriteImageToFileSystem(referrer, repoName, referrer.Digest().String(), storeController) 2097 So(err, ShouldBeNil) 2098 2099 // modify artifact media type 2100 artifactBuf, err := os.ReadFile(imgStore.BlobPath(repoName, artifact.Digest())) 2101 So(err, ShouldBeNil) 2102 2103 var artifactManifest ispec.Manifest 2104 err = json.Unmarshal(artifactBuf, &artifactManifest) 2105 So(err, ShouldBeNil) 2106 2107 artifactManifest.MediaType = unsupportedMediaType 2108 artifactBuf, err = json.Marshal(artifactManifest) 2109 So(err, ShouldBeNil) 2110 2111 artifactDigest := godigest.FromBytes(artifactBuf) 2112 err = os.WriteFile(path.Join(imgStore.RootDir(), repoName, "blobs", "sha256", artifactDigest.Encoded()), 2113 artifactBuf, storageConstants.DefaultFilePerms) 2114 So(err, ShouldBeNil) 2115 2116 // modify referrer media type 2117 referrerBuf, err := os.ReadFile(imgStore.BlobPath(repoName, referrer.Digest())) 2118 So(err, ShouldBeNil) 2119 2120 var referrerManifest ispec.Manifest 2121 err = json.Unmarshal(referrerBuf, &referrerManifest) 2122 So(err, ShouldBeNil) 2123 2124 referrerManifest.MediaType = unsupportedMediaType 2125 referrerBuf, err = json.Marshal(referrerManifest) 2126 So(err, ShouldBeNil) 2127 2128 referrerDigest := godigest.FromBytes(referrerBuf) 2129 err = os.WriteFile(path.Join(imgStore.RootDir(), repoName, "blobs", "sha256", referrerDigest.Encoded()), 2130 referrerBuf, storageConstants.DefaultFilePerms) 2131 So(err, ShouldBeNil) 2132 2133 indexJSONBuf, err := os.ReadFile(path.Join(imgStore.RootDir(), repoName, "index.json")) 2134 So(err, ShouldBeNil) 2135 2136 var indexJSON ispec.Index 2137 err = json.Unmarshal(indexJSONBuf, &indexJSON) 2138 So(err, ShouldBeNil) 2139 2140 for idx, desc := range indexJSON.Manifests { 2141 if desc.Digest == artifact.Digest() { 2142 indexJSON.Manifests[idx].Digest = artifactDigest 2143 indexJSON.Manifests[idx].MediaType = unsupportedMediaType 2144 } else if desc.Digest == referrer.Digest() { 2145 indexJSON.Manifests[idx].Digest = referrerDigest 2146 indexJSON.Manifests[idx].MediaType = unsupportedMediaType 2147 } 2148 } 2149 2150 indexJSONBuf, err = json.Marshal(indexJSON) 2151 So(err, ShouldBeNil) 2152 2153 err = os.WriteFile(path.Join(imgStore.RootDir(), repoName, "index.json"), 2154 indexJSONBuf, storageConstants.DefaultFilePerms) 2155 So(err, ShouldBeNil) 2156 2157 // sleep so orphan blob can be GC'ed 2158 time.Sleep(1 * time.Second) 2159 2160 Convey("Garbage collect blobs referenced by manifest with unsupported media type", func() { 2161 err = gc.CleanRepo(ctx, repoName) 2162 So(err, ShouldBeNil) 2163 2164 _, _, _, err = imgStore.GetImageManifest(repoName, img.DigestStr()) 2165 So(err, ShouldBeNil) 2166 2167 hasBlob, _, err := imgStore.CheckBlob(repoName, img.ConfigDescriptor.Digest) 2168 So(err, ShouldBeNil) 2169 So(hasBlob, ShouldEqual, true) 2170 2171 _, _, _, err = imgStore.GetImageManifest(repoName, artifactDigest.String()) 2172 So(err, ShouldNotBeNil) 2173 2174 _, _, _, err = imgStore.GetImageManifest(repoName, referrerDigest.String()) 2175 So(err, ShouldNotBeNil) 2176 2177 hasBlob, _, err = imgStore.CheckBlob(repoName, artifactDigest) 2178 So(err, ShouldNotBeNil) 2179 So(hasBlob, ShouldEqual, false) 2180 2181 hasBlob, _, err = imgStore.CheckBlob(repoName, referrerDigest) 2182 So(err, ShouldNotBeNil) 2183 So(hasBlob, ShouldEqual, false) 2184 2185 hasBlob, _, err = imgStore.CheckBlob(repoName, artifact.ConfigDescriptor.Digest) 2186 So(err, ShouldNotBeNil) 2187 So(hasBlob, ShouldEqual, false) 2188 2189 hasBlob, _, err = imgStore.CheckBlob(repoName, referrer.ConfigDescriptor.Digest) 2190 So(err, ShouldNotBeNil) 2191 So(hasBlob, ShouldEqual, false) 2192 }) 2193 2194 Convey("Garbage collect - gc repo after manifest delete", func() { 2195 err = imgStore.DeleteImageManifest(repoName, img.DigestStr(), true) 2196 So(err, ShouldBeNil) 2197 2198 err = gc.CleanRepo(ctx, repoName) 2199 So(err, ShouldBeNil) 2200 2201 _, _, _, err = imgStore.GetImageManifest(repoName, img.DigestStr()) 2202 So(err, ShouldNotBeNil) 2203 2204 hasBlob, _, err := imgStore.CheckBlob(repoName, img.ConfigDescriptor.Digest) 2205 So(err, ShouldNotBeNil) 2206 So(hasBlob, ShouldEqual, false) 2207 2208 _, _, _, err = imgStore.GetImageManifest(repoName, artifactDigest.String()) 2209 So(err, ShouldNotBeNil) 2210 2211 _, _, _, err = imgStore.GetImageManifest(repoName, referrerDigest.String()) 2212 So(err, ShouldNotBeNil) 2213 2214 hasBlob, _, err = imgStore.CheckBlob(repoName, artifactDigest) 2215 So(err, ShouldNotBeNil) 2216 So(hasBlob, ShouldEqual, false) 2217 2218 hasBlob, _, err = imgStore.CheckBlob(repoName, referrerDigest) 2219 So(err, ShouldNotBeNil) 2220 So(hasBlob, ShouldEqual, false) 2221 2222 hasBlob, _, err = imgStore.CheckBlob(repoName, artifact.ConfigDescriptor.Digest) 2223 So(err, ShouldNotBeNil) 2224 So(hasBlob, ShouldEqual, false) 2225 2226 hasBlob, _, err = imgStore.CheckBlob(repoName, referrer.ConfigDescriptor.Digest) 2227 So(err, ShouldNotBeNil) 2228 So(hasBlob, ShouldEqual, false) 2229 }) 2230 }) 2231 } 2232 2233 func TestGarbageCollectErrors(t *testing.T) { 2234 Convey("Make image store", t, func(c C) { 2235 ctx := context.Background() 2236 2237 dir := t.TempDir() 2238 2239 log := zlog.NewLogger("debug", "") 2240 audit := zlog.NewAuditLogger("debug", "") 2241 2242 metrics := monitoring.NewMetricsServer(false, log) 2243 cacheDriver, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{ 2244 RootDir: dir, 2245 Name: "cache", 2246 UseRelPaths: true, 2247 }, log) 2248 2249 imgStore := local.NewImageStore(dir, true, true, log, metrics, nil, cacheDriver) 2250 repoName := "gc-index" 2251 2252 gc := gc.NewGarbageCollect(imgStore, mocks.MetaDBMock{}, gc.Options{ 2253 Delay: 500 * time.Millisecond, 2254 ImageRetention: DeleteReferrers, 2255 }, audit, log) 2256 2257 // create a blob/layer 2258 upload, err := imgStore.NewBlobUpload(repoName) 2259 So(err, ShouldBeNil) 2260 So(upload, ShouldNotBeEmpty) 2261 2262 content := []byte("this is a blob1") 2263 buf := bytes.NewBuffer(content) 2264 buflen := buf.Len() 2265 digest := godigest.FromBytes(content) 2266 So(digest, ShouldNotBeNil) 2267 blob, err := imgStore.PutBlobChunkStreamed(repoName, upload, buf) 2268 So(err, ShouldBeNil) 2269 So(blob, ShouldEqual, buflen) 2270 bdgst1 := digest 2271 bsize1 := len(content) 2272 2273 err = imgStore.FinishBlobUpload(repoName, upload, buf, digest) 2274 So(err, ShouldBeNil) 2275 So(blob, ShouldEqual, buflen) 2276 2277 Convey("Trigger error on GetImageIndex", func() { 2278 var index ispec.Index 2279 index.SchemaVersion = 2 2280 index.MediaType = ispec.MediaTypeImageIndex 2281 2282 for i := 0; i < 4; i++ { 2283 // upload image config blob 2284 upload, err = imgStore.NewBlobUpload(repoName) 2285 So(err, ShouldBeNil) 2286 So(upload, ShouldNotBeEmpty) 2287 2288 cblob, cdigest := GetRandomImageConfig() 2289 buf = bytes.NewBuffer(cblob) 2290 buflen = buf.Len() 2291 blob, err = imgStore.PutBlobChunkStreamed(repoName, upload, buf) 2292 So(err, ShouldBeNil) 2293 So(blob, ShouldEqual, buflen) 2294 2295 err = imgStore.FinishBlobUpload(repoName, upload, buf, cdigest) 2296 So(err, ShouldBeNil) 2297 So(blob, ShouldEqual, buflen) 2298 2299 // create a manifest 2300 manifest := ispec.Manifest{ 2301 Config: ispec.Descriptor{ 2302 MediaType: ispec.MediaTypeImageConfig, 2303 Digest: cdigest, 2304 Size: int64(len(cblob)), 2305 }, 2306 Layers: []ispec.Descriptor{ 2307 { 2308 MediaType: ispec.MediaTypeImageLayer, 2309 Digest: bdgst1, 2310 Size: int64(bsize1), 2311 }, 2312 }, 2313 } 2314 manifest.SchemaVersion = 2 2315 content, err = json.Marshal(manifest) 2316 So(err, ShouldBeNil) 2317 digest = godigest.FromBytes(content) 2318 So(digest, ShouldNotBeNil) 2319 _, _, err = imgStore.PutImageManifest(repoName, digest.String(), ispec.MediaTypeImageManifest, content) 2320 So(err, ShouldBeNil) 2321 2322 index.Manifests = append(index.Manifests, ispec.Descriptor{ 2323 Digest: digest, 2324 MediaType: ispec.MediaTypeImageManifest, 2325 Size: int64(len(content)), 2326 }) 2327 } 2328 2329 // upload index image 2330 indexContent, err := json.Marshal(index) 2331 So(err, ShouldBeNil) 2332 indexDigest := godigest.FromBytes(indexContent) 2333 So(indexDigest, ShouldNotBeNil) 2334 2335 _, _, err = imgStore.PutImageManifest(repoName, "1.0", ispec.MediaTypeImageIndex, indexContent) 2336 So(err, ShouldBeNil) 2337 2338 err = os.Chmod(imgStore.BlobPath(repoName, indexDigest), 0o000) 2339 So(err, ShouldBeNil) 2340 2341 time.Sleep(500 * time.Millisecond) 2342 2343 err = gc.CleanRepo(ctx, repoName) 2344 So(err, ShouldNotBeNil) 2345 }) 2346 2347 Convey("Trigger error on GetBlobContent and Unmarshal for untagged manifest", func() { 2348 // upload image config blob 2349 upload, err = imgStore.NewBlobUpload(repoName) 2350 So(err, ShouldBeNil) 2351 So(upload, ShouldNotBeEmpty) 2352 2353 cblob, cdigest := GetRandomImageConfig() 2354 buf = bytes.NewBuffer(cblob) 2355 buflen = buf.Len() 2356 blob, err = imgStore.PutBlobChunkStreamed(repoName, upload, buf) 2357 So(err, ShouldBeNil) 2358 So(blob, ShouldEqual, buflen) 2359 2360 err = imgStore.FinishBlobUpload(repoName, upload, buf, cdigest) 2361 So(err, ShouldBeNil) 2362 So(blob, ShouldEqual, buflen) 2363 2364 // create a manifest 2365 manifest := ispec.Manifest{ 2366 Config: ispec.Descriptor{ 2367 MediaType: ispec.MediaTypeImageConfig, 2368 Digest: cdigest, 2369 Size: int64(len(cblob)), 2370 }, 2371 Layers: []ispec.Descriptor{ 2372 { 2373 MediaType: ispec.MediaTypeImageLayer, 2374 Digest: bdgst1, 2375 Size: int64(bsize1), 2376 }, 2377 }, 2378 } 2379 manifest.SchemaVersion = 2 2380 content, err = json.Marshal(manifest) 2381 So(err, ShouldBeNil) 2382 digest = godigest.FromBytes(content) 2383 So(digest, ShouldNotBeNil) 2384 2385 _, _, err = imgStore.PutImageManifest(repoName, digest.String(), ispec.MediaTypeImageManifest, content) 2386 So(err, ShouldBeNil) 2387 2388 // trigger GetBlobContent error 2389 err = os.Remove(imgStore.BlobPath(repoName, digest)) 2390 So(err, ShouldBeNil) 2391 2392 time.Sleep(500 * time.Millisecond) 2393 2394 err = gc.CleanRepo(ctx, repoName) 2395 So(err, ShouldNotBeNil) 2396 2397 // trigger Unmarshal error 2398 _, err = os.Create(imgStore.BlobPath(repoName, digest)) 2399 So(err, ShouldBeNil) 2400 2401 err = gc.CleanRepo(ctx, repoName) 2402 So(err, ShouldNotBeNil) 2403 }) 2404 2405 Convey("Trigger manifest conflict error", func() { 2406 // upload image config blob 2407 upload, err = imgStore.NewBlobUpload(repoName) 2408 So(err, ShouldBeNil) 2409 So(upload, ShouldNotBeEmpty) 2410 2411 cblob, cdigest := GetRandomImageConfig() 2412 buf = bytes.NewBuffer(cblob) 2413 buflen = buf.Len() 2414 blob, err = imgStore.PutBlobChunkStreamed(repoName, upload, buf) 2415 So(err, ShouldBeNil) 2416 So(blob, ShouldEqual, buflen) 2417 2418 err = imgStore.FinishBlobUpload(repoName, upload, buf, cdigest) 2419 So(err, ShouldBeNil) 2420 So(blob, ShouldEqual, buflen) 2421 2422 // create a manifest 2423 manifest := ispec.Manifest{ 2424 Config: ispec.Descriptor{ 2425 MediaType: ispec.MediaTypeImageConfig, 2426 Digest: cdigest, 2427 Size: int64(len(cblob)), 2428 }, 2429 Layers: []ispec.Descriptor{ 2430 { 2431 MediaType: ispec.MediaTypeImageLayer, 2432 Digest: bdgst1, 2433 Size: int64(bsize1), 2434 }, 2435 }, 2436 } 2437 manifest.SchemaVersion = 2 2438 content, err = json.Marshal(manifest) 2439 So(err, ShouldBeNil) 2440 digest = godigest.FromBytes(content) 2441 So(digest, ShouldNotBeNil) 2442 2443 _, _, err = imgStore.PutImageManifest(repoName, digest.String(), ispec.MediaTypeImageManifest, content) 2444 So(err, ShouldBeNil) 2445 // upload again same manifest so that we trigger manifest conflict 2446 _, _, err = imgStore.PutImageManifest(repoName, "1.0", ispec.MediaTypeImageManifest, content) 2447 So(err, ShouldBeNil) 2448 2449 time.Sleep(500 * time.Millisecond) 2450 2451 err = gc.CleanRepo(ctx, repoName) 2452 So(err, ShouldBeNil) 2453 2454 // blob shouldn't be gc'ed //TODO check this one 2455 found, _, err := imgStore.CheckBlob(repoName, digest) 2456 So(err, ShouldBeNil) 2457 So(found, ShouldEqual, true) 2458 }) 2459 }) 2460 } 2461 2462 func randSeq(n int) string { 2463 letters := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") 2464 2465 buf := make([]rune, n) 2466 for index := range buf { 2467 nBig, err := rand.Int(rand.Reader, big.NewInt(int64(len(letters)))) 2468 if err != nil { 2469 panic(err) 2470 } 2471 2472 buf[index] = letters[int(nBig.Int64())] 2473 } 2474 2475 return string(buf) 2476 } 2477 2478 func TestInitRepo(t *testing.T) { 2479 Convey("Get error when creating BlobUploadDir subdir on initRepo", t, func() { 2480 dir := t.TempDir() 2481 2482 log := zlog.Logger{Logger: zerolog.New(os.Stdout)} 2483 metrics := monitoring.NewMetricsServer(false, log) 2484 cacheDriver, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{ 2485 RootDir: dir, 2486 Name: "cache", 2487 UseRelPaths: true, 2488 }, log) 2489 2490 imgStore := local.NewImageStore(dir, true, true, log, metrics, nil, cacheDriver) 2491 2492 err := os.Mkdir(path.Join(dir, "test-dir"), 0o000) 2493 So(err, ShouldBeNil) 2494 2495 err = imgStore.InitRepo("test-dir") 2496 So(err, ShouldNotBeNil) 2497 }) 2498 } 2499 2500 func TestValidateRepo(t *testing.T) { 2501 Convey("Get error when unable to read directory", t, func() { 2502 dir := t.TempDir() 2503 2504 log := zlog.Logger{Logger: zerolog.New(os.Stdout)} 2505 metrics := monitoring.NewMetricsServer(false, log) 2506 cacheDriver, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{ 2507 RootDir: dir, 2508 Name: "cache", 2509 UseRelPaths: true, 2510 }, log) 2511 2512 imgStore := local.NewImageStore(dir, true, true, log, metrics, nil, cacheDriver) 2513 2514 err := os.Mkdir(path.Join(dir, "test-dir"), 0o000) 2515 So(err, ShouldBeNil) 2516 2517 _, err = imgStore.ValidateRepo("test-dir") 2518 So(err, ShouldNotBeNil) 2519 }) 2520 2521 Convey("Get error when repo name is not compliant with repo spec", t, func() { 2522 dir := t.TempDir() 2523 2524 log := zlog.Logger{Logger: zerolog.New(os.Stdout)} 2525 metrics := monitoring.NewMetricsServer(false, log) 2526 cacheDriver, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{ 2527 RootDir: dir, 2528 Name: "cache", 2529 UseRelPaths: true, 2530 }, log) 2531 2532 imgStore := local.NewImageStore(dir, true, true, log, metrics, nil, cacheDriver) 2533 2534 _, err := imgStore.ValidateRepo(".") 2535 So(err, ShouldNotBeNil) 2536 So(errors.Is(err, zerr.ErrInvalidRepositoryName), ShouldBeTrue) 2537 2538 _, err = imgStore.ValidateRepo("..") 2539 So(err, ShouldNotBeNil) 2540 So(errors.Is(err, zerr.ErrInvalidRepositoryName), ShouldBeTrue) 2541 2542 err = os.Mkdir(path.Join(dir, "_test-dir"), 0o755) 2543 So(err, ShouldBeNil) 2544 2545 _, err = imgStore.ValidateRepo("_test-dir") 2546 So(err, ShouldNotBeNil) 2547 So(errors.Is(err, zerr.ErrInvalidRepositoryName), ShouldBeTrue) 2548 2549 err = os.Mkdir(path.Join(dir, ".test-dir"), 0o755) 2550 So(err, ShouldBeNil) 2551 2552 _, err = imgStore.ValidateRepo(".test-dir") 2553 So(err, ShouldNotBeNil) 2554 So(errors.Is(err, zerr.ErrInvalidRepositoryName), ShouldBeTrue) 2555 2556 err = os.Mkdir(path.Join(dir, "-test-dir"), 0o755) 2557 So(err, ShouldBeNil) 2558 2559 _, err = imgStore.ValidateRepo("-test-dir") 2560 So(err, ShouldNotBeNil) 2561 So(errors.Is(err, zerr.ErrInvalidRepositoryName), ShouldBeTrue) 2562 }) 2563 } 2564 2565 func TestGetRepositories(t *testing.T) { 2566 Convey("Verify errors and repos returned by GetRepositories()", t, func() { 2567 dir := t.TempDir() 2568 2569 log := zlog.Logger{Logger: zerolog.New(os.Stdout)} 2570 metrics := monitoring.NewMetricsServer(false, log) 2571 cacheDriver, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{ 2572 RootDir: dir, 2573 Name: "cache", 2574 UseRelPaths: true, 2575 }, log) 2576 2577 imgStore := local.NewImageStore(dir, true, true, log, metrics, nil, cacheDriver) 2578 2579 // Create valid directory with permissions 2580 err := os.Mkdir(path.Join(dir, "test-dir"), 0o755) //nolint: gosec 2581 So(err, ShouldBeNil) 2582 2583 err = os.WriteFile(path.Join(dir, "test-dir/test-file"), []byte("this is test file"), 0o755) //nolint: gosec 2584 So(err, ShouldBeNil) 2585 2586 // Folder is not a repo as it is missing the requires files/subfolder 2587 repos, err := imgStore.GetRepositories() 2588 So(err, ShouldBeNil) 2589 So(len(repos), ShouldEqual, 0) 2590 2591 il := ispec.ImageLayout{Version: ispec.ImageLayoutVersion} 2592 layoutFileContent, err := json.Marshal(il) 2593 So(err, ShouldBeNil) 2594 2595 // Folder becomes a repo after the missing content is added 2596 err = os.Mkdir(path.Join(dir, "test-dir", "blobs"), 0o755) //nolint: gosec 2597 So(err, ShouldBeNil) 2598 2599 err = os.Mkdir(path.Join(dir, "test-dir", storageConstants.BlobUploadDir), 0o755) //nolint: gosec 2600 So(err, ShouldBeNil) 2601 2602 err = os.WriteFile(path.Join(dir, "test-dir", "index.json"), []byte{}, 0o755) //nolint: gosec 2603 So(err, ShouldBeNil) 2604 2605 err = os.WriteFile(path.Join(dir, "test-dir", ispec.ImageLayoutFile), layoutFileContent, 0o755) //nolint: gosec 2606 So(err, ShouldBeNil) 2607 2608 // Verify the new repo is turned 2609 repos, err = imgStore.GetRepositories() 2610 So(err, ShouldBeNil) 2611 So(len(repos), ShouldEqual, 1) 2612 So(repos[0], ShouldEqual, "test-dir") 2613 2614 // create directory starting with underscore, which is not OCI a dist spec compliant repo name 2615 // [a-z0-9]+([._-][a-z0-9]+)*(/[a-z0-9]+([._-][a-z0-9]+)*)* 2616 err = os.MkdirAll(path.Join(dir, "_trivy", "db"), 0o755) 2617 So(err, ShouldBeNil) 2618 2619 err = os.WriteFile(path.Join(dir, "_trivy", "db", "trivy.db"), []byte("this is test file"), 0o755) //nolint: gosec 2620 So(err, ShouldBeNil) 2621 2622 // Folder with invalid name is not a repo as it is missing the requires files/subfolder 2623 repos, err = imgStore.GetRepositories() 2624 So(err, ShouldBeNil) 2625 So(len(repos), ShouldEqual, 1) 2626 So(repos[0], ShouldEqual, "test-dir") 2627 2628 // Add missing content to folder with invalid name 2629 err = os.Mkdir(path.Join(dir, "_trivy", "blobs"), 0o755) //nolint: gosec 2630 So(err, ShouldBeNil) 2631 2632 err = os.Mkdir(path.Join(dir, "_trivy", storageConstants.BlobUploadDir), 0o755) //nolint: gosec 2633 So(err, ShouldBeNil) 2634 2635 err = os.WriteFile(path.Join(dir, "_trivy", "index.json"), []byte{}, 0o755) //nolint: gosec 2636 So(err, ShouldBeNil) 2637 2638 err = os.WriteFile(path.Join(dir, "_trivy", ispec.ImageLayoutFile), layoutFileContent, 0o755) //nolint: gosec 2639 So(err, ShouldBeNil) 2640 2641 // Folder with invalid name doesn't become a repo after the missing content is added 2642 repos, err = imgStore.GetRepositories() 2643 So(err, ShouldBeNil) 2644 t.Logf("repos %v", repos) 2645 So(len(repos), ShouldEqual, 1) 2646 So(repos[0], ShouldEqual, "test-dir") 2647 2648 // Rename folder with invalid name to a valid one 2649 err = os.Rename(path.Join(dir, "_trivy"), path.Join(dir, "test-dir-2")) 2650 So(err, ShouldBeNil) 2651 2652 // Verify both repos are now visible 2653 repos, err = imgStore.GetRepositories() 2654 So(err, ShouldBeNil) 2655 t.Logf("repos %v", repos) 2656 So(len(repos), ShouldEqual, 2) 2657 So(repos, ShouldContain, "test-dir") 2658 So(repos, ShouldContain, "test-dir-2") 2659 }) 2660 2661 Convey("Verify GetRepositories() doesn't return '.' when having an oci layout as root directory ", t, func() { 2662 dir := t.TempDir() 2663 2664 log := zlog.Logger{Logger: zerolog.New(os.Stdout)} 2665 metrics := monitoring.NewMetricsServer(false, log) 2666 cacheDriver, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{ 2667 RootDir: dir, 2668 Name: "cache", 2669 UseRelPaths: true, 2670 }, log) 2671 2672 imgStore := local.NewImageStore(dir, true, true, log, metrics, nil, cacheDriver) 2673 2674 // Root dir does not contain repos 2675 repos, err := imgStore.GetRepositories() 2676 So(err, ShouldBeNil) 2677 So(len(repos), ShouldEqual, 0) 2678 2679 // Configure root directory as an oci layout 2680 err = os.Mkdir(path.Join(dir, "blobs"), 0o755) //nolint: gosec 2681 So(err, ShouldBeNil) 2682 2683 err = os.Mkdir(path.Join(dir, storageConstants.BlobUploadDir), 0o755) //nolint: gosec 2684 So(err, ShouldBeNil) 2685 2686 err = os.WriteFile(path.Join(dir, "index.json"), []byte{}, 0o755) //nolint: gosec 2687 So(err, ShouldBeNil) 2688 2689 il := ispec.ImageLayout{Version: ispec.ImageLayoutVersion} 2690 layoutFileContent, err := json.Marshal(il) 2691 So(err, ShouldBeNil) 2692 2693 err = os.WriteFile(path.Join(dir, ispec.ImageLayoutFile), layoutFileContent, 0o755) //nolint: gosec 2694 So(err, ShouldBeNil) 2695 2696 // Verify root directory is not returned as a repo 2697 repos, err = imgStore.GetRepositories() 2698 So(err, ShouldBeNil) 2699 t.Logf("repos %v", repos) 2700 So(len(repos), ShouldEqual, 0) 2701 }) 2702 2703 Convey("Verify GetRepositories() doesn't return '..'", t, func() { 2704 dir := t.TempDir() 2705 2706 rootDir := path.Join(dir, "rootDir") 2707 err := os.Mkdir(rootDir, 0o755) 2708 So(err, ShouldBeNil) 2709 2710 log := zlog.Logger{Logger: zerolog.New(os.Stdout)} 2711 metrics := monitoring.NewMetricsServer(false, log) 2712 cacheDriver, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{ 2713 RootDir: rootDir, 2714 Name: "cache", 2715 UseRelPaths: true, 2716 }, log) 2717 2718 imgStore := local.NewImageStore(rootDir, 2719 true, true, log, metrics, nil, cacheDriver, 2720 ) 2721 2722 // Root dir does not contain repos 2723 repos, err := imgStore.GetRepositories() 2724 So(err, ShouldBeNil) 2725 So(len(repos), ShouldEqual, 0) 2726 2727 // Configure parent of root directory as an oci layout 2728 err = os.Mkdir(path.Join(dir, "blobs"), 0o755) //nolint: gosec 2729 So(err, ShouldBeNil) 2730 2731 err = os.Mkdir(path.Join(dir, storageConstants.BlobUploadDir), 0o755) //nolint: gosec 2732 So(err, ShouldBeNil) 2733 2734 err = os.WriteFile(path.Join(dir, "index.json"), []byte{}, 0o755) //nolint: gosec 2735 So(err, ShouldBeNil) 2736 2737 il := ispec.ImageLayout{Version: ispec.ImageLayoutVersion} 2738 layoutFileContent, err := json.Marshal(il) 2739 So(err, ShouldBeNil) 2740 2741 err = os.WriteFile(path.Join(dir, ispec.ImageLayoutFile), layoutFileContent, 0o755) //nolint: gosec 2742 So(err, ShouldBeNil) 2743 2744 // Verify root directory is not returned as a repo 2745 repos, err = imgStore.GetRepositories() 2746 So(err, ShouldBeNil) 2747 t.Logf("repos %v", repos) 2748 So(len(repos), ShouldEqual, 0) 2749 }) 2750 } 2751 2752 func TestGetNextRepository(t *testing.T) { 2753 dir := t.TempDir() 2754 log := zlog.Logger{Logger: zerolog.New(os.Stdout)} 2755 metrics := monitoring.NewMetricsServer(false, log) 2756 cacheDriver, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{ 2757 RootDir: dir, 2758 Name: "cache", 2759 UseRelPaths: true, 2760 }, log) 2761 2762 imgStore := local.NewImageStore(dir, true, true, log, metrics, nil, cacheDriver) 2763 firstRepoName := "repo1" 2764 secondRepoName := "repo2" 2765 2766 srcStorageCtlr := storage.StoreController{DefaultStore: imgStore} 2767 image := CreateDefaultImage() 2768 2769 err := WriteImageToFileSystem(image, firstRepoName, "0.0.1", srcStorageCtlr) 2770 if err != nil { 2771 t.Log(err) 2772 t.FailNow() 2773 } 2774 2775 err = WriteImageToFileSystem(image, secondRepoName, "0.0.1", srcStorageCtlr) 2776 if err != nil { 2777 t.Log(err) 2778 t.FailNow() 2779 } 2780 2781 Convey("Return first repository", t, func() { 2782 firstRepo, err := imgStore.GetNextRepository("") 2783 So(firstRepo, ShouldEqual, firstRepoName) 2784 So(err, ShouldBeNil) 2785 }) 2786 2787 Convey("Return second repository", t, func() { 2788 secondRepo, err := imgStore.GetNextRepository(firstRepoName) 2789 So(secondRepo, ShouldEqual, secondRepoName) 2790 So(err, ShouldBeNil) 2791 }) 2792 2793 Convey("Return error", t, func() { 2794 err := os.Chmod(imgStore.RootDir(), 0o000) 2795 So(err, ShouldBeNil) 2796 _, err = imgStore.GetNextRepository(firstRepoName) 2797 So(err, ShouldNotBeNil) 2798 err = os.Chmod(imgStore.RootDir(), 0o755) 2799 So(err, ShouldBeNil) 2800 }) 2801 } 2802 2803 func TestPutBlobChunkStreamed(t *testing.T) { 2804 Convey("Get error on opening file", t, func() { 2805 dir := t.TempDir() 2806 2807 log := zlog.Logger{Logger: zerolog.New(os.Stdout)} 2808 metrics := monitoring.NewMetricsServer(false, log) 2809 cacheDriver, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{ 2810 RootDir: dir, 2811 Name: "cache", 2812 UseRelPaths: true, 2813 }, log) 2814 2815 imgStore := local.NewImageStore(dir, true, true, log, metrics, nil, cacheDriver) 2816 2817 uuid, err := imgStore.NewBlobUpload("test") 2818 So(err, ShouldBeNil) 2819 2820 var reader io.Reader 2821 blobPath := imgStore.BlobUploadPath("test", uuid) 2822 err = os.Chmod(blobPath, 0o000) 2823 So(err, ShouldBeNil) 2824 2825 _, err = imgStore.PutBlobChunkStreamed("test", uuid, reader) 2826 So(err, ShouldNotBeNil) 2827 }) 2828 } 2829 2830 func TestPullRange(t *testing.T) { 2831 Convey("Repo layout", t, func(c C) { 2832 dir := t.TempDir() 2833 2834 log := zlog.Logger{Logger: zerolog.New(os.Stdout)} 2835 metrics := monitoring.NewMetricsServer(false, log) 2836 2837 Convey("Negative cases", func() { 2838 cacheDriver, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{ 2839 RootDir: dir, 2840 Name: "cache", 2841 UseRelPaths: true, 2842 }, log) 2843 2844 imgStore := local.NewImageStore(dir, true, true, log, metrics, nil, cacheDriver) 2845 repoName := "pull-range" 2846 2847 upload, err := imgStore.NewBlobUpload(repoName) 2848 So(err, ShouldBeNil) 2849 So(upload, ShouldNotBeEmpty) 2850 2851 content := []byte("test-data1") 2852 buf := bytes.NewBuffer(content) 2853 buflen := buf.Len() 2854 bdigest := godigest.FromBytes(content) 2855 2856 blob, err := imgStore.PutBlobChunk(repoName, upload, 0, int64(buflen), buf) 2857 So(err, ShouldBeNil) 2858 So(blob, ShouldEqual, buflen) 2859 2860 err = imgStore.FinishBlobUpload(repoName, upload, buf, bdigest) 2861 So(err, ShouldBeNil) 2862 2863 _, _, _, err = imgStore.GetBlobPartial(repoName, "", "application/octet-stream", 0, 1) 2864 So(err, ShouldNotBeNil) 2865 2866 _, _, _, err = imgStore.GetBlobPartial(repoName, bdigest, "application/octet-stream", 1, 0) 2867 So(err, ShouldNotBeNil) 2868 2869 _, _, _, err = imgStore.GetBlobPartial(repoName, bdigest, "application/octet-stream", 1, 0) 2870 So(err, ShouldNotBeNil) 2871 2872 blobPath := path.Join(imgStore.RootDir(), repoName, "blobs", bdigest.Algorithm().String(), bdigest.Encoded()) 2873 err = os.Chmod(blobPath, 0o000) 2874 So(err, ShouldBeNil) 2875 _, _, _, err = imgStore.GetBlobPartial(repoName, bdigest, "application/octet-stream", -1, 1) 2876 So(err, ShouldNotBeNil) 2877 }) 2878 }) 2879 } 2880 2881 func TestStatIndex(t *testing.T) { 2882 Convey("NewImageStore", t, func() { 2883 dir := t.TempDir() 2884 log := zlog.Logger{Logger: zerolog.New(os.Stdout)} 2885 metrics := monitoring.NewMetricsServer(false, log) 2886 imgStore := local.NewImageStore(dir, true, true, log, metrics, nil, nil) 2887 2888 err := WriteImageToFileSystem(CreateRandomImage(), "repo", "tag", 2889 storage.StoreController{DefaultStore: imgStore}) 2890 So(err, ShouldBeNil) 2891 2892 Convey("StatIndex PathNotFoundError", func() { 2893 _, _, _, err := imgStore.StatIndex("not-found") 2894 So(err, ShouldNotBeNil) 2895 }) 2896 }) 2897 } 2898 2899 func TestStorageDriverErr(t *testing.T) { 2900 dir := t.TempDir() 2901 2902 log := zlog.Logger{Logger: zerolog.New(os.Stdout)} 2903 metrics := monitoring.NewMetricsServer(false, log) 2904 cacheDriver, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{ 2905 RootDir: dir, 2906 Name: "cache", 2907 UseRelPaths: true, 2908 }, log) 2909 2910 imgStore := local.NewImageStore(dir, true, true, log, metrics, nil, cacheDriver) 2911 2912 Convey("Init repo", t, func() { 2913 err := imgStore.InitRepo(repoName) 2914 So(err, ShouldBeNil) 2915 2916 Convey("New blob upload error", func() { 2917 err := os.Chmod(path.Join(imgStore.RootDir(), repoName, storageConstants.BlobUploadDir), 0o000) 2918 So(err, ShouldBeNil) 2919 2920 _, err = imgStore.NewBlobUpload(repoName) 2921 So(err, ShouldNotBeNil) 2922 2923 err = os.Chmod(path.Join(imgStore.RootDir(), repoName, storageConstants.BlobUploadDir), 2924 storageConstants.DefaultDirPerms) 2925 So(err, ShouldBeNil) 2926 2927 uuid, err := imgStore.NewBlobUpload(repoName) 2928 So(err, ShouldBeNil) 2929 2930 size, err := imgStore.GetBlobUpload(repoName, uuid) 2931 So(err, ShouldBeNil) 2932 So(size, ShouldEqual, 0) 2933 2934 content := []byte("test-blob") 2935 buf := bytes.NewBuffer(content) 2936 bufLen := buf.Len() 2937 digest := godigest.FromBytes(content) 2938 2939 size, err = imgStore.PutBlobChunkStreamed(repoName, uuid, buf) 2940 So(err, ShouldBeNil) 2941 So(size, ShouldEqual, bufLen) 2942 2943 size, err = imgStore.GetBlobUpload(repoName, uuid) 2944 So(err, ShouldBeNil) 2945 So(size, ShouldEqual, bufLen) 2946 2947 err = imgStore.DeleteBlobUpload(repoName, uuid) 2948 So(err, ShouldBeNil) 2949 2950 err = imgStore.DeleteBlobUpload(repoName, uuid) 2951 So(err, ShouldNotBeNil) 2952 2953 _, err = imgStore.GetBlobUpload(repoName, uuid) 2954 So(err, ShouldNotBeNil) 2955 2956 // push again 2957 buf = bytes.NewBuffer(content) 2958 2959 uuid, err = imgStore.NewBlobUpload(repoName) 2960 So(err, ShouldBeNil) 2961 2962 size, err = imgStore.PutBlobChunkStreamed(repoName, uuid, buf) 2963 So(err, ShouldBeNil) 2964 So(size, ShouldEqual, bufLen) 2965 2966 // finish blob upload 2967 err = os.Chmod(path.Join(imgStore.BlobUploadPath(repoName, uuid)), 0o000) 2968 So(err, ShouldBeNil) 2969 2970 err = imgStore.FinishBlobUpload(repoName, uuid, &io.LimitedReader{}, digest) 2971 So(err, ShouldNotBeNil) 2972 2973 err = os.Chmod(path.Join(imgStore.BlobUploadPath(repoName, uuid)), storageConstants.DefaultFilePerms) 2974 So(err, ShouldBeNil) 2975 2976 err = imgStore.FinishBlobUpload(repoName, uuid, &io.LimitedReader{}, digest) 2977 So(err, ShouldBeNil) 2978 2979 err = imgStore.FinishBlobUpload(repoName, uuid, &io.LimitedReader{}, digest) 2980 So(err, ShouldNotBeNil) 2981 2982 // delete blob 2983 err = imgStore.DeleteBlob(repoName, digest) 2984 So(err, ShouldBeNil) 2985 2986 err = imgStore.DeleteBlob(repoName, digest) 2987 So(err, ShouldNotBeNil) 2988 }) 2989 }) 2990 } 2991 2992 func NewRandomImgManifest(data []byte, cdigest, ldigest godigest.Digest, cblob, lblob []byte) (*ispec.Manifest, error) { 2993 annotationsMap := make(map[string]string) 2994 2995 key := string(data) 2996 val := string(data) 2997 annotationsMap[key] = val 2998 2999 schemaVersion := 2 3000 3001 manifest := ispec.Manifest{ 3002 MediaType: "application/vnd.oci.image.manifest.v1+json", 3003 Config: ispec.Descriptor{ 3004 MediaType: "application/vnd.oci.image.config.v1+json", 3005 Digest: cdigest, 3006 Size: int64(len(cblob)), 3007 }, 3008 Layers: []ispec.Descriptor{ 3009 { 3010 MediaType: "application/vnd.oci.image.layer.v1.tar", 3011 Digest: ldigest, 3012 Size: int64(len(lblob)), 3013 }, 3014 }, 3015 Annotations: annotationsMap, 3016 Versioned: imeta.Versioned{ 3017 SchemaVersion: schemaVersion, 3018 }, 3019 } 3020 3021 return &manifest, nil 3022 } 3023 3024 func newRandomBlobForFuzz(data []byte) (godigest.Digest, []byte, error) { //nolint:unparam 3025 return godigest.FromBytes(data), data, nil 3026 } 3027 3028 func isKnownErr(err error) bool { 3029 if errors.Is(err, zerr.ErrInvalidRepositoryName) || errors.Is(err, zerr.ErrManifestNotFound) || 3030 errors.Is(err, zerr.ErrRepoNotFound) || 3031 errors.Is(err, zerr.ErrBadManifest) { 3032 return true 3033 } 3034 3035 if err, ok := err.(*fs.PathError); ok && errors.Is(err.Err, syscall.EACCES) || //nolint: errorlint 3036 errors.Is(err.Err, syscall.ENAMETOOLONG) || 3037 errors.Is(err.Err, syscall.EINVAL) || 3038 errors.Is(err.Err, syscall.ENOENT) { 3039 return true 3040 } 3041 3042 return false 3043 }