From 3b9d0462a8001de29e693b7842b5165477bcc62b Mon Sep 17 00:00:00 2001 From: Eric Wollesen Date: Mon, 21 Mar 2022 10:34:57 -0600 Subject: [PATCH] feat: pull charts by specifying the SHA256 checksum as the version. This worked in version 3.7, but seems to have been lost or removed in the upgrade to 3.8. It is useful because not all repositories have immutable tags. This is tangentially related to this comment: https://github.com/helm/helm/issues/10312#issuecomment-961396434 Signed-off-by: Eric Wollesen --- pkg/downloader/chart_downloader.go | 52 ++++++++++++++----------- pkg/downloader/chart_downloader_test.go | 1 + 2 files changed, 31 insertions(+), 22 deletions(-) diff --git a/pkg/downloader/chart_downloader.go b/pkg/downloader/chart_downloader.go index 3feb5b702..bc8ced3bc 100644 --- a/pkg/downloader/chart_downloader.go +++ b/pkg/downloader/chart_downloader.go @@ -21,6 +21,7 @@ import ( "net/url" "os" "path/filepath" + "regexp" "strings" "github.com/Masterminds/semver/v3" @@ -141,36 +142,41 @@ func (c *ChartDownloader) DownloadTo(ref, version, dest string) (string, *proven return destfile, ver, nil } +var sha256re = regexp.MustCompile(`^sha256:[0-9a-fA-F]{64}$`) + func (c *ChartDownloader) getOciURI(ref, version string, u *url.URL) (*url.URL, error) { - var tag string var err error // Evaluate whether an explicit version has been provided. Otherwise, determine version to use - _, errSemVer := semver.NewVersion(version) - if errSemVer == nil { - tag = version + if sha256re.MatchString(version) { + u.Path = fmt.Sprintf("%s@%s", u.Path, version) } else { - // Retrieve list of repository tags - tags, err := c.RegistryClient.Tags(strings.TrimPrefix(ref, fmt.Sprintf("%s://", registry.OCIScheme))) - if err != nil { - return nil, err - } - if len(tags) == 0 { - return nil, errors.Errorf("Unable to locate any tags in provided repository: %s", ref) - } + var tag string + _, errSemVer := semver.NewVersion(version) + if errSemVer == nil { + tag = version + } else { + // Retrieve list of repository tags + tags, err := c.RegistryClient.Tags(strings.TrimPrefix(ref, fmt.Sprintf("%s://", registry.OCIScheme))) + if err != nil { + return nil, err + } + if len(tags) == 0 { + return nil, errors.Errorf("Unable to locate any tags in provided repository: %s", ref) + } - // Determine if version provided - // If empty, try to get the highest available tag - // If exact version, try to find it - // If semver constraint string, try to find a match - tag, err = registry.GetTagMatchingVersionOrConstraint(tags, version) - if err != nil { - return nil, err + // Determine if version provided + // If empty, try to get the highest available tag + // If exact version, try to find it + // If semver constraint string, try to find a match + tag, err = registry.GetTagMatchingVersionOrConstraint(tags, version) + if err != nil { + return nil, err + } } + u.Path = fmt.Sprintf("%s:%s", u.Path, tag) } - u.Path = fmt.Sprintf("%s:%s", u.Path, tag) - return u, err } @@ -182,7 +188,9 @@ func (c *ChartDownloader) getOciURI(ref, version string, u *url.URL) (*url.URL, // A reference may be an HTTP URL, an oci reference URL, a 'reponame/chartname' // reference, or a local path. // -// A version is a SemVer string (1.2.3-beta.1+f334a6789). +// A version is a SemVer string (1.2.3-beta.1+f334a6789) or a SHA256 digest of +// the chart's manifest +// (sha256:d234555386402a5867ef0169fefe5486858b6d8d209eaf32fd26d29b16807fd6). // // - For fully qualified URLs, the version will be ignored (since URLs aren't versioned) // - For a chart reference diff --git a/pkg/downloader/chart_downloader_test.go b/pkg/downloader/chart_downloader_test.go index f70a56422..6d1256efa 100644 --- a/pkg/downloader/chart_downloader_test.go +++ b/pkg/downloader/chart_downloader_test.go @@ -40,6 +40,7 @@ func TestResolveChartRef(t *testing.T) { {name: "full URL", ref: "http://example.com/foo-1.2.3.tgz", expect: "http://example.com/foo-1.2.3.tgz"}, {name: "full URL, HTTPS", ref: "https://example.com/foo-1.2.3.tgz", expect: "https://example.com/foo-1.2.3.tgz"}, {name: "full URL, with authentication", ref: "http://username:password@example.com/foo-1.2.3.tgz", expect: "http://username:password@example.com/foo-1.2.3.tgz"}, + {name: "full URL, with sha256", ref: "oci://example.com/foo/bar", version: "sha256:d234555386402a5867ef0169fefe5486858b6d8d209eaf32fd26d29b16807fd6", expect: "oci://example.com/foo/bar@sha256:d234555386402a5867ef0169fefe5486858b6d8d209eaf32fd26d29b16807fd6"}, {name: "reference, testing repo", ref: "testing/alpine", expect: "http://example.com/alpine-1.2.3.tgz"}, {name: "reference, version, testing repo", ref: "testing/alpine", version: "0.2.0", expect: "http://example.com/alpine-0.2.0.tgz"}, {name: "reference, version, malformed repo", ref: "malformed/alpine", version: "1.2.3", expect: "http://dl.example.com/alpine-1.2.3.tgz"},