Skip to content

Commit e7a5e09

Browse files
authored
fix: image retention policy to handle patterns even if metadb is not instantiated (#3200)
It is to fix #3185. This fixes the case where MetaDB is not instantiated (none of the conditions match), and we want to retain tags only by pattern (which should not need to use MetaBD). Without this fix you could only use retention to delete untagged manifests. If you specified only the key "patterns" under "keepTags", zot would crash. It was possible to not specify "keepTags" all, which would retain all tags, but it was not possible to retains specific tags. Basically the case quoted below, from the documentation, was broken:: https://zotregistry.dev/v2.1.4/articles/retention/#configuration-example ``` When you specify a regex pattern with no rules other than the default, all tags matching the pattern are retained. ``` This would only work if MetaDb was instantiated by an unrelated configured feature. Signed-off-by: Andrei Aaron <aaaron@luxoft.com>
1 parent 6a22640 commit e7a5e09

5 files changed

Lines changed: 958 additions & 10 deletions

File tree

pkg/retention/candidate.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package retention
22

33
import (
4+
ispec "github.com/opencontainers/image-spec/specs-go/v1"
5+
46
mTypes "zotregistry.dev/zot/pkg/meta/types"
57
"zotregistry.dev/zot/pkg/retention/types"
68
)
@@ -27,3 +29,25 @@ func GetCandidates(repoMeta mTypes.RepoMeta) []*types.Candidate {
2729

2830
return candidates
2931
}
32+
33+
func GetCandidatesFromIndex(index ispec.Index) []*types.Candidate {
34+
candidates := make([]*types.Candidate, 0)
35+
36+
// collect all manifests in the repo
37+
for _, manifest := range index.Manifests {
38+
tag, ok := manifest.Annotations[ispec.AnnotationRefName]
39+
if !ok {
40+
continue
41+
}
42+
43+
candidate := &types.Candidate{
44+
MediaType: manifest.MediaType,
45+
DigestStr: string(manifest.Digest),
46+
Tag: tag,
47+
}
48+
49+
candidates = append(candidates, candidate)
50+
}
51+
52+
return candidates
53+
}

pkg/retention/retention.go

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,51 @@ func (p policyManager) getRules(tagPolicy config.KeepTagsPolicy) []types.Rule {
9898
return rules
9999
}
100100

101-
func (p policyManager) GetRetainedTags(ctx context.Context, repoMeta mTypes.RepoMeta, index ispec.Index) []string {
101+
// GetRetainedTagsFromIndex uses only index information to match tags against patterns and determine
102+
// a list of tags to be retained. This function is to be used only in case MetaDB information is not available,
103+
// if the DB is not instantiated.
104+
func (p policyManager) GetRetainedTagsFromIndex(ctx context.Context, repo string, index ispec.Index) []string {
105+
candidates := GetCandidatesFromIndex(index)
106+
retainTags := make([]string, 0)
107+
108+
// group all tags by tag policy
109+
grouped := p.groupCandidatesByTagPolicy(repo, candidates)
110+
111+
for _, candidates := range grouped {
112+
if zcommon.IsContextDone(ctx) {
113+
return nil
114+
}
115+
116+
for _, retainCandidate := range candidates.candidates {
117+
// there may be duplicates
118+
if !zcommon.Contains(retainTags, retainCandidate.Tag) {
119+
reason := fmt.Sprintf(retainedStrFormat, retainCandidate.RetainedBy)
120+
121+
logAction(repo, "keep", reason, retainCandidate, p.config.DryRun, &p.log)
122+
123+
retainTags = append(retainTags, retainCandidate.Tag)
124+
}
125+
}
126+
}
127+
128+
// log tags which will be removed
129+
for _, candidate := range candidates {
130+
if !zcommon.Contains(retainTags, candidate.Tag) {
131+
logAction(repo, "delete", filteredByTagNames, candidate, p.config.DryRun, &p.log)
132+
133+
if p.auditLog != nil {
134+
logAction(repo, "delete", filteredByTagNames, candidate, p.config.DryRun, p.auditLog)
135+
}
136+
}
137+
}
138+
139+
return retainTags
140+
}
141+
142+
// GetRetainedTagsFromMetaDB uses MetaDB information to apply retention rules and obtain a list of tags to be retained.
143+
func (p policyManager) GetRetainedTagsFromMetaDB(ctx context.Context, repoMeta mTypes.RepoMeta,
144+
index ispec.Index,
145+
) []string {
102146
repo := repoMeta.Name
103147

104148
matchedByName := make([]string, 0)

pkg/retention/types/types.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ type PolicyManager interface {
2222
HasDeleteReferrer(repo string) bool
2323
HasDeleteUntagged(repo string) bool
2424
HasTagRetention(repo string) bool
25-
GetRetainedTags(ctx context.Context, repoMeta mTypes.RepoMeta, index ispec.Index) []string
25+
GetRetainedTagsFromIndex(ctx context.Context, repo string, index ispec.Index) []string
26+
GetRetainedTagsFromMetaDB(ctx context.Context, repoMeta mTypes.RepoMeta, index ispec.Index) []string
2627
}
2728

2829
type Rule interface {

pkg/storage/gc/gc.go

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -363,15 +363,21 @@ func (gc GarbageCollect) removeTagsPerRetentionPolicy(ctx context.Context, repo
363363
return nil
364364
}
365365

366-
repoMeta, err := gc.metaDB.GetRepoMeta(ctx, repo)
367-
if err != nil {
368-
gc.log.Error().Err(err).Str("module", "gc").Str("repository", repo).
369-
Msg("failed to get repoMeta")
366+
var retainTags []string
370367

371-
return err
372-
}
368+
if gc.metaDB != nil {
369+
repoMeta, err := gc.metaDB.GetRepoMeta(ctx, repo)
370+
if err != nil {
371+
gc.log.Error().Err(err).Str("module", "gc").Str("repository", repo).
372+
Msg("failed to get repoMeta")
373373

374-
retainTags := gc.policyMgr.GetRetainedTags(ctx, repoMeta, *index)
374+
return err
375+
}
376+
377+
retainTags = gc.policyMgr.GetRetainedTagsFromMetaDB(ctx, repoMeta, *index)
378+
} else {
379+
retainTags = gc.policyMgr.GetRetainedTagsFromIndex(ctx, repo, *index)
380+
}
375381

376382
// remove
377383
for _, desc := range index.Manifests {

0 commit comments

Comments
 (0)