diff --git a/README.md b/README.md index f3cef586..981a74eb 100644 --- a/README.md +++ b/README.md @@ -107,6 +107,10 @@ enrich version checking on image tags: is. In this example, the current version of `my-container` will be compared against the image versions in the `docker.io/bitnami/etcd` registry. +- `resolve-sha-to-tags.version-checker.io/my-container`: is used to + resolve images specified using sha256 in kubernetes manifests to valid semver + tags. To enable this the annotation value must be set to "true". + ## Known configurations From time to time, version-checker may need some of the above options applied to determine the latest version, diff --git a/go.sum b/go.sum index d404eaab..985fee26 100644 --- a/go.sum +++ b/go.sum @@ -19,12 +19,8 @@ github.com/Azure/go-autorest/logger v0.2.2/go.mod h1:I5fg9K52o+iuydlWfa9T5K6WFos github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/Azure/go-autorest/tracing v0.6.1 h1:YUMSrC/CeD1ZnnXcNYU4a/fzsO35u2Fsful9L/2nyR0= github.com/Azure/go-autorest/tracing v0.6.1/go.mod h1:/3EgjbsjraOqiicERAeu3m7/z0x1TzjQGAwDrJrXGkc= -github.com/MicahParks/jwkset v0.5.19 h1:XZCsgJv05DBCvxEHYEHlSafqiuVn5ESG0VRB331Fxhw= -github.com/MicahParks/jwkset v0.5.19/go.mod h1:q8ptTGn/Z9c4MwbcfeCDssADeVQb3Pk7PnVxrvi+2QY= github.com/MicahParks/jwkset v0.8.0 h1:jHtclI38Gibmu17XMI6+6/UB59srp58pQVxePHRK5o8= github.com/MicahParks/jwkset v0.8.0/go.mod h1:fVrj6TmG1aKlJEeceAz7JsXGTXEn72zP1px3us53JrA= -github.com/MicahParks/keyfunc/v3 v3.3.5 h1:7ceAJLUAldnoueHDNzF8Bx06oVcQ5CfJnYwNt1U3YYo= -github.com/MicahParks/keyfunc/v3 v3.3.5/go.mod h1:SdCCyMJn/bYqWDvARspC6nCT8Sk74MjuAY22C7dCST8= github.com/MicahParks/keyfunc/v3 v3.3.10 h1:JtEGE8OcNeI297AMrR4gVXivV8fyAawFUMkbwNreJRk= github.com/MicahParks/keyfunc/v3 v3.3.10/go.mod h1:1TEt+Q3FO7Yz2zWeYO//fMxZMOiar808NqjWQQpBPtU= github.com/aws/aws-sdk-go-v2 v1.36.3 h1:mJoei2CxPutQVxaATCzDUjcZEjVRdpsiiXi2o38yqWM= diff --git a/pkg/api/annotations.go b/pkg/api/annotations.go index df1231ee..57769fca 100644 --- a/pkg/api/annotations.go +++ b/pkg/api/annotations.go @@ -14,6 +14,9 @@ const ( // as its tag. UseSHAAnnotationKey = "use-sha.version-checker.io" + //ResolveSHAToTagsKey is used to resolve image sha256 to corresponding tags + ResolveSHAToTagsKey = "resolve-sha-to-tags.version-checker.io" + // MatchRegexAnnotationKey will enforce that tags that are looked up must // match this regex. UseMetaDataAnnotationKey is not required when this is // set. All other options are ignored when this is set. diff --git a/pkg/api/options.go b/pkg/api/options.go index df7c8091..8590aff1 100644 --- a/pkg/api/options.go +++ b/pkg/api/options.go @@ -9,6 +9,8 @@ type Options struct { // UseSHA cannot be used with any other options UseSHA bool `json:"use-sha,omitempty"` + // Resolve SHA to a TAG + ResolveSHAToTags bool `json:"resolve-sha-to-tags,omitempty"` MatchRegex *string `json:"match-regex,omitempty"` diff --git a/pkg/controller/checker/checker.go b/pkg/controller/checker/checker.go index dcf42bbc..a80f81ba 100644 --- a/pkg/controller/checker/checker.go +++ b/pkg/controller/checker/checker.go @@ -44,6 +44,22 @@ func (c *Checker) Container(ctx context.Context, log *logrus.Entry, imageURL, currentTag, currentSHA := urlTagSHAFromImage(container.Image) usingSHA, usingTag := len(currentSHA) > 0, len(currentTag) > 0 + if opts.ResolveSHAToTags { + + if len(*opts.OverrideURL) > 0 { + imageURL = *opts.OverrideURL + } + resolvedTag, err := c.search.ResolveSHAToTag(ctx, imageURL, currentSHA) + + if len(resolvedTag) > 0 && err == nil { + log.Infof("Successfully resolved tag for sha256: %s at url: %s", currentSHA, imageURL) + currentTag = resolvedTag + usingSHA = false + usingTag = true + } + } + + // If using latest or no tag, then compare on SHA if c.isLatestOrEmptyTag(currentTag) { c.handleLatestOrEmptyTag(log, currentTag, currentSHA, opts) usingTag = false diff --git a/pkg/controller/internal/fake/search/search.go b/pkg/controller/internal/fake/search/search.go index 2760f4aa..fc867f53 100644 --- a/pkg/controller/internal/fake/search/search.go +++ b/pkg/controller/internal/fake/search/search.go @@ -11,7 +11,8 @@ import ( var _ search.Searcher = &FakeSearch{} type FakeSearch struct { - latestImageF func() (*api.ImageTag, error) + latestImageF func() (*api.ImageTag, error) + resolveSHAToTagF func() (string, error) } func New() *FakeSearch { @@ -19,6 +20,9 @@ func New() *FakeSearch { latestImageF: func() (*api.ImageTag, error) { return nil, nil }, + resolveSHAToTagF: func() (string, error) { + return "", nil + }, } } @@ -33,5 +37,8 @@ func (f *FakeSearch) LatestImage(context.Context, string, *api.Options) (*api.Im return f.latestImageF() } +func (f *FakeSearch) ResolveSHAToTag(ctx context.Context, imageURL string, imageSHA string) (string, error) { + return f.resolveSHAToTagF() +} func (f *FakeSearch) Run(time.Duration) { } diff --git a/pkg/controller/options/options.go b/pkg/controller/options/options.go index 9982f86d..f5264450 100644 --- a/pkg/controller/options/options.go +++ b/pkg/controller/options/options.go @@ -36,6 +36,7 @@ func (b *Builder) Options(name string) (*api.Options, error) { // Define the handlers handlers := []optionsHandler{ b.handleSHAOption, + b.handleSHAToTagOption, b.handleMetadataOption, b.handleRegexOption, b.handlePinMajorOption, @@ -53,7 +54,9 @@ func (b *Builder) Options(name string) (*api.Options, error) { // Ensure UseSHA is not used with other semver options if opts.UseSHA && setNonSha { - errs = append(errs, fmt.Sprintf("cannot define %q with any semver options", b.index(name, api.UseSHAAnnotationKey))) + errs = append(errs, + fmt.Sprintf("cannot define %q with any semver options", b.index(name, api.UseSHAAnnotationKey)), + ) } if len(errs) > 0 { @@ -68,6 +71,12 @@ func (b *Builder) handleSHAOption(name string, opts *api.Options, setNonSha *boo } return nil } +func (b *Builder) handleSHAToTagOption(name string, opts *api.Options, setNonSha *bool, errs *[]string) error { + if ResolveSHAToTags, ok := b.ans[b.index(name, api.ResolveSHAToTagsKey)]; ok && ResolveSHAToTags == "true" { + opts.ResolveSHAToTags = true + } + return nil +} func (b *Builder) handleMetadataOption(name string, opts *api.Options, setNonSha *bool, errs *[]string) error { if useMetaData, ok := b.ans[b.index(name, api.UseMetaDataAnnotationKey)]; ok && useMetaData == "true" { diff --git a/pkg/controller/options/options_test.go b/pkg/controller/options/options_test.go index f54c102a..6ec206f0 100644 --- a/pkg/controller/options/options_test.go +++ b/pkg/controller/options/options_test.go @@ -111,6 +111,16 @@ func TestBuild(t *testing.T) { }, expErr: "", }, + "output options for resolve sha": { + containerName: "test-name", + annotations: map[string]string{ + api.ResolveSHAToTagsKey + "/test-name": "true", + }, + expOptions: &api.Options{ + ResolveSHAToTags: true, + }, + expErr: "", + }, "bool options that don't have 'true' and nothing": { containerName: "test-name", annotations: map[string]string{ diff --git a/pkg/controller/search/search.go b/pkg/controller/search/search.go index fe2a528d..e75daccd 100644 --- a/pkg/controller/search/search.go +++ b/pkg/controller/search/search.go @@ -17,6 +17,7 @@ import ( // Searcher is the interface for Search to facilitate testing. type Searcher interface { LatestImage(context.Context, string, *api.Options) (*api.ImageTag, error) + ResolveSHAToTag(ctx context.Context, imageURL string, imageSHA string) (string, error) } // Search is the implementation for the searching and caching of image URLs. @@ -65,6 +66,16 @@ func (s *Search) LatestImage(ctx context.Context, imageURL string, opts *api.Opt return lastestImage.(*api.ImageTag), nil } +func (s *Search) ResolveSHAToTag(ctx context.Context, imageURL string, imageSHA string) (string, error) { + + tag, err := s.versionGetter.ResolveSHAToTag(ctx, imageURL, imageSHA) + if err != nil { + return "", fmt.Errorf("failed to resolve sha to tag: %w", err) + } + + return tag, err +} + // calculateHashIndex returns a hash index given an imageURL and options. func calculateHashIndex(imageURL string, opts *api.Options) (string, error) { optsJSON, err := json.Marshal(opts) diff --git a/pkg/version/version.go b/pkg/version/version.go index dd3043ec..b34a846d 100644 --- a/pkg/version/version.go +++ b/pkg/version/version.go @@ -74,6 +74,24 @@ func (v *Version) LatestTagFromImage(ctx context.Context, imageURL string, opts return tag, err } +// ResolveSHAToTag Resolve a SHA to a tag if possible +func (v *Version) ResolveSHAToTag(ctx context.Context, imageURL string, imageSHA string) (string, error) { + + tagsI, err := v.imageCache.Get(ctx, imageURL, imageURL, nil) + if err != nil { + return "", err + } + tags := tagsI.([]api.ImageTag) + + for i := range tags { + if tags[i].SHA == imageSHA { + return tags[i].Tag, nil + } + } + + return "", nil +} + // Fetch returns the given image tags for a given image URL. func (v *Version) Fetch(ctx context.Context, imageURL string, _ *api.Options) (interface{}, error) { // fetch tags from image URL