diff --git a/pkg/api/config/config.go b/pkg/api/config/config.go index 46f6acfe..003469ca 100644 --- a/pkg/api/config/config.go +++ b/pkg/api/config/config.go @@ -289,7 +289,9 @@ func New() *Config { GC: true, GCDelay: storageConstants.DefaultGCDelay, GCInterval: storageConstants.DefaultGCInterval, - Retention: ImageRetention{}, + Retention: ImageRetention{ + Delay: storageConstants.DefaultRetentionDelay, + }, }, }, HTTP: HTTPConfig{Address: "127.0.0.1", Port: "8080", Auth: &AuthConfig{FailDelay: 0}}, diff --git a/pkg/cli/server/root.go b/pkg/cli/server/root.go index 1fc674c0..2ce32f3c 100644 --- a/pkg/cli/server/root.go +++ b/pkg/cli/server/root.go @@ -648,6 +648,7 @@ func applyDefaultValues(config *config.Config, viperInstance *viper.Viper, log z } } + // set default values in case GC is disabled if !config.Storage.GC { if viperInstance.Get("storage::gcdelay") == nil { config.Storage.GCDelay = 0 diff --git a/pkg/retention/retention.go b/pkg/retention/retention.go index fe9e0e24..510f19ba 100644 --- a/pkg/retention/retention.go +++ b/pkg/retention/retention.go @@ -54,8 +54,8 @@ func (p policyManager) HasDeleteUntagged(repo string) bool { return true } - // default - return false + // by default zot deletes untagged manifests if the config does not contain retention settings and gc is enabled + return true } func (p policyManager) HasDeleteReferrer(repo string) bool { diff --git a/pkg/storage/gc/gc_test.go b/pkg/storage/gc/gc_test.go index 3e3ffcfb..6a0866aa 100644 --- a/pkg/storage/gc/gc_test.go +++ b/pkg/storage/gc/gc_test.go @@ -1108,7 +1108,7 @@ func TestGarbageCollectAndRetentionMetaDB(t *testing.T) { So(err, ShouldBeNil) _, _, _, err = imgStore.GetImageManifest("gc-test1", gcUntagged1.DigestStr()) - So(err, ShouldBeNil) + So(err, ShouldNotBeNil) _, _, _, err = imgStore.GetImageManifest("gc-test1", ref1.DigestStr()) So(err, ShouldBeNil) @@ -1319,6 +1319,7 @@ func TestGarbageCollectDeletion(t *testing.T) { metrics := monitoring.NewMetricsServer(false, log) trueVal := true + falseVal := false // Create temporary directory rootDir := t.TempDir() @@ -1536,6 +1537,80 @@ func TestGarbageCollectDeletion(t *testing.T) { So(err, ShouldNotBeNil) }) + Convey("do not gc untagged manifests after deleting the tag of the top index", func() { + gc := gc.NewGarbageCollect(imgStore, metaDB, gc.Options{ + Delay: 1 * time.Millisecond, + ImageRetention: config.ImageRetention{ + Delay: 1 * time.Millisecond, + Policies: []config.RetentionPolicy{ + { + Repositories: []string{"**"}, + DeleteReferrers: true, + DeleteUntagged: &falseVal, + KeepTags: []config.KeepTagsPolicy{}, + }, + }, + }, + }, audit, log) + + err = deleteTagInStorage(rootDir, repoName, "topindex") + + err = gc.CleanRepo(ctx, repoName) + So(err, ShouldBeNil) + + // manifest1, bottomIndex1 and topIndex are untagged, so manifest1 should not be deleted + tags, err := readTagsFromStorage(rootDir, repoName, manifest1Digest) + So(err, ShouldBeNil) + So(tags, ShouldContain, "") + So(len(tags), ShouldEqual, 1) + + _, err = os.Stat(path.Join(blobsDir, manifest1Digest.Algorithm().String(), manifest1Digest.Encoded())) + So(err, ShouldBeNil) + + // manifest2 is has a tag, so it should not be deleted + tags, err = readTagsFromStorage(rootDir, repoName, manifest2Digest) + So(err, ShouldBeNil) + So(tags, ShouldContain, "manifest2") + So(len(tags), ShouldEqual, 1) + + _, err = os.Stat(path.Join(blobsDir, manifest2Digest.Algorithm().String(), manifest2Digest.Encoded())) + So(err, ShouldBeNil) + + // manifest3 is referenced by tagged bottomIndex2, so it should not be deleted + tags, err = readTagsFromStorage(rootDir, repoName, manifest3Digest) + So(err, ShouldBeNil) + So(tags, ShouldContain, "") + So(len(tags), ShouldEqual, 1) + + _, err = os.Stat(path.Join(blobsDir, manifest3Digest.Algorithm().String(), manifest3Digest.Encoded())) + So(err, ShouldBeNil) + + // bottomIndex1 and topIndex are untagged, so bottomIndex1 should not be deleted + _, err = readTagsFromStorage(rootDir, repoName, bottomIndex1Digest) + So(err, ShouldBeNil) + + _, err = os.Stat(path.Join(blobsDir, bottomIndex1Digest.Algorithm().String(), bottomIndex1Digest.Encoded())) + So(err, ShouldBeNil) + + // bottomIndex2 is has a tag, so it should not be deleted + tags, err = readTagsFromStorage(rootDir, repoName, bottomIndex2Digest) + So(err, ShouldBeNil) + So(tags, ShouldContain, "bottomIndex2") + So(len(tags), ShouldEqual, 1) + + _, err = os.Stat(path.Join(blobsDir, bottomIndex2Digest.Algorithm().String(), bottomIndex2Digest.Encoded())) + So(err, ShouldBeNil) + + // topIndex is untagged, so it should not be deleted + tags, err = readTagsFromStorage(rootDir, repoName, rootIndexDigest) + So(err, ShouldBeNil) + So(tags, ShouldContain, "") + So(len(tags), ShouldEqual, 1) + + _, err = os.Stat(path.Join(blobsDir, rootIndexDigest.Algorithm().String(), rootIndexDigest.Encoded())) + So(err, ShouldBeNil) + }) + Convey("gc unmatching tags", func() { gc := gc.NewGarbageCollect(imgStore, metaDB, gc.Options{ Delay: 1 * time.Millisecond, @@ -2414,7 +2489,7 @@ func TestGarbageCollectAndRetentionNoMetaDB(t *testing.T) { So(err, ShouldBeNil) _, _, _, err = imgStore.GetImageManifest("gc-test1", gcUntagged1.DigestStr()) - So(err, ShouldBeNil) + So(err, ShouldNotBeNil) _, _, _, err = imgStore.GetImageManifest("gc-test1", ref1.DigestStr()) So(err, ShouldBeNil)