-
Notifications
You must be signed in to change notification settings - Fork 273
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support using regex capture groups to clean up the semver of a tag #407
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,21 +7,29 @@ import ( | |
) | ||
|
||
// MatchFuncAny matches any pattern, i.e. always returns true | ||
func MatchFuncAny(tagName string, args interface{}) bool { | ||
return true | ||
func MatchFuncAny(tagName string, args interface{}) (string, bool) { | ||
return tagName, true | ||
} | ||
|
||
// MatchFuncNone matches no pattern, i.e. always returns false | ||
func MatchFuncNone(tagName string, args interface{}) bool { | ||
return false | ||
func MatchFuncNone(tagName string, args interface{}) (string, bool) { | ||
return tagName, false | ||
} | ||
|
||
// MatchFuncRegexp matches the tagName against regexp pattern and returns the result | ||
func MatchFuncRegexp(tagName string, args interface{}) bool { | ||
func MatchFuncRegexp(tagName string, args interface{}) (string, bool) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The methods' documentary comment should be updated to reflect the new behavior. |
||
pattern, ok := args.(*regexp.Regexp) | ||
if !ok { | ||
log.Errorf("args is not a RegExp") | ||
return false | ||
return tagName, false | ||
} | ||
matches := pattern.FindStringSubmatch(tagName) | ||
switch len(matches) { | ||
case 0: | ||
return "", false | ||
case 1: | ||
return tagName, true | ||
default: | ||
return matches[1], true | ||
} | ||
return pattern.Match([]byte(tagName)) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -51,7 +51,11 @@ func (endpoint *RegistryEndpoint) GetTags(img *image.ContainerImage, regClient R | |
return nil, err | ||
} | ||
|
||
tags := []string{} | ||
type tuple struct { | ||
original, found string | ||
} | ||
|
||
tags := []tuple{} | ||
|
||
// For digest strategy, we do require a version constraint | ||
if vc.Strategy.NeedsVersionConstraint() && vc.Constraint == "" { | ||
|
@@ -62,14 +66,32 @@ func (endpoint *RegistryEndpoint) GetTags(img *image.ContainerImage, regClient R | |
// digest, all but the constraint tag are ignored. | ||
if vc.MatchFunc != nil || len(vc.IgnoreList) > 0 || vc.Strategy.WantsOnlyConstraintTag() { | ||
for _, t := range tTags { | ||
if (vc.MatchFunc != nil && !vc.MatchFunc(t, vc.MatchArgs)) || vc.IsTagIgnored(t) || (vc.Strategy.WantsOnlyConstraintTag() && t != vc.Constraint) { | ||
var ( | ||
shouldRemove = false | ||
item = tuple{t, t} | ||
) | ||
Comment on lines
+69
to
+72
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please don't use |
||
if vc.MatchFunc != nil { | ||
if found, ok := vc.MatchFunc(t, vc.MatchArgs); ok { | ||
item.found = found | ||
} else { | ||
shouldRemove = true | ||
} | ||
} | ||
|
||
if vc.IsTagIgnored(t) || (vc.Strategy.WantsOnlyConstraintTag() && t != vc.Constraint) { | ||
shouldRemove = true | ||
} | ||
|
||
if shouldRemove { | ||
logCtx.Tracef("Removing tag %s because it either didn't match defined pattern or is ignored", t) | ||
} else { | ||
tags = append(tags, t) | ||
tags = append(tags, item) | ||
} | ||
} | ||
} else { | ||
tags = tTags | ||
for _, t := range tTags { | ||
tags = append(tags, tuple{t, t}) | ||
} | ||
} | ||
|
||
// In some cases, we don't need to fetch the metadata to get the creation time | ||
|
@@ -88,7 +110,7 @@ func (endpoint *RegistryEndpoint) GetTags(img *image.ContainerImage, regClient R | |
} else if endpoint.TagListSort == TagListSortLatestLast { | ||
ts = i | ||
} | ||
imgTag := tag.NewImageTag(tagStr, time.Unix(int64(ts), 0), "") | ||
imgTag := tag.NewImageTagWithTagPart(tagStr.original, tagStr.found, time.Unix(int64(ts), 0), "") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm. I think |
||
tagList.Add(imgTag) | ||
} | ||
return tagList, nil | ||
|
@@ -109,7 +131,7 @@ func (endpoint *RegistryEndpoint) GetTags(img *image.ContainerImage, regClient R | |
// an error, we treat it as a cache miss and just go ahead to invalidate | ||
// the entry. | ||
if vc.Strategy.IsCacheable() { | ||
imgTag, err := endpoint.Cache.GetTag(nameInRegistry, tagStr) | ||
imgTag, err := endpoint.Cache.GetTag(nameInRegistry, tagStr.original) | ||
if err != nil { | ||
log.Warnf("invalid entry for %s:%s in cache, invalidating.", nameInRegistry, imgTag.TagName) | ||
} else if imgTag != nil { | ||
|
@@ -172,7 +194,7 @@ func (endpoint *RegistryEndpoint) GetTags(img *image.ContainerImage, regClient R | |
tagList.Add(imgTag) | ||
tagListLock.Unlock() | ||
endpoint.Cache.SetTag(nameInRegistry, imgTag) | ||
}(tagStr) | ||
}(tagStr.original) | ||
} | ||
|
||
wg.Wait() | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,9 +14,11 @@ import ( | |
// ImageTag is a representation of an image tag with metadata | ||
// Use NewImageTag to to initialize a new object. | ||
type ImageTag struct { | ||
TagName string | ||
TagDate *time.Time | ||
TagDigest string | ||
TagName string | ||
TagNamePart string | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this field would need some additional comment on what it actually represents, it is not clear to me. |
||
TagDate *time.Time | ||
TagDigest string | ||
TagVersion *semver.Version | ||
} | ||
|
||
// ImageTagList is a collection of ImageTag objects. | ||
|
@@ -47,13 +49,34 @@ func (il SortableImageTagList) Swap(i, j int) { | |
|
||
// NewImageTag initializes an ImageTag object and returns it | ||
func NewImageTag(tagName string, tagDate time.Time, tagDigest string) *ImageTag { | ||
return NewImageTagWithTagPart(tagName, tagName, tagDate, tagDigest) | ||
} | ||
|
||
// NewImageTagWithTagPart initializes an ImageTag object and returns it | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please be more specific what the difference to func (t *ImageTag) WithTagNamePart(tagNamePart string) *ImageTag {
t.TagNamePart = tagNamePart
return t
}
tag := NewImageTag(...).WithTagNamePart(...) WDYT? |
||
func NewImageTagWithTagPart(tagName, tagNamePart string, tagDate time.Time, tagDigest string) *ImageTag { | ||
tag := &ImageTag{} | ||
tag.TagName = tagName | ||
tag.TagNamePart = tagNamePart | ||
tag.TagDate = &tagDate | ||
tag.TagDigest = tagDigest | ||
tag.TagVersion = tryParseSemVer(tagNamePart) | ||
return tag | ||
} | ||
|
||
func tryParseSemVer(tag string) *semver.Version { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we please have a unit test for this method? |
||
if len(tag) > 0 && tag[0] != 'v' { | ||
tag = "v" + tag | ||
} | ||
Comment on lines
+67
to
+69
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure this is necessary. I think Masterminds/semver treats "x.y.z" and "vx.y.z" the same. |
||
|
||
svi, err := semver.NewVersion(tag) | ||
if err != nil { | ||
log.Debugf("could not parse input tag %s as semver: %v", tag, err) | ||
return nil | ||
} | ||
|
||
return svi | ||
} | ||
|
||
// NewImageTagList initializes an ImageTagList object and returns it | ||
func NewImageTagList() *ImageTagList { | ||
itl := ImageTagList{} | ||
|
@@ -157,17 +180,17 @@ func (il ImageTagList) SortBySemVer() SortableImageTagList { | |
|
||
sil := SortableImageTagList{} | ||
svl := make([]*semver.Version, 0) | ||
tagMap := make(map[string]*ImageTag) | ||
for _, v := range il.items { | ||
svi, err := semver.NewVersion(v.TagName) | ||
if err != nil { | ||
log.Debugf("could not parse input tag %s as semver: %v", v.TagName, err) | ||
if v.TagVersion == nil { | ||
continue | ||
} | ||
svl = append(svl, svi) | ||
tagMap[v.TagVersion.Original()] = v | ||
svl = append(svl, v.TagVersion) | ||
} | ||
sort.Sort(semver.Collection(svl)) | ||
for _, svi := range svl { | ||
sil = append(sil, NewImageTag(svi.Original(), *il.items[svi.Original()].TagDate, il.items[svi.Original()].TagDigest)) | ||
sil = append(sil, tagMap[svi.Original()]) | ||
} | ||
return sil | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should return
""
instead oftagName
, for consistency withMatchFuncRegexp