0
Fork 0
mirror of https://github.com/project-zot/zot.git synced 2025-01-20 22:52:51 -05:00
zot/pkg/storage/gc/gc_test.go
Ramkumar Chinchani cb2af94b0b
feat: add support for docker images (#2714)
* feat: add support for docker images

Issue #724

A new config section under "HTTP" called "Compat" is added which
currently takes a list of possible compatible legacy media-types.

https://github.com/opencontainers/image-spec/blob/main/media-types.md#compatibility-matrix

Only "docker2s2" (Docker Manifest V2 Schema V2) is currently supported.

Garbage collection also needs to be made aware of non-OCI compatible
layer types.
feat: add cve support for non-OCI compatible layer types

Signed-off-by: Ramkumar Chinchani <rchincha@cisco.com>

* 

Signed-off-by: Ramkumar Chinchani <rchincha@cisco.com>

* test: add more docker compat tests

Signed-off-by: Ramkumar Chinchani <rchincha@cisco.com>

* feat: add additional validation checks for non-OCI images

Signed-off-by: Ramkumar Chinchani <rchincha@cisco.com>

* ci: make "full" images docker-compatible

Signed-off-by: Ramkumar Chinchani <rchincha@cisco.com>

---------

Signed-off-by: Ramkumar Chinchani <rchincha@cisco.com>
2024-10-31 09:44:04 +02:00

1457 lines
46 KiB
Go

package gc_test
import (
"bytes"
"context"
"encoding/json"
"fmt"
"os"
"path"
"testing"
"time"
"github.com/distribution/distribution/v3/registry/storage/driver/factory"
_ "github.com/distribution/distribution/v3/registry/storage/driver/s3-aws"
guuid "github.com/gofrs/uuid"
godigest "github.com/opencontainers/go-digest"
"github.com/opencontainers/image-spec/specs-go"
ispec "github.com/opencontainers/image-spec/specs-go/v1"
. "github.com/smartystreets/goconvey/convey"
"gopkg.in/resty.v1"
"zotregistry.dev/zot/pkg/api/config"
"zotregistry.dev/zot/pkg/extensions/monitoring"
zlog "zotregistry.dev/zot/pkg/log"
"zotregistry.dev/zot/pkg/meta"
"zotregistry.dev/zot/pkg/meta/boltdb"
"zotregistry.dev/zot/pkg/meta/dynamodb"
mTypes "zotregistry.dev/zot/pkg/meta/types"
"zotregistry.dev/zot/pkg/storage"
storageConstants "zotregistry.dev/zot/pkg/storage/constants"
"zotregistry.dev/zot/pkg/storage/gc"
"zotregistry.dev/zot/pkg/storage/local"
"zotregistry.dev/zot/pkg/storage/s3"
storageTypes "zotregistry.dev/zot/pkg/storage/types"
. "zotregistry.dev/zot/pkg/test/image-utils"
tskip "zotregistry.dev/zot/pkg/test/skip"
)
const (
region = "us-east-2"
s3TestName = "S3APIs"
localTestName = "LocalAPIs"
)
//nolint:gochecknoglobals
var testCases = []struct {
testCaseName string
storageType string
}{
{
testCaseName: s3TestName,
storageType: storageConstants.S3StorageDriverName,
},
{
testCaseName: localTestName,
storageType: storageConstants.LocalStorageDriverName,
},
}
func TestGarbageCollectAndRetention(t *testing.T) {
log := zlog.NewLogger("debug", "")
audit := zlog.NewAuditLogger("debug", "/dev/null")
metrics := monitoring.NewMetricsServer(false, log)
trueVal := true
for _, testcase := range testCases {
testcase := testcase
t.Run(testcase.testCaseName, func(t *testing.T) {
var imgStore storageTypes.ImageStore
var metaDB mTypes.MetaDB
if testcase.storageType == storageConstants.S3StorageDriverName {
tskip.SkipDynamo(t)
tskip.SkipS3(t)
uuid, err := guuid.NewV4()
if err != nil {
panic(err)
}
rootDir := path.Join("/oci-repo-test", uuid.String())
cacheDir := t.TempDir()
bucket := "zot-storage-test"
storageDriverParams := map[string]interface{}{
"rootDir": rootDir,
"name": "s3",
"region": region,
"bucket": bucket,
"regionendpoint": os.Getenv("S3MOCK_ENDPOINT"),
"accesskey": "minioadmin",
"secretkey": "minioadmin",
"secure": false,
"skipverify": false,
"forcepathstyle": true,
}
storeName := fmt.Sprintf("%v", storageDriverParams["name"])
store, err := factory.Create(context.Background(), storeName, storageDriverParams)
if err != nil {
panic(err)
}
defer store.Delete(context.Background(), rootDir) //nolint: errcheck
// create bucket if it doesn't exists
_, err = resty.R().Put("http://" + os.Getenv("S3MOCK_ENDPOINT") + "/" + bucket)
if err != nil {
panic(err)
}
uuid, err = guuid.NewV4()
if err != nil {
panic(err)
}
params := dynamodb.DBDriverParameters{ //nolint:contextcheck
Endpoint: os.Getenv("DYNAMODBMOCK_ENDPOINT"),
Region: region,
RepoMetaTablename: "repo" + uuid.String(),
RepoBlobsInfoTablename: "repoblobsinfo" + uuid.String(),
ImageMetaTablename: "imagemeta" + uuid.String(),
UserDataTablename: "user" + uuid.String(),
APIKeyTablename: "apiKey" + uuid.String(),
VersionTablename: "version" + uuid.String(),
}
client, err := dynamodb.GetDynamoClient(params)
if err != nil {
panic(err)
}
metaDB, err = dynamodb.New(client, params, log)
if err != nil {
panic(err)
}
imgStore = s3.NewImageStore(rootDir, cacheDir, true, false, log, metrics, nil, store, nil, nil)
} else {
// Create temporary directory
rootDir := t.TempDir()
// Create ImageStore
imgStore = local.NewImageStore(rootDir, false, false, log, metrics, nil, nil, nil)
// init metaDB
params := boltdb.DBParameters{
RootDir: rootDir,
}
boltDriver, err := boltdb.GetBoltDriver(params)
if err != nil {
panic(err)
}
metaDB, err = boltdb.New(boltDriver, log)
if err != nil {
panic(err)
}
}
storeController := storage.StoreController{}
storeController.DefaultStore = imgStore
ctx := context.Background()
Convey("setup gc images", t, func() {
// for gc testing
// basic images
gcTest1 := CreateRandomImage()
err := WriteImageToFileSystem(gcTest1, "gc-test1", "0.0.1", storeController)
So(err, ShouldBeNil)
// also add same image(same digest) with another tag
err = WriteImageToFileSystem(gcTest1, "gc-test1", "0.0.2", storeController)
So(err, ShouldBeNil)
gcTest2 := CreateRandomImage()
err = WriteImageToFileSystem(gcTest2, "gc-test2", "0.0.1", storeController)
So(err, ShouldBeNil)
gcTest3 := CreateRandomImage()
err = WriteImageToFileSystem(gcTest3, "gc-test3", "0.0.1", storeController)
So(err, ShouldBeNil)
gcTest4 := CreateRandomMultiarch()
err = WriteMultiArchImageToFileSystem(gcTest4, "gc-test4", "0.0.1", storeController)
So(err, ShouldBeNil)
// referrers
ref1 := CreateRandomImageWith().Subject(gcTest1.DescriptorRef()).Build()
err = WriteImageToFileSystem(ref1, "gc-test1", ref1.DigestStr(), storeController)
So(err, ShouldBeNil)
ref2 := CreateRandomImageWith().Subject(gcTest2.DescriptorRef()).Build()
err = WriteImageToFileSystem(ref2, "gc-test2", ref2.DigestStr(), storeController)
So(err, ShouldBeNil)
ref3 := CreateRandomImageWith().Subject(gcTest3.DescriptorRef()).Build()
err = WriteImageToFileSystem(ref3, "gc-test3", ref3.DigestStr(), storeController)
So(err, ShouldBeNil)
ref4 := CreateMultiarchWith().RandomImages(3).Subject(gcTest4.DescriptorRef()).Build()
err = WriteMultiArchImageToFileSystem(ref4, "gc-test4", ref4.DigestStr(), storeController)
So(err, ShouldBeNil)
// referrers pointing to referrers
refOfRef1 := CreateRandomImageWith().Subject(ref1.DescriptorRef()).Build()
err = WriteImageToFileSystem(refOfRef1, "gc-test1", refOfRef1.DigestStr(), storeController)
So(err, ShouldBeNil)
refOfRef2 := CreateRandomImageWith().Subject(ref2.DescriptorRef()).Build()
err = WriteImageToFileSystem(refOfRef2, "gc-test2", refOfRef2.DigestStr(), storeController)
So(err, ShouldBeNil)
refOfRef3 := CreateRandomImageWith().Subject(ref3.DescriptorRef()).Build()
err = WriteImageToFileSystem(refOfRef3, "gc-test3", refOfRef3.DigestStr(), storeController)
So(err, ShouldBeNil)
refOfRef4 := CreateMultiarchWith().RandomImages(3).Subject(ref4.DescriptorRef()).Build()
err = WriteMultiArchImageToFileSystem(refOfRef4, "gc-test4", refOfRef4.DigestStr(), storeController)
So(err, ShouldBeNil)
// untagged images
gcUntagged1 := CreateRandomImage()
err = WriteImageToFileSystem(gcUntagged1, "gc-test1", gcUntagged1.DigestStr(), storeController)
So(err, ShouldBeNil)
gcUntagged2 := CreateRandomImage()
err = WriteImageToFileSystem(gcUntagged2, "gc-test2", gcUntagged2.DigestStr(), storeController)
So(err, ShouldBeNil)
gcUntagged3 := CreateRandomImage()
err = WriteImageToFileSystem(gcUntagged3, "gc-test3", gcUntagged3.DigestStr(), storeController)
So(err, ShouldBeNil)
gcUntagged4 := CreateRandomMultiarch()
err = WriteMultiArchImageToFileSystem(gcUntagged4, "gc-test4", gcUntagged4.DigestStr(), storeController)
So(err, ShouldBeNil)
// for image retention testing
// old images
gcOld1 := CreateRandomImage()
err = WriteImageToFileSystem(gcOld1, "retention", "0.0.1", storeController)
So(err, ShouldBeNil)
gcOld2 := CreateRandomImage()
err = WriteImageToFileSystem(gcOld2, "retention", "0.0.2", storeController)
So(err, ShouldBeNil)
gcOld3 := CreateRandomImage()
err = WriteImageToFileSystem(gcOld3, "retention", "0.0.3", storeController)
So(err, ShouldBeNil)
gcOld4 := CreateRandomMultiarch()
err = WriteMultiArchImageToFileSystem(gcOld4, "retention", "0.0.7", storeController)
So(err, ShouldBeNil)
// new images
gcNew1 := CreateRandomImage()
err = WriteImageToFileSystem(gcNew1, "retention", "0.0.4", storeController)
So(err, ShouldBeNil)
gcNew2 := CreateRandomImage()
err = WriteImageToFileSystem(gcNew2, "retention", "0.0.5", storeController)
So(err, ShouldBeNil)
gcNew3 := CreateRandomImage()
err = WriteImageToFileSystem(gcNew3, "retention", "0.0.6", storeController)
So(err, ShouldBeNil)
gcNew4 := CreateRandomMultiarch()
err = WriteMultiArchImageToFileSystem(gcNew4, "retention", "0.0.8", storeController)
So(err, ShouldBeNil)
err = meta.ParseStorage(metaDB, storeController, log) //nolint: contextcheck
So(err, ShouldBeNil)
retentionMeta, err := metaDB.GetRepoMeta(ctx, "retention")
So(err, ShouldBeNil)
// update timestamps for image retention
gcOld1Stats := retentionMeta.Statistics[gcOld1.DigestStr()]
gcOld1Stats.PushTimestamp = time.Now().Add(-10 * 24 * time.Hour)
gcOld1Stats.LastPullTimestamp = time.Now().Add(-10 * 24 * time.Hour)
gcOld2Stats := retentionMeta.Statistics[gcOld2.DigestStr()]
gcOld2Stats.PushTimestamp = time.Now().Add(-11 * 24 * time.Hour)
gcOld2Stats.LastPullTimestamp = time.Now().Add(-11 * 24 * time.Hour)
gcOld3Stats := retentionMeta.Statistics[gcOld3.DigestStr()]
gcOld3Stats.PushTimestamp = time.Now().Add(-12 * 24 * time.Hour)
gcOld3Stats.LastPullTimestamp = time.Now().Add(-12 * 24 * time.Hour)
gcOld4Stats := retentionMeta.Statistics[gcOld4.DigestStr()]
gcOld4Stats.PushTimestamp = time.Now().Add(-13 * 24 * time.Hour)
gcOld4Stats.LastPullTimestamp = time.Now().Add(-13 * 24 * time.Hour)
gcNew1Stats := retentionMeta.Statistics[gcNew1.DigestStr()]
gcNew1Stats.PushTimestamp = time.Now().Add(-1 * 24 * time.Hour)
gcNew1Stats.LastPullTimestamp = time.Now().Add(-1 * 24 * time.Hour)
gcNew2Stats := retentionMeta.Statistics[gcNew2.DigestStr()]
gcNew2Stats.PushTimestamp = time.Now().Add(-2 * 24 * time.Hour)
gcNew2Stats.LastPullTimestamp = time.Now().Add(-2 * 24 * time.Hour)
gcNew3Stats := retentionMeta.Statistics[gcNew3.DigestStr()]
gcNew3Stats.PushTimestamp = time.Now().Add(-3 * 24 * time.Hour)
gcNew3Stats.LastPullTimestamp = time.Now().Add(-2 * 24 * time.Hour)
gcNew4Stats := retentionMeta.Statistics[gcNew4.DigestStr()]
gcNew4Stats.PushTimestamp = time.Now().Add(-4 * 24 * time.Hour)
gcNew4Stats.LastPullTimestamp = time.Now().Add(-4 * 24 * time.Hour)
retentionMeta.Statistics[gcOld1.DigestStr()] = gcOld1Stats
retentionMeta.Statistics[gcOld2.DigestStr()] = gcOld2Stats
retentionMeta.Statistics[gcOld3.DigestStr()] = gcOld3Stats
retentionMeta.Statistics[gcOld4.DigestStr()] = gcOld4Stats
retentionMeta.Statistics[gcNew1.DigestStr()] = gcNew1Stats
retentionMeta.Statistics[gcNew2.DigestStr()] = gcNew2Stats
retentionMeta.Statistics[gcNew3.DigestStr()] = gcNew3Stats
retentionMeta.Statistics[gcNew4.DigestStr()] = gcNew4Stats
// update repo meta
err = metaDB.SetRepoMeta("retention", retentionMeta)
So(err, ShouldBeNil)
Convey("should not gc anything", func() {
gc := gc.NewGarbageCollect(imgStore, metaDB, gc.Options{
Delay: storageConstants.DefaultGCDelay,
ImageRetention: config.ImageRetention{
Delay: storageConstants.DefaultRetentionDelay,
Policies: []config.RetentionPolicy{
{
Repositories: []string{"**"},
DeleteReferrers: true,
DeleteUntagged: &trueVal,
KeepTags: []config.KeepTagsPolicy{
{},
},
},
},
},
}, audit, log)
err := gc.CleanRepo(ctx, "gc-test1")
So(err, ShouldBeNil)
err = gc.CleanRepo(ctx, "gc-test2")
So(err, ShouldBeNil)
err = gc.CleanRepo(ctx, "gc-test3")
So(err, ShouldBeNil)
err = gc.CleanRepo(ctx, "gc-test4")
So(err, ShouldBeNil)
err = gc.CleanRepo(ctx, "retention")
So(err, ShouldBeNil)
_, _, _, err = imgStore.GetImageManifest("gc-test1", gcTest1.DigestStr())
So(err, ShouldBeNil)
_, _, _, err = imgStore.GetImageManifest("gc-test1", gcUntagged1.DigestStr())
So(err, ShouldBeNil)
_, _, _, err = imgStore.GetImageManifest("gc-test1", ref1.DigestStr())
So(err, ShouldBeNil)
_, _, _, err = imgStore.GetImageManifest("gc-test1", refOfRef1.DigestStr())
So(err, ShouldBeNil)
_, _, _, err = imgStore.GetImageManifest("gc-test2", gcTest2.DigestStr())
So(err, ShouldBeNil)
_, _, _, err = imgStore.GetImageManifest("gc-test2", gcUntagged2.DigestStr())
So(err, ShouldBeNil)
_, _, _, err = imgStore.GetImageManifest("gc-test2", ref2.DigestStr())
So(err, ShouldBeNil)
_, _, _, err = imgStore.GetImageManifest("gc-test2", refOfRef2.DigestStr())
So(err, ShouldBeNil)
_, _, _, err = imgStore.GetImageManifest("gc-test3", gcTest3.DigestStr())
So(err, ShouldBeNil)
_, _, _, err = imgStore.GetImageManifest("gc-test3", gcUntagged3.DigestStr())
So(err, ShouldBeNil)
_, _, _, err = imgStore.GetImageManifest("gc-test3", ref3.DigestStr())
So(err, ShouldBeNil)
_, _, _, err = imgStore.GetImageManifest("gc-test3", refOfRef3.DigestStr())
So(err, ShouldBeNil)
_, _, _, err = imgStore.GetImageManifest("gc-test4", gcTest4.DigestStr())
So(err, ShouldBeNil)
_, _, _, err = imgStore.GetImageManifest("gc-test4", gcUntagged4.DigestStr())
So(err, ShouldBeNil)
_, _, _, err = imgStore.GetImageManifest("gc-test4", ref4.DigestStr())
So(err, ShouldBeNil)
_, _, _, err = imgStore.GetImageManifest("gc-test4", refOfRef4.DigestStr())
So(err, ShouldBeNil)
_, _, _, err = imgStore.GetImageManifest("retention", "0.0.1")
So(err, ShouldBeNil)
_, _, _, err = imgStore.GetImageManifest("retention", "0.0.2")
So(err, ShouldBeNil)
_, _, _, err = imgStore.GetImageManifest("retention", "0.0.3")
So(err, ShouldBeNil)
_, _, _, err = imgStore.GetImageManifest("retention", "0.0.4")
So(err, ShouldBeNil)
_, _, _, err = imgStore.GetImageManifest("retention", "0.0.5")
So(err, ShouldBeNil)
_, _, _, err = imgStore.GetImageManifest("retention", "0.0.6")
So(err, ShouldBeNil)
_, _, _, err = imgStore.GetImageManifest("retention", "0.0.7")
So(err, ShouldBeNil)
_, _, _, err = imgStore.GetImageManifest("retention", "0.0.8")
So(err, ShouldBeNil)
})
Convey("gc untagged manifests", func() {
gc := gc.NewGarbageCollect(imgStore, metaDB, gc.Options{
Delay: storageConstants.DefaultGCDelay,
ImageRetention: config.ImageRetention{
Delay: 1 * time.Millisecond,
Policies: []config.RetentionPolicy{
{
Repositories: []string{"**"},
DeleteReferrers: true,
DeleteUntagged: &trueVal,
KeepTags: []config.KeepTagsPolicy{},
},
},
},
}, audit, log)
err := gc.CleanRepo(ctx, "gc-test1")
So(err, ShouldBeNil)
err = gc.CleanRepo(ctx, "gc-test2")
So(err, ShouldBeNil)
err = gc.CleanRepo(ctx, "gc-test3")
So(err, ShouldBeNil)
err = gc.CleanRepo(ctx, "gc-test4")
So(err, ShouldBeNil)
err = gc.CleanRepo(ctx, "retention")
So(err, ShouldBeNil)
_, _, _, err = imgStore.GetImageManifest("gc-test1", gcTest1.DigestStr())
So(err, ShouldBeNil)
_, _, _, err = imgStore.GetImageManifest("gc-test1", gcUntagged1.DigestStr())
So(err, ShouldNotBeNil)
_, _, _, err = imgStore.GetImageManifest("gc-test1", ref1.DigestStr())
So(err, ShouldBeNil)
_, _, _, err = imgStore.GetImageManifest("gc-test1", refOfRef1.DigestStr())
So(err, ShouldBeNil)
_, _, _, err = imgStore.GetImageManifest("gc-test2", gcTest2.DigestStr())
So(err, ShouldBeNil)
_, _, _, err = imgStore.GetImageManifest("gc-test2", gcUntagged2.DigestStr())
So(err, ShouldNotBeNil)
_, _, _, err = imgStore.GetImageManifest("gc-test2", ref2.DigestStr())
So(err, ShouldBeNil)
_, _, _, err = imgStore.GetImageManifest("gc-test2", refOfRef2.DigestStr())
So(err, ShouldBeNil)
_, _, _, err = imgStore.GetImageManifest("gc-test3", gcTest3.DigestStr())
So(err, ShouldBeNil)
_, _, _, err = imgStore.GetImageManifest("gc-test3", gcUntagged3.DigestStr())
So(err, ShouldNotBeNil)
_, _, _, err = imgStore.GetImageManifest("gc-test3", ref3.DigestStr())
So(err, ShouldBeNil)
_, _, _, err = imgStore.GetImageManifest("gc-test3", refOfRef3.DigestStr())
So(err, ShouldBeNil)
_, _, _, err = imgStore.GetImageManifest("gc-test4", gcTest4.DigestStr())
So(err, ShouldBeNil)
_, _, _, err = imgStore.GetImageManifest("gc-test4", gcUntagged4.DigestStr())
So(err, ShouldNotBeNil)
_, _, _, err = imgStore.GetImageManifest("gc-test4", ref4.DigestStr())
So(err, ShouldBeNil)
_, _, _, err = imgStore.GetImageManifest("gc-test4", refOfRef4.DigestStr())
So(err, ShouldBeNil)
})
Convey("gc all tags, untagged, and afterwards referrers", func() {
gc := gc.NewGarbageCollect(imgStore, metaDB, gc.Options{
Delay: 1 * time.Millisecond,
ImageRetention: config.ImageRetention{
Delay: 1 * time.Millisecond,
Policies: []config.RetentionPolicy{
{
Repositories: []string{"gc-test1"},
DeleteReferrers: true,
DeleteUntagged: &trueVal,
KeepTags: []config.KeepTagsPolicy{
{
Patterns: []string{"v1"}, // should not match any tag
},
},
},
},
},
}, audit, log)
err := gc.CleanRepo(ctx, "gc-test1")
So(err, ShouldBeNil)
_, _, _, err = imgStore.GetImageManifest("gc-test1", gcUntagged1.DigestStr())
So(err, ShouldNotBeNil)
// although we have two tags both should be deleted
_, _, _, err = imgStore.GetImageManifest("gc-test1", gcTest1.DigestStr())
So(err, ShouldNotBeNil)
_, _, _, err = imgStore.GetImageManifest("gc-test1", ref1.DigestStr())
So(err, ShouldNotBeNil)
_, _, _, err = imgStore.GetImageManifest("gc-test1", refOfRef1.DigestStr())
So(err, ShouldNotBeNil)
// now repo should get gc'ed
repos, err := imgStore.GetRepositories()
So(err, ShouldBeNil)
So(repos, ShouldNotContain, "gc-test1")
So(repos, ShouldContain, "gc-test2")
So(repos, ShouldContain, "gc-test3")
So(repos, ShouldContain, "gc-test4")
So(repos, ShouldContain, "retention")
})
Convey("gc with dry-run all tags, untagged, and afterwards referrers", func() {
gc := gc.NewGarbageCollect(imgStore, metaDB, gc.Options{
Delay: 1 * time.Millisecond,
ImageRetention: config.ImageRetention{
Delay: 1 * time.Millisecond,
DryRun: true,
Policies: []config.RetentionPolicy{
{
Repositories: []string{"gc-test1"},
DeleteReferrers: true,
DeleteUntagged: &trueVal,
KeepTags: []config.KeepTagsPolicy{
{
Patterns: []string{"v1"}, // should not match any tag
},
},
},
},
},
}, audit, log)
err := gc.CleanRepo(ctx, "gc-test1")
So(err, ShouldBeNil)
_, _, _, err = imgStore.GetImageManifest("gc-test1", gcUntagged1.DigestStr())
So(err, ShouldBeNil)
_, _, _, err = imgStore.GetImageManifest("gc-test1", ref1.DigestStr())
So(err, ShouldBeNil)
_, _, _, err = imgStore.GetImageManifest("gc-test1", refOfRef1.DigestStr())
So(err, ShouldBeNil)
// now repo should not be gc'ed
repos, err := imgStore.GetRepositories()
So(err, ShouldBeNil)
So(repos, ShouldContain, "gc-test1")
So(repos, ShouldContain, "gc-test2")
So(repos, ShouldContain, "gc-test3")
So(repos, ShouldContain, "gc-test4")
So(repos, ShouldContain, "retention")
tags, err := imgStore.GetImageTags("gc-test1")
So(err, ShouldBeNil)
So(tags, ShouldContain, "0.0.1")
So(tags, ShouldContain, "0.0.2")
})
Convey("all tags matches for retention", func() {
gc := gc.NewGarbageCollect(imgStore, metaDB, gc.Options{
Delay: storageConstants.DefaultGCDelay,
ImageRetention: config.ImageRetention{
Delay: storageConstants.DefaultRetentionDelay,
Policies: []config.RetentionPolicy{
{
Repositories: []string{"**"},
DeleteReferrers: true,
DeleteUntagged: &trueVal,
KeepTags: []config.KeepTagsPolicy{
{
Patterns: []string{"0.0.*"},
},
},
},
},
},
}, audit, log)
err = gc.CleanRepo(ctx, "retention")
So(err, ShouldBeNil)
_, _, _, err = imgStore.GetImageManifest("gc-test1", "0.0.1")
So(err, ShouldBeNil)
_, _, _, err = imgStore.GetImageManifest("gc-test1", "0.0.2")
So(err, ShouldBeNil)
_, _, _, err = imgStore.GetImageManifest("gc-test2", "0.0.1")
So(err, ShouldBeNil)
_, _, _, err = imgStore.GetImageManifest("gc-test3", "0.0.1")
So(err, ShouldBeNil)
_, _, _, err = imgStore.GetImageManifest("retention", "0.0.1")
So(err, ShouldBeNil)
_, _, _, err = imgStore.GetImageManifest("retention", "0.0.2")
So(err, ShouldBeNil)
_, _, _, err = imgStore.GetImageManifest("retention", "0.0.3")
So(err, ShouldBeNil)
_, _, _, err = imgStore.GetImageManifest("retention", "0.0.4")
So(err, ShouldBeNil)
_, _, _, err = imgStore.GetImageManifest("retention", "0.0.5")
So(err, ShouldBeNil)
_, _, _, err = imgStore.GetImageManifest("retention", "0.0.6")
So(err, ShouldBeNil)
_, _, _, err = imgStore.GetImageManifest("retention", "0.0.7")
So(err, ShouldBeNil)
_, _, _, err = imgStore.GetImageManifest("retention", "0.0.8")
So(err, ShouldBeNil)
})
Convey("retain new tags", func() {
sevenDays := 7 * 24 * time.Hour
gc := gc.NewGarbageCollect(imgStore, metaDB, gc.Options{
Delay: storageConstants.DefaultGCDelay,
ImageRetention: config.ImageRetention{
Delay: storageConstants.DefaultRetentionDelay,
Policies: []config.RetentionPolicy{
{
Repositories: []string{"**"},
DeleteReferrers: true,
DeleteUntagged: &trueVal,
KeepTags: []config.KeepTagsPolicy{
{
Patterns: []string{".*"},
PulledWithin: &sevenDays,
PushedWithin: &sevenDays,
},
},
},
},
},
}, audit, log)
err = gc.CleanRepo(ctx, "retention")
So(err, ShouldBeNil)
tags, err := imgStore.GetImageTags("retention")
So(err, ShouldBeNil)
So(tags, ShouldContain, "0.0.4")
So(tags, ShouldContain, "0.0.5")
So(tags, ShouldContain, "0.0.6")
So(tags, ShouldContain, "0.0.8")
So(tags, ShouldNotContain, "0.0.1")
So(tags, ShouldNotContain, "0.0.2")
So(tags, ShouldNotContain, "0.0.3")
So(tags, ShouldNotContain, "0.0.7")
})
Convey("retain 3 most recently pushed images", func() {
gc := gc.NewGarbageCollect(imgStore, metaDB, gc.Options{
Delay: storageConstants.DefaultGCDelay,
ImageRetention: config.ImageRetention{
Delay: storageConstants.DefaultRetentionDelay,
Policies: []config.RetentionPolicy{
{
Repositories: []string{"**"},
DeleteReferrers: true,
DeleteUntagged: &trueVal,
KeepTags: []config.KeepTagsPolicy{
{
Patterns: []string{".*"},
MostRecentlyPushedCount: 3,
},
},
},
},
},
}, audit, log)
err = gc.CleanRepo(ctx, "retention")
So(err, ShouldBeNil)
tags, err := imgStore.GetImageTags("retention")
So(err, ShouldBeNil)
So(tags, ShouldContain, "0.0.4")
So(tags, ShouldContain, "0.0.5")
So(tags, ShouldContain, "0.0.6")
So(tags, ShouldNotContain, "0.0.1")
So(tags, ShouldNotContain, "0.0.2")
So(tags, ShouldNotContain, "0.0.3")
So(tags, ShouldNotContain, "0.0.7")
So(tags, ShouldNotContain, "0.0.8")
})
Convey("retain 3 most recently pulled images", func() {
gc := gc.NewGarbageCollect(imgStore, metaDB, gc.Options{
Delay: storageConstants.DefaultGCDelay,
ImageRetention: config.ImageRetention{
Delay: storageConstants.DefaultRetentionDelay,
Policies: []config.RetentionPolicy{
{
Repositories: []string{"**"},
DeleteReferrers: true,
DeleteUntagged: &trueVal,
KeepTags: []config.KeepTagsPolicy{
{
Patterns: []string{".*"},
MostRecentlyPulledCount: 3,
},
},
},
},
},
}, audit, log)
err = gc.CleanRepo(ctx, "retention")
So(err, ShouldBeNil)
tags, err := imgStore.GetImageTags("retention")
So(err, ShouldBeNil)
So(tags, ShouldContain, "0.0.4")
So(tags, ShouldContain, "0.0.5")
So(tags, ShouldContain, "0.0.6")
So(tags, ShouldNotContain, "0.0.1")
So(tags, ShouldNotContain, "0.0.2")
So(tags, ShouldNotContain, "0.0.3")
So(tags, ShouldNotContain, "0.0.7")
So(tags, ShouldNotContain, "0.0.8")
})
Convey("retain 3 most recently pulled OR 4 most recently pushed images", func() {
gc := gc.NewGarbageCollect(imgStore, metaDB, gc.Options{
Delay: storageConstants.DefaultGCDelay,
ImageRetention: config.ImageRetention{
Delay: storageConstants.DefaultRetentionDelay,
Policies: []config.RetentionPolicy{
{
Repositories: []string{"**"},
DeleteReferrers: true,
DeleteUntagged: &trueVal,
KeepTags: []config.KeepTagsPolicy{
{
Patterns: []string{".*"},
MostRecentlyPulledCount: 3,
MostRecentlyPushedCount: 4,
},
},
},
},
},
}, audit, log)
err = gc.CleanRepo(ctx, "retention")
So(err, ShouldBeNil)
tags, err := imgStore.GetImageTags("retention")
So(err, ShouldBeNil)
So(tags, ShouldContain, "0.0.4")
So(tags, ShouldContain, "0.0.5")
So(tags, ShouldContain, "0.0.6")
So(tags, ShouldContain, "0.0.8")
So(tags, ShouldNotContain, "0.0.1")
So(tags, ShouldNotContain, "0.0.2")
So(tags, ShouldNotContain, "0.0.3")
So(tags, ShouldNotContain, "0.0.7")
})
Convey("test if first match rule logic works", func() {
twoDays := 2 * 24 * time.Hour
gc := gc.NewGarbageCollect(imgStore, metaDB, gc.Options{
Delay: storageConstants.DefaultGCDelay,
ImageRetention: config.ImageRetention{
Delay: storageConstants.DefaultRetentionDelay,
Policies: []config.RetentionPolicy{
{
Repositories: []string{"**"},
DeleteReferrers: true,
DeleteUntagged: &trueVal,
KeepTags: []config.KeepTagsPolicy{
{
Patterns: []string{"0.0.1"},
},
{
Patterns: []string{"0.0.2"},
},
{
Patterns: []string{".*"},
PulledWithin: &twoDays,
},
},
},
},
},
}, audit, log)
err = gc.CleanRepo(ctx, "retention")
So(err, ShouldBeNil)
tags, err := imgStore.GetImageTags("retention")
So(err, ShouldBeNil)
t.Log(tags)
So(tags, ShouldContain, "0.0.1")
So(tags, ShouldContain, "0.0.2")
So(tags, ShouldContain, "0.0.4")
So(tags, ShouldNotContain, "0.0.3")
So(tags, ShouldNotContain, "0.0.5")
So(tags, ShouldNotContain, "0.0.6")
So(tags, ShouldNotContain, "0.0.7")
So(tags, ShouldNotContain, "0.0.8")
})
Convey("gc - do not match any repo", func() {
gc := gc.NewGarbageCollect(imgStore, metaDB, gc.Options{
Delay: 1 * time.Millisecond,
ImageRetention: config.ImageRetention{
Delay: 1 * time.Millisecond,
Policies: []config.RetentionPolicy{
{
Repositories: []string{"no-match"},
DeleteReferrers: true,
DeleteUntagged: &trueVal,
},
},
},
}, audit, log)
err := gc.CleanRepo(ctx, "gc-test1")
So(err, ShouldBeNil)
_, _, _, err = imgStore.GetImageManifest("gc-test1", gcUntagged1.DigestStr())
So(err, ShouldBeNil)
_, _, _, err = imgStore.GetImageManifest("gc-test1", ref1.DigestStr())
So(err, ShouldBeNil)
_, _, _, err = imgStore.GetImageManifest("gc-test1", refOfRef1.DigestStr())
So(err, ShouldBeNil)
repos, err := imgStore.GetRepositories()
So(err, ShouldBeNil)
So(repos, ShouldContain, "gc-test1")
So(repos, ShouldContain, "gc-test2")
So(repos, ShouldContain, "gc-test3")
So(repos, ShouldContain, "retention")
})
Convey("remove one tag because it didn't match, preserve tags without statistics in metaDB", func() {
// add new tag in retention repo which can not be found in metaDB, should be always retained
err = WriteImageToFileSystem(CreateRandomImage(), "retention", "0.0.9", storeController)
So(err, ShouldBeNil)
gc := gc.NewGarbageCollect(imgStore, metaDB, gc.Options{
Delay: storageConstants.DefaultGCDelay,
ImageRetention: config.ImageRetention{
Delay: storageConstants.DefaultRetentionDelay,
Policies: []config.RetentionPolicy{
{
Repositories: []string{"**"},
DeleteReferrers: true,
DeleteUntagged: &trueVal,
KeepTags: []config.KeepTagsPolicy{
{
Patterns: []string{"0.0.[1-5]", "0.0.7"},
},
},
},
},
},
}, audit, log)
err = gc.CleanRepo(ctx, "retention")
So(err, ShouldBeNil)
_, _, _, err = imgStore.GetImageManifest("retention", "0.0.1")
So(err, ShouldBeNil)
_, _, _, err = imgStore.GetImageManifest("retention", "0.0.2")
So(err, ShouldBeNil)
_, _, _, err = imgStore.GetImageManifest("retention", "0.0.3")
So(err, ShouldBeNil)
_, _, _, err = imgStore.GetImageManifest("retention", "0.0.4")
So(err, ShouldBeNil)
_, _, _, err = imgStore.GetImageManifest("retention", "0.0.5")
So(err, ShouldBeNil)
_, _, _, err = imgStore.GetImageManifest("retention", "0.0.6")
So(err, ShouldNotBeNil)
_, _, _, err = imgStore.GetImageManifest("retention", "0.0.7")
So(err, ShouldBeNil)
_, _, _, err = imgStore.GetImageManifest("retention", "0.0.8")
So(err, ShouldNotBeNil)
_, _, _, err = imgStore.GetImageManifest("retention", "0.0.9")
So(err, ShouldBeNil)
})
Convey("gc with context done", func() {
gc := gc.NewGarbageCollect(imgStore, metaDB, gc.Options{
Delay: storageConstants.DefaultGCDelay,
ImageRetention: config.ImageRetention{
Delay: 1 * time.Millisecond,
Policies: []config.RetentionPolicy{
{
Repositories: []string{"**"},
DeleteReferrers: true,
DeleteUntagged: &trueVal,
KeepTags: []config.KeepTagsPolicy{
{
Patterns: []string{"0.0.*"},
},
},
},
},
},
}, audit, log)
ctx, cancel := context.WithCancel(ctx)
cancel()
err := gc.CleanRepo(ctx, "gc-test1")
So(err, ShouldNotBeNil)
})
Convey("should gc only stale blob uploads", func() {
gcDelay := 1 * time.Second
repoName := "gc-test1"
gc := gc.NewGarbageCollect(imgStore, metaDB, gc.Options{
Delay: gcDelay,
ImageRetention: config.ImageRetention{
Delay: storageConstants.DefaultRetentionDelay,
Policies: []config.RetentionPolicy{
{
Repositories: []string{"**"},
DeleteReferrers: true,
DeleteUntagged: &trueVal,
KeepTags: []config.KeepTagsPolicy{
{},
},
},
},
},
}, audit, log)
blobUploadID, err := imgStore.NewBlobUpload(repoName)
So(err, ShouldBeNil)
content := []byte("test-data3")
buf := bytes.NewBuffer(content)
_, err = imgStore.PutBlobChunkStreamed(repoName, blobUploadID, buf)
So(err, ShouldBeNil)
// Blob upload should be there
uploads, err := imgStore.ListBlobUploads(repoName)
So(err, ShouldBeNil)
if testcase.testCaseName == s3TestName {
// Remote sorage is written to only after the blob upload is finished,
// there should be no space used by blob uploads
So(uploads, ShouldEqual, []string{})
} else {
// Local storage is used right away
So(uploads, ShouldEqual, []string{blobUploadID})
}
isPresent, _, _, err := imgStore.StatBlobUpload(repoName, blobUploadID)
if testcase.testCaseName == s3TestName {
// Remote sorage is written to only after the blob upload is finished,
// there should be no space used by blob uploads
So(err, ShouldNotBeNil)
So(isPresent, ShouldBeFalse)
} else {
// Local storage is used right away
So(err, ShouldBeNil)
So(isPresent, ShouldBeTrue)
}
err = gc.CleanRepo(ctx, repoName)
So(err, ShouldBeNil)
// Blob upload is recent it should still be there
uploads, err = imgStore.ListBlobUploads(repoName)
So(err, ShouldBeNil)
if testcase.testCaseName == s3TestName {
// Remote sorage is written to only after the blob upload is finished,
// there should be no space used by blob uploads
So(uploads, ShouldEqual, []string{})
} else {
// Local storage is used right away
So(uploads, ShouldEqual, []string{blobUploadID})
}
isPresent, _, _, err = imgStore.StatBlobUpload(repoName, blobUploadID)
if testcase.testCaseName == s3TestName {
// Remote sorage is written to only after the blob upload is finished,
// there should be no space used by blob uploads
So(err, ShouldNotBeNil)
So(isPresent, ShouldBeFalse)
} else {
// Local storage is used right away
So(err, ShouldBeNil)
So(isPresent, ShouldBeTrue)
}
time.Sleep(gcDelay + 1*time.Second)
err = gc.CleanRepo(ctx, repoName)
So(err, ShouldBeNil)
// Blob uploads should be GCed
uploads, err = imgStore.ListBlobUploads(repoName)
So(err, ShouldBeNil)
So(uploads, ShouldBeEmpty)
isPresent, _, _, err = imgStore.StatBlobUpload(repoName, blobUploadID)
So(err, ShouldNotBeNil)
So(isPresent, ShouldBeFalse)
})
})
})
}
}
func TestGarbageCollectDeletion(t *testing.T) {
Convey("setup store", t, func() {
log := zlog.NewLogger("debug", "")
audit := zlog.NewAuditLogger("debug", "/dev/null")
metrics := monitoring.NewMetricsServer(false, log)
trueVal := true
// Create temporary directory
rootDir := t.TempDir()
// Create ImageStore
imgStore := local.NewImageStore(rootDir, false, false, log, metrics, nil, nil, nil)
// init metaDB
params := boltdb.DBParameters{
RootDir: rootDir,
}
boltDriver, err := boltdb.GetBoltDriver(params)
So(err, ShouldBeNil)
metaDB, err := boltdb.New(boltDriver, log)
So(err, ShouldBeNil)
storeController := storage.StoreController{}
storeController.DefaultStore = imgStore
ctx := context.Background()
repoName := "multiarch"
blobsDir := path.Join(rootDir, repoName, "blobs")
Convey("Create test data", func() {
image1 := CreateRandomImage()
image2 := CreateRandomImage()
image3 := CreateRandomImage()
bottomIndex1 := CreateMultiarchWith().Images([]Image{image1, image2}).Build()
bottomIndex2 := CreateMultiarchWith().Images([]Image{image3}).Build()
err = WriteImageToFileSystem(image2, repoName, "manifest2", storeController)
So(err, ShouldBeNil)
err = WriteMultiArchImageToFileSystem(bottomIndex1, repoName, bottomIndex1.Digest().String(), storeController)
So(err, ShouldBeNil)
err = WriteMultiArchImageToFileSystem(bottomIndex2, repoName, "bottomIndex2", storeController)
So(err, ShouldBeNil)
topIndex := ispec.Index{
Versioned: specs.Versioned{SchemaVersion: 2},
MediaType: ispec.MediaTypeImageIndex,
Manifests: []ispec.Descriptor{
{
Digest: bottomIndex1.IndexDescriptor.Digest,
Size: bottomIndex1.IndexDescriptor.Size,
MediaType: ispec.MediaTypeImageIndex,
},
{
Digest: bottomIndex2.IndexDescriptor.Digest,
Size: bottomIndex2.IndexDescriptor.Size,
MediaType: ispec.MediaTypeImageIndex,
},
},
}
topIndexBlob, err := json.Marshal(topIndex)
So(err, ShouldBeNil)
rootIndexDigest, _, err := imgStore.PutImageManifest(repoName, "topindex", ispec.MediaTypeImageIndex,
topIndexBlob)
So(err, ShouldBeNil)
bottomIndex1Digest := bottomIndex1.IndexDescriptor.Digest
bottomIndex2Digest := bottomIndex2.IndexDescriptor.Digest
manifest1Digest := image1.Digest()
manifest2Digest := image2.Digest()
manifest3Digest := image3.Digest()
err = meta.ParseStorage(metaDB, storeController, log) //nolint: contextcheck
So(err, ShouldBeNil)
Convey("gc untagged manifests should not do anything, as all images refer to one another", 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: &trueVal,
KeepTags: []config.KeepTagsPolicy{},
},
},
},
}, audit, log)
err = gc.CleanRepo(ctx, repoName)
So(err, ShouldBeNil)
// All indexes and manifests refer to one another, so none should be missing
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)
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)
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)
tags, err = readTagsFromStorage(rootDir, repoName, bottomIndex1Digest)
So(err, ShouldBeNil)
So(tags, ShouldContain, "")
So(len(tags), ShouldEqual, 1)
_, err = os.Stat(path.Join(blobsDir, bottomIndex1Digest.Algorithm().String(), bottomIndex1Digest.Encoded()))
So(err, ShouldBeNil)
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)
tags, err = readTagsFromStorage(rootDir, repoName, rootIndexDigest)
So(err, ShouldBeNil)
So(tags, ShouldContain, "topindex")
So(len(tags), ShouldEqual, 1)
_, err = os.Stat(path.Join(blobsDir, rootIndexDigest.Algorithm().String(), rootIndexDigest.Encoded()))
So(err, ShouldBeNil)
})
Convey("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: &trueVal,
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 be deleted
tags, err := readTagsFromStorage(rootDir, repoName, manifest1Digest)
So(err, ShouldBeNil)
So(len(tags), ShouldEqual, 0)
_, err = os.Stat(path.Join(blobsDir, manifest1Digest.Algorithm().String(), manifest1Digest.Encoded()))
So(err, ShouldNotBeNil)
// 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 be deleted
tags, err = readTagsFromStorage(rootDir, repoName, bottomIndex1Digest)
So(err, ShouldBeNil)
So(len(tags), ShouldEqual, 0)
_, err = os.Stat(path.Join(blobsDir, bottomIndex1Digest.Algorithm().String(), bottomIndex1Digest.Encoded()))
So(err, ShouldNotBeNil)
// 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 be deleted
tags, err = readTagsFromStorage(rootDir, repoName, rootIndexDigest)
So(err, ShouldBeNil)
So(len(tags), ShouldEqual, 0)
_, err = os.Stat(path.Join(blobsDir, rootIndexDigest.Algorithm().String(), rootIndexDigest.Encoded()))
So(err, ShouldNotBeNil)
})
Convey("gc unmatching tags", 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: &trueVal,
KeepTags: []config.KeepTagsPolicy{
{
Patterns: []string{"manifest2"},
},
},
},
},
},
}, audit, log)
err = gc.CleanRepo(ctx, repoName)
So(err, ShouldBeNil)
// manifest1, bottomIndex1 and topIndex are untagged or don't have matching tags
tags, err := readTagsFromStorage(rootDir, repoName, manifest1Digest)
So(err, ShouldBeNil)
So(len(tags), ShouldEqual, 0)
_, err = os.Stat(path.Join(blobsDir, manifest1Digest.Algorithm().String(), manifest1Digest.Encoded()))
So(err, ShouldNotBeNil)
// manifest2 has a matching 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, bottomIndex2 and topIndex are untagged or don't have matching tags
tags, err = readTagsFromStorage(rootDir, repoName, manifest3Digest)
So(err, ShouldBeNil)
So(len(tags), ShouldEqual, 0)
_, err = os.Stat(path.Join(blobsDir, manifest3Digest.Algorithm().String(), manifest3Digest.Encoded()))
So(err, ShouldNotBeNil)
// bottomIndex1 and topIndex are untagged, so bottomIndex1 should be deleted
tags, err = readTagsFromStorage(rootDir, repoName, bottomIndex1Digest)
So(err, ShouldBeNil)
So(len(tags), ShouldEqual, 0)
_, err = os.Stat(path.Join(blobsDir, bottomIndex1Digest.Algorithm().String(), bottomIndex1Digest.Encoded()))
So(err, ShouldNotBeNil)
// bottomIndex2 and topIndex are untagged, so bottomIndex1 should be deleted
tags, err = readTagsFromStorage(rootDir, repoName, bottomIndex2Digest)
So(err, ShouldBeNil)
So(len(tags), ShouldEqual, 0)
_, err = os.Stat(path.Join(blobsDir, bottomIndex2Digest.Algorithm().String(), bottomIndex2Digest.Encoded()))
So(err, ShouldNotBeNil)
// topIndex is untagged, so it should be deleted
tags, err = readTagsFromStorage(rootDir, repoName, rootIndexDigest)
So(err, ShouldBeNil)
So(len(tags), ShouldEqual, 0)
_, err = os.Stat(path.Join(blobsDir, rootIndexDigest.Algorithm().String(), rootIndexDigest.Encoded()))
So(err, ShouldNotBeNil)
})
})
})
}
func deleteTagInStorage(rootDir, repoName, tag string) error {
indexJSONBuf, err := os.ReadFile(path.Join(rootDir, repoName, "index.json"))
if err != nil {
return err
}
var indexJSON ispec.Index
err = json.Unmarshal(indexJSONBuf, &indexJSON)
if err != nil {
return err
}
for _, desc := range indexJSON.Manifests {
if desc.Annotations[ispec.AnnotationRefName] == tag {
delete(desc.Annotations, ispec.AnnotationRefName)
}
}
indexJSONBuf, err = json.Marshal(indexJSON)
if err != nil {
return err
}
err = os.WriteFile(path.Join(rootDir, repoName, "index.json"), indexJSONBuf, 0o600)
if err != nil {
return err
}
return nil
}
func readTagsFromStorage(rootDir, repoName string, digest godigest.Digest) ([]string, error) {
result := []string{}
indexJSONBuf, err := os.ReadFile(path.Join(rootDir, repoName, "index.json"))
if err != nil {
return result, err
}
var indexJSON ispec.Index
err = json.Unmarshal(indexJSONBuf, &indexJSON)
if err != nil {
return result, err
}
for _, desc := range indexJSON.Manifests {
if desc.Digest != digest {
continue
}
name := desc.Annotations[ispec.AnnotationRefName]
// There is a special case where there is an entry in
// the index.json without tags, in this case name is an empty string
// Also we should not have duplicates
// Do these checks in the actual test cases, not here
result = append(result, name)
}
return result, nil
}