mirror of
https://github.com/project-zot/zot.git
synced 2026-06-17 12:58:02 +08:00
fix(storage): deleting manifests with identical digests (#951)
Suppose we push two identical manifests (sharing same digest) but with different tags, then deleting by digest should throw an error otherwise we end up deleting all image tags (with gc) or dangling references (without gc) This behaviour is controlled via Authorization, added a new policy action named detectManifestsCollision which enables this behaviour Signed-off-by: Ramkumar Chinchani <rchincha@cisco.com> Signed-off-by: Petu Eusebiu <peusebiu@cisco.com> Co-authored-by: Ramkumar Chinchani <rchincha@cisco.com>
This commit is contained in:
+15
-8
@@ -178,7 +178,7 @@ func GetAndValidateRequestDigest(body []byte, digestStr string, log zerolog.Logg
|
||||
}
|
||||
|
||||
/*
|
||||
CheckIfIndexNeedsUpdate verifies if an index needs to be updated given a new manifest descriptor.
|
||||
CheckIfIndexNeedsUpdate verifies if an index needs to be updated given a new manifest descriptor.
|
||||
|
||||
Returns whether or not index needs update, in the latter case it will also return the previous digest.
|
||||
*/
|
||||
@@ -272,11 +272,14 @@ func GetIndex(imgStore ImageStore, repo string, log zerolog.Logger) (ispec.Index
|
||||
return index, nil
|
||||
}
|
||||
|
||||
func RemoveManifestDescByReference(index *ispec.Index, reference string) (ispec.Descriptor, bool) {
|
||||
func RemoveManifestDescByReference(index *ispec.Index, reference string, detectCollisions bool,
|
||||
) (ispec.Descriptor, bool, error) {
|
||||
var removedManifest ispec.Descriptor
|
||||
|
||||
var found bool
|
||||
|
||||
foundCount := 0
|
||||
|
||||
var outIndex ispec.Index
|
||||
|
||||
for _, manifest := range index.Manifests {
|
||||
@@ -284,11 +287,13 @@ func RemoveManifestDescByReference(index *ispec.Index, reference string) (ispec.
|
||||
if ok && tag == reference {
|
||||
removedManifest = manifest
|
||||
found = true
|
||||
foundCount++
|
||||
|
||||
continue
|
||||
} else if reference == manifest.Digest.String() {
|
||||
removedManifest = manifest
|
||||
found = true
|
||||
foundCount++
|
||||
|
||||
continue
|
||||
}
|
||||
@@ -296,14 +301,17 @@ func RemoveManifestDescByReference(index *ispec.Index, reference string) (ispec.
|
||||
outIndex.Manifests = append(outIndex.Manifests, manifest)
|
||||
}
|
||||
|
||||
if foundCount > 1 && detectCollisions {
|
||||
return ispec.Descriptor{}, false, zerr.ErrManifestConflict
|
||||
}
|
||||
|
||||
index.Manifests = outIndex.Manifests
|
||||
|
||||
return removedManifest, found
|
||||
return removedManifest, found, nil
|
||||
}
|
||||
|
||||
/*
|
||||
additionally, unmarshal an image index and for all manifests in that
|
||||
|
||||
Unmarshal an image index and for all manifests in that
|
||||
index, ensure that they do not have a name or they are not in other
|
||||
manifest indexes else GC can never clean them.
|
||||
*/
|
||||
@@ -333,13 +341,12 @@ func UpdateIndexWithPrunedImageManifests(imgStore ImageStore, index *ispec.Index
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
before an image index manifest is pushed to a repo, its constituent manifests
|
||||
Before an image index manifest is pushed to a repo, its constituent manifests
|
||||
are pushed first, so when updating/removing this image index manifest, we also
|
||||
need to determine if there are other image index manifests which refer to the
|
||||
same constitutent manifests so that they can be garbage-collected correctly
|
||||
|
||||
pruneImageManifestsFromIndex is a helper routine to achieve this.
|
||||
PruneImageManifestsFromIndex is a helper routine to achieve this.
|
||||
*/
|
||||
func PruneImageManifestsFromIndex(imgStore ImageStore, repo string, digest godigest.Digest, //nolint:gocyclo
|
||||
outIndex ispec.Index, otherImgIndexes []ispec.Descriptor, log zerolog.Logger,
|
||||
|
||||
@@ -549,7 +549,7 @@ func (is *ImageStoreLocal) PutImageManifest(repo, reference, mediaType string, /
|
||||
}
|
||||
|
||||
// DeleteImageManifest deletes the image manifest from the repository.
|
||||
func (is *ImageStoreLocal) DeleteImageManifest(repo, reference string) error {
|
||||
func (is *ImageStoreLocal) DeleteImageManifest(repo, reference string, detectCollision bool) error {
|
||||
var lockLatency time.Time
|
||||
|
||||
dir := path.Join(is.rootDir, repo)
|
||||
@@ -562,7 +562,11 @@ func (is *ImageStoreLocal) DeleteImageManifest(repo, reference string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
manifestDesc, found := storage.RemoveManifestDescByReference(&index, reference)
|
||||
manifestDesc, found, err := storage.RemoveManifestDescByReference(&index, reference, detectCollision)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !found {
|
||||
return zerr.ErrManifestNotFound
|
||||
}
|
||||
|
||||
@@ -164,7 +164,7 @@ func TestStorageFSAPIs(t *testing.T) {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = imgStore.DeleteImageManifest(repoName, digest.String())
|
||||
err = imgStore.DeleteImageManifest(repoName, digest.String(), false)
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
err = os.RemoveAll(path.Join(imgStore.RootDir(), repoName))
|
||||
@@ -440,7 +440,7 @@ func FuzzTestPutDeleteImageManifest(f *testing.F) {
|
||||
t.Errorf("the error that occurred is %v \n", err)
|
||||
}
|
||||
|
||||
err = imgStore.DeleteImageManifest(repoName, mdigest.String())
|
||||
err = imgStore.DeleteImageManifest(repoName, mdigest.String(), false)
|
||||
if err != nil {
|
||||
if isKnownErr(err) {
|
||||
return
|
||||
@@ -470,7 +470,7 @@ func FuzzTestDeleteImageManifest(f *testing.F) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = imgStore.DeleteImageManifest(string(data), digest.String())
|
||||
err = imgStore.DeleteImageManifest(string(data), digest.String(), false)
|
||||
if err != nil {
|
||||
if errors.Is(err, zerr.ErrRepoNotFound) || isKnownErr(err) {
|
||||
return
|
||||
@@ -1822,7 +1822,7 @@ func TestGarbageCollect(t *testing.T) {
|
||||
So(err, ShouldBeNil)
|
||||
So(hasBlob, ShouldEqual, true)
|
||||
|
||||
err = imgStore.DeleteImageManifest(repoName, digest.String())
|
||||
err = imgStore.DeleteImageManifest(repoName, digest.String(), false)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
hasBlob, _, err = imgStore.CheckBlob(repoName, bdigest)
|
||||
@@ -1922,7 +1922,7 @@ func TestGarbageCollect(t *testing.T) {
|
||||
// sleep so orphan blob can be GC'ed
|
||||
time.Sleep(5 * time.Second)
|
||||
|
||||
err = imgStore.DeleteImageManifest(repoName, digest.String())
|
||||
err = imgStore.DeleteImageManifest(repoName, digest.String(), false)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
hasBlob, _, err = imgStore.CheckBlob(repoName, bdigest)
|
||||
|
||||
@@ -442,7 +442,7 @@ func (is *ObjectStorage) PutImageManifest(repo, reference, mediaType string, //n
|
||||
}
|
||||
|
||||
// DeleteImageManifest deletes the image manifest from the repository.
|
||||
func (is *ObjectStorage) DeleteImageManifest(repo, reference string) error {
|
||||
func (is *ObjectStorage) DeleteImageManifest(repo, reference string, detectCollisions bool) error {
|
||||
var lockLatency time.Time
|
||||
|
||||
dir := path.Join(is.rootDir, repo)
|
||||
@@ -455,7 +455,11 @@ func (is *ObjectStorage) DeleteImageManifest(repo, reference string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
manifestDesc, found := storage.RemoveManifestDescByReference(&index, reference)
|
||||
manifestDesc, found, err := storage.RemoveManifestDescByReference(&index, reference, detectCollisions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !found {
|
||||
return zerr.ErrManifestNotFound
|
||||
}
|
||||
|
||||
+10
-10
@@ -783,7 +783,7 @@ func TestNegativeCasesObjectsStorage(t *testing.T) {
|
||||
err = imgStore.DeleteBlobUpload(testImage, upload)
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
err = imgStore.DeleteImageManifest(testImage, "1.0")
|
||||
err = imgStore.DeleteImageManifest(testImage, "1.0", false)
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
_, err = imgStore.PutImageManifest(testImage, "1.0", "application/json", []byte{})
|
||||
@@ -887,13 +887,13 @@ func TestNegativeCasesObjectsStorage(t *testing.T) {
|
||||
return []byte{}, errS3
|
||||
},
|
||||
})
|
||||
err := imgStore.DeleteImageManifest(testImage, "1.0")
|
||||
err := imgStore.DeleteImageManifest(testImage, "1.0", false)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("Test DeleteImageManifest2", func(c C) {
|
||||
imgStore = createMockStorage(testDir, tdir, false, &StorageDriverMock{})
|
||||
err := imgStore.DeleteImageManifest(testImage, "1.0")
|
||||
err := imgStore.DeleteImageManifest(testImage, "1.0", false)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
@@ -1891,12 +1891,12 @@ func TestS3ManifestImageIndex(t *testing.T) {
|
||||
|
||||
Convey("Deleting an image index", func() {
|
||||
// delete manifest by tag should pass
|
||||
err := imgStore.DeleteImageManifest("index", "test:index3")
|
||||
err := imgStore.DeleteImageManifest("index", "test:index3", false)
|
||||
So(err, ShouldNotBeNil)
|
||||
_, _, _, err = imgStore.GetImageManifest("index", "test:index3")
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
err = imgStore.DeleteImageManifest("index", "test:index1")
|
||||
err = imgStore.DeleteImageManifest("index", "test:index1", false)
|
||||
So(err, ShouldBeNil)
|
||||
_, _, _, err = imgStore.GetImageManifest("index", "test:index1")
|
||||
So(err, ShouldNotBeNil)
|
||||
@@ -1907,12 +1907,12 @@ func TestS3ManifestImageIndex(t *testing.T) {
|
||||
|
||||
Convey("Deleting an image index by digest", func() {
|
||||
// delete manifest by tag should pass
|
||||
err := imgStore.DeleteImageManifest("index", "test:index3")
|
||||
err := imgStore.DeleteImageManifest("index", "test:index3", false)
|
||||
So(err, ShouldNotBeNil)
|
||||
_, _, _, err = imgStore.GetImageManifest("index", "test:index3")
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
err = imgStore.DeleteImageManifest("index", index1dgst.String())
|
||||
err = imgStore.DeleteImageManifest("index", index1dgst.String(), false)
|
||||
So(err, ShouldBeNil)
|
||||
_, _, _, err = imgStore.GetImageManifest("index", "test:index1")
|
||||
So(err, ShouldNotBeNil)
|
||||
@@ -1983,7 +1983,7 @@ func TestS3ManifestImageIndex(t *testing.T) {
|
||||
_, _, _, err = imgStore.GetImageManifest("index", "test:index1")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = imgStore.DeleteImageManifest("index", "test:index1")
|
||||
err = imgStore.DeleteImageManifest("index", "test:index1", false)
|
||||
So(err, ShouldBeNil)
|
||||
_, _, _, err = imgStore.GetImageManifest("index", "test:index1")
|
||||
So(err, ShouldNotBeNil)
|
||||
@@ -1994,7 +1994,7 @@ func TestS3ManifestImageIndex(t *testing.T) {
|
||||
cleanupStorage(storeDriver, path.Join(testDir, "index", "blobs",
|
||||
index1dgst.Algorithm().String(), index1dgst.Encoded()))
|
||||
|
||||
err = imgStore.DeleteImageManifest("index", index1dgst.String())
|
||||
err = imgStore.DeleteImageManifest("index", index1dgst.String(), false)
|
||||
So(err, ShouldNotBeNil)
|
||||
_, _, _, err = imgStore.GetImageManifest("index", "test:index1")
|
||||
So(err, ShouldNotBeNil)
|
||||
@@ -2009,7 +2009,7 @@ func TestS3ManifestImageIndex(t *testing.T) {
|
||||
_, err = wrtr.Write([]byte("deadbeef"))
|
||||
So(err, ShouldBeNil)
|
||||
wrtr.Close()
|
||||
err = imgStore.DeleteImageManifest("index", index1dgst.String())
|
||||
err = imgStore.DeleteImageManifest("index", index1dgst.String(), false)
|
||||
So(err, ShouldBeNil)
|
||||
_, _, _, err = imgStore.GetImageManifest("index", "test:index1")
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
@@ -30,7 +30,7 @@ type ImageStore interface { //nolint:interfacebloat
|
||||
GetImageTags(repo string) ([]string, error)
|
||||
GetImageManifest(repo, reference string) ([]byte, godigest.Digest, string, error)
|
||||
PutImageManifest(repo, reference, mediaType string, body []byte) (godigest.Digest, error)
|
||||
DeleteImageManifest(repo, reference string) error
|
||||
DeleteImageManifest(repo, reference string, detectCollision bool) error
|
||||
BlobUploadPath(repo, uuid string) string
|
||||
NewBlobUpload(repo string) (string, error)
|
||||
GetBlobUpload(repo, uuid string) (int64, error)
|
||||
|
||||
@@ -356,7 +356,7 @@ func TestStorageAPIs(t *testing.T) {
|
||||
_, _, _, err = imgStore.GetImageManifest("test", "3.0")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = imgStore.DeleteImageManifest("test", "1.0")
|
||||
err = imgStore.DeleteImageManifest("test", "1.0", false)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
tags, err = imgStore.GetImageTags("test")
|
||||
@@ -368,8 +368,12 @@ func TestStorageAPIs(t *testing.T) {
|
||||
So(err, ShouldBeNil)
|
||||
So(hasBlob, ShouldEqual, true)
|
||||
|
||||
// with detectManifestCollision should get error
|
||||
err = imgStore.DeleteImageManifest("test", digest.String(), true)
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
// If we pass reference all manifest with input reference should be deleted.
|
||||
err = imgStore.DeleteImageManifest("test", digest.String())
|
||||
err = imgStore.DeleteImageManifest("test", digest.String(), false)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
tags, err = imgStore.GetImageTags("test")
|
||||
@@ -541,13 +545,13 @@ func TestStorageAPIs(t *testing.T) {
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(len(index.Manifests), ShouldEqual, 1)
|
||||
err = imgStore.DeleteImageManifest("test", "1.0")
|
||||
err = imgStore.DeleteImageManifest("test", "1.0", false)
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
err = imgStore.DeleteImageManifest("inexistent", "1.0")
|
||||
err = imgStore.DeleteImageManifest("inexistent", "1.0", false)
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
err = imgStore.DeleteImageManifest("test", digest.String())
|
||||
err = imgStore.DeleteImageManifest("test", digest.String(), false)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_, _, _, err = imgStore.GetImageManifest("test", digest.String())
|
||||
|
||||
Reference in New Issue
Block a user