zotregistry.dev/zot@v1.4.4-0.20240314164342-eec277e14d20/pkg/storage/common/common_test.go (about) 1 package storage_test 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "errors" 7 "os" 8 "testing" 9 10 godigest "github.com/opencontainers/go-digest" 11 ispec "github.com/opencontainers/image-spec/specs-go/v1" 12 "github.com/rs/zerolog" 13 . "github.com/smartystreets/goconvey/convey" 14 15 zerr "zotregistry.dev/zot/errors" 16 "zotregistry.dev/zot/pkg/extensions/monitoring" 17 "zotregistry.dev/zot/pkg/log" 18 "zotregistry.dev/zot/pkg/storage" 19 "zotregistry.dev/zot/pkg/storage/cache" 20 common "zotregistry.dev/zot/pkg/storage/common" 21 "zotregistry.dev/zot/pkg/storage/imagestore" 22 "zotregistry.dev/zot/pkg/storage/local" 23 . "zotregistry.dev/zot/pkg/test/image-utils" 24 "zotregistry.dev/zot/pkg/test/mocks" 25 ) 26 27 var ErrTestError = errors.New("TestError") 28 29 func TestValidateManifest(t *testing.T) { 30 Convey("Make manifest", t, func(c C) { 31 dir := t.TempDir() 32 33 log := log.Logger{Logger: zerolog.New(os.Stdout)} 34 metrics := monitoring.NewMetricsServer(false, log) 35 cacheDriver, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{ 36 RootDir: dir, 37 Name: "cache", 38 UseRelPaths: true, 39 }, log) 40 imgStore := local.NewImageStore(dir, true, true, log, metrics, nil, cacheDriver) 41 42 content := []byte("this is a blob") 43 digest := godigest.FromBytes(content) 44 So(digest, ShouldNotBeNil) 45 46 _, blen, err := imgStore.FullBlobUpload("test", bytes.NewReader(content), digest) 47 So(err, ShouldBeNil) 48 So(blen, ShouldEqual, len(content)) 49 50 cblob, cdigest := GetRandomImageConfig() 51 _, clen, err := imgStore.FullBlobUpload("test", bytes.NewReader(cblob), cdigest) 52 So(err, ShouldBeNil) 53 So(clen, ShouldEqual, len(cblob)) 54 55 Convey("bad manifest schema version", func() { 56 manifest := ispec.Manifest{ 57 Config: ispec.Descriptor{ 58 MediaType: ispec.MediaTypeImageConfig, 59 Digest: cdigest, 60 Size: int64(len(cblob)), 61 }, 62 Layers: []ispec.Descriptor{ 63 { 64 MediaType: ispec.MediaTypeImageLayer, 65 Digest: digest, 66 Size: int64(len(content)), 67 }, 68 }, 69 } 70 71 manifest.SchemaVersion = 999 72 73 body, err := json.Marshal(manifest) 74 So(err, ShouldBeNil) 75 76 _, _, err = imgStore.PutImageManifest("test", "1.0", ispec.MediaTypeImageManifest, body) 77 So(err, ShouldNotBeNil) 78 var internalErr *zerr.Error 79 So(errors.As(err, &internalErr), ShouldBeTrue) 80 So(internalErr.GetDetails(), ShouldContainKey, "jsonSchemaValidation") 81 So(internalErr.GetDetails()["jsonSchemaValidation"], ShouldEqual, "[schemaVersion: Must be less than or equal to 2]") 82 }) 83 84 Convey("bad config blob", func() { 85 manifest := ispec.Manifest{ 86 Config: ispec.Descriptor{ 87 MediaType: ispec.MediaTypeImageConfig, 88 Digest: cdigest, 89 Size: int64(len(cblob)), 90 }, 91 Layers: []ispec.Descriptor{ 92 { 93 MediaType: ispec.MediaTypeImageLayer, 94 Digest: digest, 95 Size: int64(len(content)), 96 }, 97 }, 98 } 99 100 manifest.SchemaVersion = 2 101 102 configBlobPath := imgStore.BlobPath("test", cdigest) 103 104 err := os.WriteFile(configBlobPath, []byte("bad config blob"), 0o000) 105 So(err, ShouldBeNil) 106 107 body, err := json.Marshal(manifest) 108 So(err, ShouldBeNil) 109 110 // this was actually an umoci error on config blob 111 _, _, err = imgStore.PutImageManifest("test", "1.0", ispec.MediaTypeImageManifest, body) 112 So(err, ShouldBeNil) 113 }) 114 115 Convey("manifest with non-distributable layers", func() { 116 content := []byte("this blob doesn't exist") 117 digest := godigest.FromBytes(content) 118 So(digest, ShouldNotBeNil) 119 120 manifest := ispec.Manifest{ 121 Config: ispec.Descriptor{ 122 MediaType: ispec.MediaTypeImageConfig, 123 Digest: cdigest, 124 Size: int64(len(cblob)), 125 }, 126 Layers: []ispec.Descriptor{ 127 { 128 MediaType: ispec.MediaTypeImageLayerNonDistributable, //nolint:staticcheck 129 Digest: digest, 130 Size: int64(len(content)), 131 }, 132 }, 133 } 134 135 manifest.SchemaVersion = 2 136 137 body, err := json.Marshal(manifest) 138 So(err, ShouldBeNil) 139 140 _, _, err = imgStore.PutImageManifest("test", "1.0", ispec.MediaTypeImageManifest, body) 141 So(err, ShouldBeNil) 142 }) 143 }) 144 } 145 146 func TestGetReferrersErrors(t *testing.T) { 147 Convey("make storage", t, func(c C) { 148 dir := t.TempDir() 149 150 log := log.Logger{Logger: zerolog.New(os.Stdout)} 151 metrics := monitoring.NewMetricsServer(false, log) 152 cacheDriver, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{ 153 RootDir: dir, 154 Name: "cache", 155 UseRelPaths: true, 156 }, log) 157 158 imgStore := local.NewImageStore(dir, false, true, log, metrics, nil, cacheDriver) 159 160 artifactType := "application/vnd.example.icecream.v1" 161 validDigest := godigest.FromBytes([]byte("blob")) 162 163 Convey("Trigger invalid digest error", func(c C) { 164 _, err := common.GetReferrers(imgStore, "zot-test", "invalidDigest", 165 []string{artifactType}, log) 166 So(err, ShouldNotBeNil) 167 }) 168 169 Convey("Trigger repo not found error", func(c C) { 170 _, err := common.GetReferrers(imgStore, "zot-test", validDigest, 171 []string{artifactType}, log) 172 So(err, ShouldNotBeNil) 173 }) 174 175 storageCtlr := storage.StoreController{DefaultStore: imgStore} 176 err := WriteImageToFileSystem(CreateDefaultImage(), "zot-test", "0.0.1", storageCtlr) 177 So(err, ShouldBeNil) 178 179 digest := godigest.FromBytes([]byte("{}")) 180 181 index := ispec.Index{ 182 Manifests: []ispec.Descriptor{ 183 { 184 MediaType: "application/vnd.example.invalid.v1", 185 Digest: digest, 186 }, 187 }, 188 } 189 190 indexBuf, err := json.Marshal(index) 191 So(err, ShouldBeNil) 192 193 Convey("Trigger GetBlobContent() not found", func(c C) { 194 imgStore = &mocks.MockedImageStore{ 195 GetIndexContentFn: func(repo string) ([]byte, error) { 196 return indexBuf, nil 197 }, 198 GetBlobContentFn: func(repo string, digest godigest.Digest) ([]byte, error) { 199 return []byte{}, zerr.ErrBlobNotFound 200 }, 201 } 202 203 _, err = common.GetReferrers(imgStore, "zot-test", validDigest, 204 []string{artifactType}, log) 205 So(err, ShouldNotBeNil) 206 }) 207 208 Convey("Trigger GetBlobContent() generic error", func(c C) { 209 imgStore = &mocks.MockedImageStore{ 210 GetIndexContentFn: func(repo string) ([]byte, error) { 211 return indexBuf, nil 212 }, 213 GetBlobContentFn: func(repo string, digest godigest.Digest) ([]byte, error) { 214 return []byte{}, zerr.ErrBadBlob 215 }, 216 } 217 218 _, err = common.GetReferrers(imgStore, "zot-test", validDigest, 219 []string{artifactType}, log) 220 So(err, ShouldNotBeNil) 221 }) 222 223 Convey("Trigger unmarshal error on manifest image mediaType", func(c C) { 224 index = ispec.Index{ 225 Manifests: []ispec.Descriptor{ 226 { 227 MediaType: ispec.MediaTypeImageManifest, 228 Digest: digest, 229 }, 230 }, 231 } 232 233 indexBuf, err = json.Marshal(index) 234 So(err, ShouldBeNil) 235 236 imgStore = &mocks.MockedImageStore{ 237 GetIndexContentFn: func(repo string) ([]byte, error) { 238 return indexBuf, nil 239 }, 240 GetBlobContentFn: func(repo string, digest godigest.Digest) ([]byte, error) { 241 return []byte{}, nil 242 }, 243 } 244 245 _, err = common.GetReferrers(imgStore, "zot-test", validDigest, 246 []string{artifactType}, log) 247 So(err, ShouldNotBeNil) 248 }) 249 250 Convey("Trigger nil subject", func(c C) { 251 index = ispec.Index{ 252 Manifests: []ispec.Descriptor{ 253 { 254 MediaType: ispec.MediaTypeImageManifest, 255 Digest: digest, 256 }, 257 }, 258 } 259 260 indexBuf, err = json.Marshal(index) 261 So(err, ShouldBeNil) 262 263 ociManifest := ispec.Manifest{ 264 Subject: nil, 265 } 266 267 ociManifestBuf, err := json.Marshal(ociManifest) 268 So(err, ShouldBeNil) 269 270 imgStore = &mocks.MockedImageStore{ 271 GetIndexContentFn: func(repo string) ([]byte, error) { 272 return indexBuf, nil 273 }, 274 GetBlobContentFn: func(repo string, digest godigest.Digest) ([]byte, error) { 275 return ociManifestBuf, nil 276 }, 277 } 278 279 _, err = common.GetReferrers(imgStore, "zot-test", validDigest, 280 []string{artifactType}, log) 281 So(err, ShouldBeNil) 282 }) 283 284 Convey("Index bad blob", func() { 285 imgStore = &mocks.MockedImageStore{ 286 GetIndexContentFn: func(repo string) ([]byte, error) { 287 return []byte(`{ 288 "manifests": [{ 289 "digest": "digest", 290 "mediaType": "application/vnd.oci.image.index.v1+json" 291 }] 292 }`), nil 293 }, 294 GetBlobContentFn: func(repo string, digest godigest.Digest) ([]byte, error) { 295 return []byte("bad blob"), nil 296 }, 297 } 298 299 _, err = common.GetReferrers(imgStore, "zot-test", validDigest, 300 []string{}, log) 301 So(err, ShouldNotBeNil) 302 }) 303 304 Convey("Index bad artifac type", func() { 305 imgStore = &mocks.MockedImageStore{ 306 GetIndexContentFn: func(repo string) ([]byte, error) { 307 return []byte(`{ 308 "manifests": [{ 309 "digest": "digest", 310 "mediaType": "application/vnd.oci.image.index.v1+json" 311 }] 312 }`), nil 313 }, 314 GetBlobContentFn: func(repo string, digest godigest.Digest) ([]byte, error) { 315 return []byte(`{ 316 "subject": {"digest": "` + validDigest.String() + `"} 317 }`), nil 318 }, 319 } 320 321 ref, err := common.GetReferrers(imgStore, "zot-test", validDigest, 322 []string{"art.type"}, log) 323 So(err, ShouldBeNil) 324 So(len(ref.Manifests), ShouldEqual, 0) 325 }) 326 }) 327 } 328 329 func TestGetImageIndexErrors(t *testing.T) { 330 log := log.Logger{Logger: zerolog.New(os.Stdout)} 331 332 Convey("Trigger invalid digest error", t, func(c C) { 333 imgStore := &mocks.MockedImageStore{} 334 335 _, err := common.GetImageIndex(imgStore, "zot-test", "invalidDigest", log) 336 So(err, ShouldNotBeNil) 337 }) 338 339 Convey("Trigger GetBlobContent error", t, func(c C) { 340 imgStore := &mocks.MockedImageStore{ 341 GetBlobContentFn: func(repo string, digest godigest.Digest) ([]byte, error) { 342 return []byte{}, zerr.ErrBlobNotFound 343 }, 344 } 345 346 validDigest := godigest.FromBytes([]byte("blob")) 347 348 _, err := common.GetImageIndex(imgStore, "zot-test", validDigest, log) 349 So(err, ShouldNotBeNil) 350 }) 351 352 Convey("Trigger unmarshal error", t, func(c C) { 353 imgStore := &mocks.MockedImageStore{ 354 GetBlobContentFn: func(repo string, digest godigest.Digest) ([]byte, error) { 355 return []byte{}, nil 356 }, 357 } 358 359 validDigest := godigest.FromBytes([]byte("blob")) 360 361 _, err := common.GetImageIndex(imgStore, "zot-test", validDigest, log) 362 So(err, ShouldNotBeNil) 363 }) 364 } 365 366 func TestGetBlobDescriptorFromRepo(t *testing.T) { 367 log := log.Logger{Logger: zerolog.New(os.Stdout)} 368 metrics := monitoring.NewMetricsServer(false, log) 369 370 tdir := t.TempDir() 371 cacheDriver, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{ 372 RootDir: tdir, 373 Name: "cache", 374 UseRelPaths: true, 375 }, log) 376 377 driver := local.New(true) 378 imgStore := imagestore.NewImageStore(tdir, tdir, true, 379 true, log, metrics, nil, driver, cacheDriver) 380 381 repoName := "zot-test" 382 383 Convey("Test error paths", t, func() { 384 storeController := storage.StoreController{DefaultStore: imgStore} 385 386 image := CreateRandomMultiarch() 387 388 tag := "index" 389 390 err := WriteMultiArchImageToFileSystem(image, repoName, tag, storeController) 391 So(err, ShouldBeNil) 392 393 blob := image.Images[0].Layers[0] 394 blobDigest := godigest.FromBytes(blob) 395 blobSize := len(blob) 396 397 desc, err := common.GetBlobDescriptorFromIndex(imgStore, ispec.Index{Manifests: []ispec.Descriptor{ 398 { 399 Digest: image.Digest(), 400 MediaType: ispec.MediaTypeImageIndex, 401 }, 402 }}, repoName, blobDigest, log) 403 So(err, ShouldBeNil) 404 So(desc.Digest, ShouldEqual, blobDigest) 405 So(desc.Size, ShouldEqual, blobSize) 406 407 desc, err = common.GetBlobDescriptorFromRepo(imgStore, repoName, blobDigest, log) 408 So(err, ShouldBeNil) 409 So(desc.Digest, ShouldEqual, blobDigest) 410 So(desc.Size, ShouldEqual, blobSize) 411 412 indexBlobPath := imgStore.BlobPath(repoName, image.Digest()) 413 err = os.Chmod(indexBlobPath, 0o000) 414 So(err, ShouldBeNil) 415 416 defer func() { 417 err = os.Chmod(indexBlobPath, 0o644) 418 So(err, ShouldBeNil) 419 }() 420 421 _, err = common.GetBlobDescriptorFromIndex(imgStore, ispec.Index{Manifests: []ispec.Descriptor{ 422 { 423 Digest: image.Digest(), 424 MediaType: ispec.MediaTypeImageIndex, 425 }, 426 }}, repoName, blobDigest, log) 427 So(err, ShouldNotBeNil) 428 429 manifestDigest := image.Images[0].Digest() 430 manifestBlobPath := imgStore.BlobPath(repoName, manifestDigest) 431 err = os.Chmod(manifestBlobPath, 0o000) 432 So(err, ShouldBeNil) 433 434 defer func() { 435 err = os.Chmod(manifestBlobPath, 0o644) 436 So(err, ShouldBeNil) 437 }() 438 439 _, err = common.GetBlobDescriptorFromRepo(imgStore, repoName, blobDigest, log) 440 So(err, ShouldNotBeNil) 441 442 _, err = common.GetBlobDescriptorFromRepo(imgStore, repoName, manifestDigest, log) 443 So(err, ShouldBeNil) 444 }) 445 } 446 447 func TestIsSignature(t *testing.T) { 448 Convey("Unknown media type", t, func(c C) { 449 isSingature := common.IsSignature(ispec.Descriptor{ 450 MediaType: "unknown media type", 451 }) 452 So(isSingature, ShouldBeFalse) 453 }) 454 } 455 456 func TestDedupeGeneratorErrors(t *testing.T) { 457 log := log.Logger{Logger: zerolog.New(os.Stdout)} 458 459 // Ideally this would be covered by the end-to-end test, 460 // but the coverage for the error is unpredictable, prone to race conditions 461 Convey("GetNextDigestWithBlobPaths errors", t, func(c C) { 462 imgStore := &mocks.MockedImageStore{ 463 GetRepositoriesFn: func() ([]string, error) { 464 return []string{"repo1", "repo2"}, nil 465 }, 466 GetNextDigestWithBlobPathsFn: func(repos []string, lastDigests []godigest.Digest) ( 467 godigest.Digest, []string, error, 468 ) { 469 return "sha256:123", []string{}, ErrTestError 470 }, 471 } 472 473 generator := &common.DedupeTaskGenerator{ 474 ImgStore: imgStore, 475 Dedupe: true, 476 Log: log, 477 } 478 479 task, err := generator.Next() 480 So(err, ShouldNotBeNil) 481 So(task, ShouldBeNil) 482 }) 483 }