mirror of
https://github.com/project-zot/zot.git
synced 2026-06-17 21:17:58 +08:00
a5cc8ab810
* feat: support pushing multiple tags for a single manifest See https://github.com/opencontainers/distribution-spec/pull/600 Signed-off-by: Andrei Aaron <andreifdaaron@gmail.com> * fix: constants not replaced in swagger output Also godot mandates comments ending in dots, which produces bad results in the swagger generated files, see the extra ". which is now fixed below: ``` diff --git a/swagger/docs.go b/swagger/docs.go index 84b08277..fb2c45c3 100644 --- a/swagger/docs.go +++ b/swagger/docs.go @@ -114,7 +114,7 @@ const docTemplate = `{ } }, "400": { - "description": "bad request\".", + "description": "bad request", "schema": { "type": "string" } @@ -200,7 +200,7 @@ const docTemplate = `{ } }, "400": { - "description": "bad request\".", + "description": "bad request", "schema": { "type": "string" } diff --git a/swagger/swagger.json b/swagger/swagger.json index cfeb3900..247f95fa 100644 --- a/swagger/swagger.json +++ b/swagger/swagger.json @@ -106,7 +106,7 @@ } }, "400": { - "description": "bad request\".", + "description": "bad request", "schema": { "type": "string" } @@ -192,7 +192,7 @@ } }, "400": { - "description": "bad request\".", + "description": "bad request", "schema": { "type": "string" } diff --git a/swagger/swagger.yaml b/swagger/swagger.yaml index 57641c2f..09b30dcc 100644 --- a/swagger/swagger.yaml +++ b/swagger/swagger.yaml @@ -310,7 +310,7 @@ paths: schema: type: string "400": - description: bad request". + description: bad request schema: type: string "500": @@ -366,7 +366,7 @@ paths: schema: type: string "400": - description: bad request". + description: bad request schema: type: string "500": ``` Signed-off-by: Andrei Aaron <andreifdaaron@gmail.com> --------- Signed-off-by: Andrei Aaron <andreifdaaron@gmail.com>
787 lines
23 KiB
Go
787 lines
23 KiB
Go
package gc
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"os"
|
|
"path"
|
|
"testing"
|
|
"time"
|
|
|
|
godigest "github.com/opencontainers/go-digest"
|
|
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
|
. "github.com/smartystreets/goconvey/convey"
|
|
|
|
zerr "zotregistry.dev/zot/v2/errors"
|
|
"zotregistry.dev/zot/v2/pkg/api/config"
|
|
zcommon "zotregistry.dev/zot/v2/pkg/common"
|
|
"zotregistry.dev/zot/v2/pkg/extensions/monitoring"
|
|
zlog "zotregistry.dev/zot/v2/pkg/log"
|
|
"zotregistry.dev/zot/v2/pkg/meta/types"
|
|
"zotregistry.dev/zot/v2/pkg/storage"
|
|
"zotregistry.dev/zot/v2/pkg/storage/cache"
|
|
common "zotregistry.dev/zot/v2/pkg/storage/common"
|
|
storageConstants "zotregistry.dev/zot/v2/pkg/storage/constants"
|
|
"zotregistry.dev/zot/v2/pkg/storage/local"
|
|
. "zotregistry.dev/zot/v2/pkg/test/image-utils"
|
|
"zotregistry.dev/zot/v2/pkg/test/mocks"
|
|
)
|
|
|
|
var (
|
|
errGC = errors.New("gc error")
|
|
repoName = "test" //nolint: gochecknoglobals
|
|
)
|
|
|
|
func TestGarbageCollectManifestErrors(t *testing.T) {
|
|
Convey("Make imagestore and upload manifest", t, func(c C) {
|
|
dir := t.TempDir()
|
|
|
|
log := zlog.NewTestLogger()
|
|
audit := zlog.NewAuditLogger("debug", "")
|
|
|
|
metrics := monitoring.NewMetricsServer(false, log)
|
|
defer metrics.Stop() // Clean up metrics server to prevent resource leaks
|
|
|
|
cacheDriver, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{
|
|
RootDir: dir,
|
|
Name: "cache",
|
|
UseRelPaths: true,
|
|
}, log)
|
|
imgStore := local.NewImageStore(dir, true, true, log, metrics, nil, cacheDriver, nil, nil)
|
|
|
|
gc := NewGarbageCollect(imgStore, mocks.MetaDBMock{}, Options{
|
|
Delay: storageConstants.DefaultGCDelay,
|
|
ImageRetention: config.ImageRetention{
|
|
Delay: storageConstants.DefaultGCDelay,
|
|
Policies: []config.RetentionPolicy{
|
|
{
|
|
Repositories: []string{"**"},
|
|
DeleteReferrers: true,
|
|
},
|
|
},
|
|
},
|
|
}, audit, log)
|
|
|
|
Convey("trigger missing blob in addImageIndexBlobsToReferences()", func() {
|
|
// GC should continue when blobs are missing (not found), not return an error
|
|
err := gc.addIndexBlobsToReferences(repoName, ispec.Index{
|
|
Manifests: []ispec.Descriptor{
|
|
{
|
|
Digest: godigest.FromString("miss"),
|
|
MediaType: ispec.MediaTypeImageIndex,
|
|
},
|
|
},
|
|
}, map[string]bool{})
|
|
So(err, ShouldBeNil)
|
|
})
|
|
|
|
Convey("trigger missing blob in addImageManifestBlobsToReferences()", func() {
|
|
// GC should continue when blobs are missing (not found), not return an error
|
|
err := gc.addIndexBlobsToReferences(repoName, ispec.Index{
|
|
Manifests: []ispec.Descriptor{
|
|
{
|
|
Digest: godigest.FromString("miss"),
|
|
MediaType: ispec.MediaTypeImageManifest,
|
|
},
|
|
},
|
|
}, map[string]bool{})
|
|
So(err, ShouldBeNil)
|
|
})
|
|
|
|
content := []byte("this is a blob")
|
|
digest := godigest.FromBytes(content)
|
|
So(digest, ShouldNotBeNil)
|
|
|
|
_, blen, err := imgStore.FullBlobUpload(repoName, bytes.NewReader(content), digest)
|
|
So(err, ShouldBeNil)
|
|
So(blen, ShouldEqual, len(content))
|
|
|
|
cblob, cdigest := GetRandomImageConfig()
|
|
_, clen, err := imgStore.FullBlobUpload(repoName, bytes.NewReader(cblob), cdigest)
|
|
So(err, ShouldBeNil)
|
|
So(clen, ShouldEqual, len(cblob))
|
|
|
|
manifest := ispec.Manifest{
|
|
Config: ispec.Descriptor{
|
|
MediaType: ispec.MediaTypeImageConfig,
|
|
Digest: cdigest,
|
|
Size: int64(len(cblob)),
|
|
},
|
|
Layers: []ispec.Descriptor{
|
|
{
|
|
MediaType: ispec.MediaTypeImageLayer,
|
|
Digest: digest,
|
|
Size: int64(len(content)),
|
|
},
|
|
},
|
|
}
|
|
|
|
manifest.SchemaVersion = 2
|
|
|
|
body, err := json.Marshal(manifest)
|
|
So(err, ShouldBeNil)
|
|
|
|
manifestDigest := godigest.FromBytes(body)
|
|
|
|
_, _, err = imgStore.PutImageManifest(repoName, "1.0", ispec.MediaTypeImageManifest, body, nil)
|
|
So(err, ShouldBeNil)
|
|
|
|
Convey("trigger GetIndex error in GetReferencedBlobs", func() {
|
|
index, err := common.GetIndex(imgStore, repoName, log)
|
|
So(err, ShouldBeNil)
|
|
|
|
err = os.Chmod(path.Join(imgStore.RootDir(), repoName), 0o000)
|
|
So(err, ShouldBeNil)
|
|
|
|
defer func() {
|
|
err := os.Chmod(path.Join(imgStore.RootDir(), repoName), 0o755)
|
|
So(err, ShouldBeNil)
|
|
}()
|
|
|
|
// Note: Permission denied from Stat() is converted to ErrBlobNotFound in originalBlobInfo,
|
|
// so we can't distinguish it from missing blobs. GC treats missing blobs gracefully,
|
|
// so permission denied from Stat() will also be treated as missing (return nil).
|
|
// Permission denied from ReadFile() will still return an error.
|
|
err = gc.addIndexBlobsToReferences(repoName, index, map[string]bool{})
|
|
So(err, ShouldBeNil)
|
|
})
|
|
|
|
Convey("trigger GetImageManifest error in AddIndexBlobsToReferences", func() {
|
|
index, err := common.GetIndex(imgStore, repoName, log)
|
|
So(err, ShouldBeNil)
|
|
|
|
err = os.Chmod(path.Join(imgStore.RootDir(), repoName, "blobs", "sha256", manifestDigest.Encoded()), 0o000)
|
|
So(err, ShouldBeNil)
|
|
|
|
defer func() {
|
|
err := os.Chmod(path.Join(imgStore.RootDir(), repoName, "blobs", "sha256", manifestDigest.Encoded()), 0o755)
|
|
So(err, ShouldBeNil)
|
|
}()
|
|
|
|
err = gc.addIndexBlobsToReferences(repoName, index, map[string]bool{})
|
|
So(err, ShouldNotBeNil)
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestGarbageCollectIndexErrors(t *testing.T) {
|
|
Convey("Make imagestore and upload manifest", t, func(c C) {
|
|
dir := t.TempDir()
|
|
|
|
log := zlog.NewTestLogger()
|
|
audit := zlog.NewAuditLogger("debug", "")
|
|
|
|
metrics := monitoring.NewMetricsServer(false, log)
|
|
defer metrics.Stop() // Clean up metrics server to prevent resource leaks
|
|
cacheDriver, _ := storage.Create("boltdb", cache.BoltDBDriverParameters{
|
|
RootDir: dir,
|
|
Name: "cache",
|
|
UseRelPaths: true,
|
|
}, log)
|
|
imgStore := local.NewImageStore(dir, true, true, log, metrics, nil, cacheDriver, nil, nil)
|
|
|
|
gc := NewGarbageCollect(imgStore, mocks.MetaDBMock{}, Options{
|
|
Delay: storageConstants.DefaultGCDelay,
|
|
ImageRetention: config.ImageRetention{
|
|
Delay: storageConstants.DefaultGCDelay,
|
|
Policies: []config.RetentionPolicy{
|
|
{
|
|
Repositories: []string{"**"},
|
|
DeleteReferrers: true,
|
|
},
|
|
},
|
|
},
|
|
}, audit, log)
|
|
|
|
content := []byte("this is a blob")
|
|
bdgst := godigest.FromBytes(content)
|
|
So(bdgst, ShouldNotBeNil)
|
|
|
|
_, bsize, err := imgStore.FullBlobUpload(repoName, bytes.NewReader(content), bdgst)
|
|
So(err, ShouldBeNil)
|
|
So(bsize, ShouldEqual, len(content))
|
|
|
|
var index ispec.Index
|
|
index.SchemaVersion = 2
|
|
index.MediaType = ispec.MediaTypeImageIndex
|
|
|
|
var digest godigest.Digest
|
|
|
|
for i := 0; i < 4; i++ {
|
|
// upload image config blob
|
|
upload, err := imgStore.NewBlobUpload(repoName)
|
|
So(err, ShouldBeNil)
|
|
So(upload, ShouldNotBeEmpty)
|
|
|
|
cblob, cdigest := GetRandomImageConfig()
|
|
buf := bytes.NewBuffer(cblob)
|
|
buflen := buf.Len()
|
|
blob, err := imgStore.PutBlobChunkStreamed(repoName, upload, buf)
|
|
So(err, ShouldBeNil)
|
|
So(blob, ShouldEqual, buflen)
|
|
|
|
err = imgStore.FinishBlobUpload(repoName, upload, buf, cdigest)
|
|
So(err, ShouldBeNil)
|
|
So(blob, ShouldEqual, buflen)
|
|
|
|
// create a manifest
|
|
manifest := ispec.Manifest{
|
|
Config: ispec.Descriptor{
|
|
MediaType: ispec.MediaTypeImageConfig,
|
|
Digest: cdigest,
|
|
Size: int64(len(cblob)),
|
|
},
|
|
Layers: []ispec.Descriptor{
|
|
{
|
|
MediaType: ispec.MediaTypeImageLayer,
|
|
Digest: bdgst,
|
|
Size: bsize,
|
|
},
|
|
},
|
|
}
|
|
manifest.SchemaVersion = 2
|
|
content, err = json.Marshal(manifest)
|
|
So(err, ShouldBeNil)
|
|
|
|
digest = godigest.FromBytes(content)
|
|
So(digest, ShouldNotBeNil)
|
|
_, _, err = imgStore.PutImageManifest(repoName, digest.String(), ispec.MediaTypeImageManifest, content, nil)
|
|
So(err, ShouldBeNil)
|
|
|
|
index.Manifests = append(index.Manifests, ispec.Descriptor{
|
|
Digest: digest,
|
|
MediaType: ispec.MediaTypeImageManifest,
|
|
Size: int64(len(content)),
|
|
})
|
|
}
|
|
|
|
// upload index image
|
|
indexContent, err := json.Marshal(index)
|
|
So(err, ShouldBeNil)
|
|
|
|
indexDigest := godigest.FromBytes(indexContent)
|
|
So(indexDigest, ShouldNotBeNil)
|
|
|
|
_, _, err = imgStore.PutImageManifest(repoName, "1.0", ispec.MediaTypeImageIndex, indexContent, nil)
|
|
So(err, ShouldBeNil)
|
|
|
|
index, err = common.GetIndex(imgStore, repoName, log)
|
|
So(err, ShouldBeNil)
|
|
|
|
err = gc.addIndexBlobsToReferences(repoName, index, map[string]bool{})
|
|
So(err, ShouldBeNil)
|
|
|
|
Convey("trigger GetImageIndex error in GetReferencedBlobsInImageIndex", func() {
|
|
err := os.Chmod(path.Join(imgStore.RootDir(), repoName, "blobs", "sha256", indexDigest.Encoded()), 0o000)
|
|
So(err, ShouldBeNil)
|
|
|
|
defer func() {
|
|
err := os.Chmod(path.Join(imgStore.RootDir(), repoName, "blobs", "sha256", indexDigest.Encoded()), 0o755)
|
|
So(err, ShouldBeNil)
|
|
}()
|
|
|
|
err = gc.addIndexBlobsToReferences(repoName, index, map[string]bool{})
|
|
So(err, ShouldNotBeNil)
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestGarbageCollectWithMockedImageStore(t *testing.T) {
|
|
trueVal := true
|
|
|
|
ctx := context.Background()
|
|
|
|
Convey("Cover gc error paths", t, func(c C) {
|
|
log := zlog.NewTestLogger()
|
|
audit := zlog.NewAuditLogger("debug", "")
|
|
|
|
gcOptions := Options{
|
|
Delay: storageConstants.DefaultGCDelay,
|
|
ImageRetention: config.ImageRetention{
|
|
Delay: storageConstants.DefaultGCDelay,
|
|
Policies: []config.RetentionPolicy{
|
|
{
|
|
Repositories: []string{"**"},
|
|
DeleteReferrers: true,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
Convey("Error on GetIndex in gc.cleanRepo()", func() {
|
|
gc := NewGarbageCollect(mocks.MockedImageStore{}, mocks.MetaDBMock{
|
|
GetRepoMetaFn: func(ctx context.Context, repo string) (types.RepoMeta, error) {
|
|
return types.RepoMeta{}, errGC
|
|
},
|
|
}, gcOptions, audit, log)
|
|
|
|
err := gc.cleanRepo(ctx, repoName)
|
|
So(err, ShouldNotBeNil)
|
|
})
|
|
|
|
Convey("Error on GetIndex in gc.removeUnreferencedBlobs()", func() {
|
|
gc := NewGarbageCollect(mocks.MockedImageStore{}, mocks.MetaDBMock{
|
|
GetRepoMetaFn: func(ctx context.Context, repo string) (types.RepoMeta, error) {
|
|
return types.RepoMeta{}, errGC
|
|
},
|
|
}, gcOptions, audit, log)
|
|
|
|
err := gc.removeUnreferencedBlobs("repo", time.Hour, log)
|
|
So(err, ShouldNotBeNil)
|
|
})
|
|
|
|
Convey("Error on gc.removeManifest()", func() {
|
|
gc := NewGarbageCollect(mocks.MockedImageStore{}, mocks.MetaDBMock{
|
|
GetRepoMetaFn: func(ctx context.Context, repo string) (types.RepoMeta, error) {
|
|
return types.RepoMeta{}, errGC
|
|
},
|
|
}, gcOptions, audit, log)
|
|
|
|
_, err := gc.removeManifest("", &ispec.Index{}, ispec.DescriptorEmptyJSON, "tag", "", "")
|
|
So(err, ShouldNotBeNil)
|
|
})
|
|
|
|
Convey("Error on metaDB in gc.cleanRepo()", func() {
|
|
gcOptions := Options{
|
|
Delay: storageConstants.DefaultGCDelay,
|
|
ImageRetention: config.ImageRetention{
|
|
Delay: storageConstants.DefaultGCDelay,
|
|
Policies: []config.RetentionPolicy{
|
|
{
|
|
Repositories: []string{"**"},
|
|
KeepTags: []config.KeepTagsPolicy{
|
|
{
|
|
Patterns: []string{".*"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
gc := NewGarbageCollect(mocks.MockedImageStore{}, mocks.MetaDBMock{
|
|
GetRepoMetaFn: func(ctx context.Context, repo string) (types.RepoMeta, error) {
|
|
return types.RepoMeta{}, errGC
|
|
},
|
|
}, gcOptions, audit, log)
|
|
|
|
err := gc.removeTagsPerRetentionPolicy(ctx, "name", &ispec.Index{})
|
|
So(err, ShouldNotBeNil)
|
|
})
|
|
|
|
Convey("Error on context done in removeTags...", func() {
|
|
gcOptions := Options{
|
|
Delay: storageConstants.DefaultGCDelay,
|
|
ImageRetention: config.ImageRetention{
|
|
Delay: storageConstants.DefaultGCDelay,
|
|
Policies: []config.RetentionPolicy{
|
|
{
|
|
Repositories: []string{"**"},
|
|
KeepTags: []config.KeepTagsPolicy{
|
|
{
|
|
Patterns: []string{".*"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
gc := NewGarbageCollect(mocks.MockedImageStore{}, mocks.MetaDBMock{}, gcOptions, audit, log)
|
|
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
cancel()
|
|
|
|
err := gc.removeTagsPerRetentionPolicy(ctx, "name", &ispec.Index{
|
|
Manifests: []ispec.Descriptor{
|
|
{
|
|
MediaType: ispec.MediaTypeImageManifest,
|
|
Digest: godigest.FromBytes([]byte("digest")),
|
|
},
|
|
},
|
|
})
|
|
So(err, ShouldNotBeNil)
|
|
})
|
|
|
|
Convey("Error on PutIndexContent in gc.cleanRepo()", func() {
|
|
returnedIndexJSON := ispec.Index{}
|
|
|
|
returnedIndexJSONBuf, err := json.Marshal(returnedIndexJSON)
|
|
So(err, ShouldBeNil)
|
|
|
|
imgStore := mocks.MockedImageStore{
|
|
PutIndexContentFn: func(repo string, index ispec.Index) error {
|
|
return errGC
|
|
},
|
|
GetIndexContentFn: func(repo string) ([]byte, error) {
|
|
return returnedIndexJSONBuf, nil
|
|
},
|
|
}
|
|
|
|
gc := NewGarbageCollect(imgStore, mocks.MetaDBMock{}, gcOptions, audit, log)
|
|
|
|
err = gc.cleanRepo(ctx, repoName)
|
|
So(err, ShouldNotBeNil)
|
|
})
|
|
|
|
Convey("Error on gc.cleanBlobs() in gc.cleanRepo()", func() {
|
|
returnedIndexJSON := ispec.Index{}
|
|
|
|
returnedIndexJSONBuf, err := json.Marshal(returnedIndexJSON)
|
|
So(err, ShouldBeNil)
|
|
|
|
imgStore := mocks.MockedImageStore{
|
|
PutIndexContentFn: func(repo string, index ispec.Index) error {
|
|
return nil
|
|
},
|
|
GetIndexContentFn: func(repo string) ([]byte, error) {
|
|
return returnedIndexJSONBuf, nil
|
|
},
|
|
GetAllBlobsFn: func(repo string) ([]godigest.Digest, error) {
|
|
return []godigest.Digest{}, errGC
|
|
},
|
|
}
|
|
|
|
gc := NewGarbageCollect(imgStore, mocks.MetaDBMock{}, gcOptions, audit, log)
|
|
|
|
err = gc.cleanRepo(ctx, repoName)
|
|
So(err, ShouldNotBeNil)
|
|
})
|
|
|
|
Convey("False on imgStore.DirExists() in gc.cleanRepo()", func() {
|
|
imgStore := mocks.MockedImageStore{
|
|
DirExistsFn: func(d string) bool {
|
|
return false
|
|
},
|
|
}
|
|
|
|
gc := NewGarbageCollect(imgStore, mocks.MetaDBMock{}, gcOptions, audit, log)
|
|
|
|
err := gc.cleanRepo(ctx, repoName)
|
|
So(err, ShouldNotBeNil)
|
|
})
|
|
|
|
Convey("Error on gc.identifyManifestsReferencedInIndex in gc.cleanManifests() with multiarch image", func() {
|
|
indexImageDigest := godigest.FromBytes([]byte("digest"))
|
|
|
|
returnedIndexImage := ispec.Index{
|
|
Subject: &ispec.DescriptorEmptyJSON,
|
|
Manifests: []ispec.Descriptor{
|
|
{
|
|
MediaType: ispec.MediaTypeImageIndex,
|
|
Digest: godigest.FromBytes([]byte("digest2")),
|
|
},
|
|
},
|
|
}
|
|
|
|
returnedIndexImageBuf, err := json.Marshal(returnedIndexImage)
|
|
So(err, ShouldBeNil)
|
|
|
|
imgStore := mocks.MockedImageStore{
|
|
GetBlobContentFn: func(repo string, digest godigest.Digest) ([]byte, error) {
|
|
if digest == indexImageDigest {
|
|
return returnedIndexImageBuf, nil
|
|
} else {
|
|
return nil, errGC
|
|
}
|
|
},
|
|
}
|
|
|
|
gcOptions.ImageRetention = config.ImageRetention{
|
|
Policies: []config.RetentionPolicy{
|
|
{
|
|
Repositories: []string{"**"},
|
|
DeleteUntagged: &trueVal,
|
|
},
|
|
},
|
|
}
|
|
gc := NewGarbageCollect(imgStore, mocks.MetaDBMock{}, gcOptions, audit, log)
|
|
|
|
err = gc.removeManifestsPerRepoPolicy(ctx, repoName, &ispec.Index{
|
|
Manifests: []ispec.Descriptor{
|
|
{
|
|
MediaType: ispec.MediaTypeImageIndex,
|
|
Digest: indexImageDigest,
|
|
},
|
|
},
|
|
})
|
|
So(err, ShouldNotBeNil)
|
|
})
|
|
|
|
Convey("Error on gc.identifyManifestsReferencedInIndex in gc.cleanManifests() with image", func() {
|
|
imgStore := mocks.MockedImageStore{
|
|
GetBlobContentFn: func(repo string, digest godigest.Digest) ([]byte, error) {
|
|
return nil, errGC
|
|
},
|
|
}
|
|
|
|
gcOptions.ImageRetention = config.ImageRetention{
|
|
Policies: []config.RetentionPolicy{
|
|
{
|
|
Repositories: []string{"**"},
|
|
DeleteUntagged: &trueVal,
|
|
},
|
|
},
|
|
}
|
|
|
|
gc := NewGarbageCollect(imgStore, mocks.MetaDBMock{}, gcOptions, audit, log)
|
|
|
|
err := gc.removeManifestsPerRepoPolicy(ctx, repoName, &ispec.Index{
|
|
Manifests: []ispec.Descriptor{
|
|
{
|
|
MediaType: ispec.MediaTypeImageManifest,
|
|
Digest: godigest.FromBytes([]byte("digest")),
|
|
},
|
|
},
|
|
})
|
|
So(err, ShouldNotBeNil)
|
|
})
|
|
|
|
Convey("Error on context done in removeManifests...", func() {
|
|
imgStore := mocks.MockedImageStore{}
|
|
|
|
gcOptions.ImageRetention = config.ImageRetention{
|
|
Policies: []config.RetentionPolicy{
|
|
{
|
|
Repositories: []string{"**"},
|
|
DeleteUntagged: &trueVal,
|
|
},
|
|
},
|
|
}
|
|
|
|
gc := NewGarbageCollect(imgStore, mocks.MetaDBMock{}, gcOptions, audit, log)
|
|
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
cancel()
|
|
|
|
err := gc.removeManifestsPerRepoPolicy(ctx, repoName, &ispec.Index{
|
|
Manifests: []ispec.Descriptor{
|
|
{
|
|
MediaType: ispec.MediaTypeImageManifest,
|
|
Digest: godigest.FromBytes([]byte("digest")),
|
|
},
|
|
},
|
|
})
|
|
So(err, ShouldNotBeNil)
|
|
})
|
|
|
|
Convey("Error on gc.gcManifest() in gc.cleanManifests() with image", func() {
|
|
returnedImage := ispec.Manifest{
|
|
MediaType: ispec.MediaTypeImageManifest,
|
|
}
|
|
|
|
returnedImageBuf, err := json.Marshal(returnedImage)
|
|
So(err, ShouldBeNil)
|
|
|
|
imgStore := mocks.MockedImageStore{
|
|
GetBlobContentFn: func(repo string, digest godigest.Digest) ([]byte, error) {
|
|
return returnedImageBuf, nil
|
|
},
|
|
}
|
|
|
|
metaDB := mocks.MetaDBMock{
|
|
RemoveRepoReferenceFn: func(repo, reference string, manifestDigest godigest.Digest) error {
|
|
return errGC
|
|
},
|
|
}
|
|
|
|
gcOptions.ImageRetention = config.ImageRetention{
|
|
Policies: []config.RetentionPolicy{
|
|
{
|
|
Repositories: []string{"**"},
|
|
DeleteUntagged: &trueVal,
|
|
},
|
|
},
|
|
}
|
|
gc := NewGarbageCollect(imgStore, metaDB, gcOptions, audit, log)
|
|
|
|
err = gc.removeManifestsPerRepoPolicy(ctx, repoName, &ispec.Index{
|
|
Manifests: []ispec.Descriptor{
|
|
{
|
|
MediaType: ispec.MediaTypeImageManifest,
|
|
Digest: godigest.FromBytes([]byte("digest")),
|
|
},
|
|
},
|
|
})
|
|
So(err, ShouldNotBeNil)
|
|
})
|
|
Convey("Error on gc.gcManifest() in gc.cleanManifests() with signature", func() {
|
|
returnedImage := ispec.Manifest{
|
|
MediaType: ispec.MediaTypeImageManifest,
|
|
ArtifactType: zcommon.NotationSignature,
|
|
}
|
|
|
|
returnedImageBuf, err := json.Marshal(returnedImage)
|
|
So(err, ShouldBeNil)
|
|
|
|
imgStore := mocks.MockedImageStore{
|
|
GetBlobContentFn: func(repo string, digest godigest.Digest) ([]byte, error) {
|
|
return returnedImageBuf, nil
|
|
},
|
|
}
|
|
|
|
metaDB := mocks.MetaDBMock{
|
|
DeleteSignatureFn: func(repo string, signedManifestDigest godigest.Digest, sm types.SignatureMetadata) error {
|
|
return errGC
|
|
},
|
|
}
|
|
|
|
gcOptions.ImageRetention = config.ImageRetention{}
|
|
gc := NewGarbageCollect(imgStore, metaDB, gcOptions, audit, log)
|
|
|
|
desc := ispec.Descriptor{
|
|
MediaType: ispec.MediaTypeImageManifest,
|
|
Digest: godigest.FromBytes([]byte("digest")),
|
|
}
|
|
|
|
index := &ispec.Index{
|
|
Manifests: []ispec.Descriptor{desc},
|
|
}
|
|
_, err = gc.removeManifest(repoName, index, desc, desc.Digest.String(), storage.NotationType,
|
|
godigest.FromBytes([]byte("digest2")))
|
|
|
|
So(err, ShouldNotBeNil)
|
|
})
|
|
|
|
Convey("Error on gc.gcReferrer() in gc.cleanManifests() with image index", func() {
|
|
manifestDesc := ispec.Descriptor{
|
|
MediaType: ispec.MediaTypeImageIndex,
|
|
Digest: godigest.FromBytes([]byte("digest")),
|
|
}
|
|
|
|
returnedIndexImage := ispec.Index{
|
|
MediaType: ispec.MediaTypeImageIndex,
|
|
Subject: &ispec.Descriptor{
|
|
Digest: godigest.FromBytes([]byte("digest2")),
|
|
},
|
|
Manifests: []ispec.Descriptor{
|
|
manifestDesc,
|
|
},
|
|
}
|
|
|
|
returnedIndexImageBuf, err := json.Marshal(returnedIndexImage)
|
|
So(err, ShouldBeNil)
|
|
|
|
imgStore := mocks.MockedImageStore{
|
|
GetBlobContentFn: func(repo string, digest godigest.Digest) ([]byte, error) {
|
|
return returnedIndexImageBuf, nil
|
|
},
|
|
StatBlobFn: func(repo string, digest godigest.Digest) (bool, int64, time.Time, error) {
|
|
return false, -1, time.Time{}, errGC
|
|
},
|
|
}
|
|
|
|
gc := NewGarbageCollect(imgStore, mocks.MetaDBMock{}, gcOptions, audit, log)
|
|
|
|
err = gc.removeManifestsPerRepoPolicy(ctx, repoName, &returnedIndexImage)
|
|
So(err, ShouldNotBeNil)
|
|
})
|
|
|
|
Convey("Error on gc.gcReferrer() in gc.cleanManifests() with image", func() {
|
|
manifestDesc := ispec.Descriptor{
|
|
MediaType: ispec.MediaTypeImageManifest,
|
|
Digest: godigest.FromBytes([]byte("digest")),
|
|
}
|
|
|
|
returnedImage := ispec.Manifest{
|
|
Subject: &ispec.Descriptor{
|
|
Digest: godigest.FromBytes([]byte("digest2")),
|
|
},
|
|
MediaType: ispec.MediaTypeImageManifest,
|
|
}
|
|
|
|
returnedImageBuf, err := json.Marshal(returnedImage)
|
|
So(err, ShouldBeNil)
|
|
|
|
imgStore := mocks.MockedImageStore{
|
|
GetBlobContentFn: func(repo string, digest godigest.Digest) ([]byte, error) {
|
|
return returnedImageBuf, nil
|
|
},
|
|
StatBlobFn: func(repo string, digest godigest.Digest) (bool, int64, time.Time, error) {
|
|
return false, -1, time.Time{}, errGC
|
|
},
|
|
}
|
|
|
|
gc := NewGarbageCollect(imgStore, mocks.MetaDBMock{}, gcOptions, audit, log)
|
|
|
|
err = gc.removeManifestsPerRepoPolicy(ctx, repoName, &ispec.Index{
|
|
Manifests: []ispec.Descriptor{
|
|
manifestDesc,
|
|
},
|
|
})
|
|
So(err, ShouldNotBeNil)
|
|
})
|
|
|
|
Convey("Missing nested index blob in removeIndexReferrers is skipped gracefully", func() {
|
|
// Create a top-level index that contains a nested index
|
|
// The nested index blob will be missing
|
|
topLevelIndex := ispec.Index{
|
|
MediaType: ispec.MediaTypeImageIndex,
|
|
Manifests: []ispec.Descriptor{
|
|
{
|
|
MediaType: ispec.MediaTypeImageIndex,
|
|
Digest: godigest.FromString("missing-nested-index"),
|
|
Size: 100,
|
|
},
|
|
},
|
|
}
|
|
|
|
imgStore := mocks.MockedImageStore{
|
|
GetBlobContentFn: func(repo string, digest godigest.Digest) ([]byte, error) {
|
|
// Return ErrBlobNotFound for the missing nested index
|
|
return nil, zerr.ErrBlobNotFound
|
|
},
|
|
}
|
|
|
|
gcOptions.ImageRetention = config.ImageRetention{
|
|
Policies: []config.RetentionPolicy{
|
|
{
|
|
Repositories: []string{"**"},
|
|
DeleteReferrers: true,
|
|
},
|
|
},
|
|
}
|
|
|
|
gc := NewGarbageCollect(imgStore, mocks.MetaDBMock{}, gcOptions, audit, log)
|
|
|
|
// removeIndexReferrers should skip the missing nested index and continue
|
|
gced, err := gc.removeIndexReferrers(repoName, &topLevelIndex, topLevelIndex)
|
|
So(err, ShouldBeNil)
|
|
So(gced, ShouldBeFalse)
|
|
})
|
|
|
|
Convey("Missing nested index blob in identifyManifestsReferencedInIndex is skipped gracefully", func() {
|
|
// Create a top-level index that contains a nested index
|
|
// The nested index blob will be missing
|
|
topLevelIndex := ispec.Index{
|
|
MediaType: ispec.MediaTypeImageIndex,
|
|
Manifests: []ispec.Descriptor{
|
|
{
|
|
MediaType: ispec.MediaTypeImageIndex,
|
|
Digest: godigest.FromString("missing-nested-index"),
|
|
Size: 100,
|
|
},
|
|
},
|
|
}
|
|
|
|
imgStore := mocks.MockedImageStore{
|
|
GetBlobContentFn: func(repo string, digest godigest.Digest) ([]byte, error) {
|
|
// Return ErrBlobNotFound for the missing nested index
|
|
return nil, zerr.ErrBlobNotFound
|
|
},
|
|
}
|
|
|
|
gc := NewGarbageCollect(imgStore, mocks.MetaDBMock{}, gcOptions, audit, log)
|
|
|
|
// identifyManifestsReferencedInIndex should skip the missing nested index and continue
|
|
referenced := make(map[godigest.Digest]bool)
|
|
err := gc.identifyManifestsReferencedInIndex(topLevelIndex, repoName, referenced)
|
|
So(err, ShouldBeNil)
|
|
// No manifests should be marked as referenced since the nested index is missing
|
|
So(len(referenced), ShouldEqual, 0)
|
|
})
|
|
})
|
|
}
|