From 72652c0f44ba1e471e3491c62162613b433bbe71 Mon Sep 17 00:00:00 2001 From: Luis Davim Date: Fri, 17 Sep 2021 09:26:43 +0100 Subject: [PATCH] feat: support the new OCI implementation from helm 3.7.0 Signed-off-by: Luis Davim --- internal/app/helm_helpers.go | 11 ++++++++++ internal/app/utils.go | 40 ++++++++++++++++++++++++++++++++---- internal/app/utils_test.go | 40 ++++++++++++++++++++++++++++++++++++ 3 files changed, 87 insertions(+), 4 deletions(-) diff --git a/internal/app/helm_helpers.go b/internal/app/helm_helpers.go index 598f906d..43af55b2 100644 --- a/internal/app/helm_helpers.go +++ b/internal/app/helm_helpers.go @@ -111,6 +111,7 @@ func updateChartDep(chartPath string) error { } // helmExportChart pulls chart and exports it to the specified destination +// this is only compatible with hlem versions lower than 3.0.7 func helmExportChart(chart, dest string) error { cmd := helmCmd([]string{"chart", "pull", chart}, "Pulling chart [ "+chart+" ] to local registry cache") if _, err := cmd.Exec(); err != nil { @@ -123,6 +124,16 @@ func helmExportChart(chart, dest string) error { return nil } +// helmPullChart pulls chart and exports it to the specified destination +// this should only be used with helm versions greater or equal to 3.7.0 +func helmPullChart(chart, dest string) error { + cmd := helmCmd([]string{"chart", "pull", chart, "-d", dest}, "Pulling chart [ "+chart+" ] to "+dest) + if _, err := cmd.Exec(); err != nil { + return err + } + return nil +} + // addHelmRepos adds repositories to Helm if they don't exist already. // Helm does not mind if a repo with the same name exists. It treats it as an update. func addHelmRepos(repos map[string]string) error { diff --git a/internal/app/utils.go b/internal/app/utils.go index 78cc3df9..da5ce729 100644 --- a/internal/app/utils.go +++ b/internal/app/utils.go @@ -15,6 +15,7 @@ import ( "strconv" "strings" "time" + "unicode/utf8" "github.com/Masterminds/semver" "github.com/Praqma/helmsman/internal/aws" @@ -198,6 +199,25 @@ func sliceContains(slice []string, s string) bool { return false } +// replaceAtIndex replaces the charecter at the given index in the string with the given rune +func replaceAtIndex(in string, r rune, i int) (string, error) { + if i < 0 || i >= utf8.RuneCountInString(in) { + return in, fmt.Errorf("index out of bounds") + } + out := []rune(in) + out[i] = r + return string(out), nil +} + +// ociRefToFilename computes the helm package filename for a given OCI ref +func ociRefToFilename(ref string) (string, error) { + var err error + fileName := filepath.Base(ref) + i := strings.LastIndex(fileName, ":") + fileName, err = replaceAtIndex(fileName, '-', i) + return fmt.Sprintf("%s.tgz", fileName), err +} + // downloadFile downloads a file from a URL, GCS, Azure or AWS buckets and saves it with a // given outfile name and in a given dir // If the file path is local file system path, it returns the absolute path to the file @@ -210,11 +230,23 @@ func downloadFile(file string, dir string, outfile string) string { switch u.Scheme { case "oci": dest := filepath.Dir(outfile) - fileName := strings.Split(filepath.Base(file), ":")[0] - if err := helmExportChart(strings.ReplaceAll(file, "oci://", ""), dest); err != nil { - log.Fatal(err.Error()) + switch { + case checkHelmVersion("<3.7.0"): + fileName := strings.Split(filepath.Base(file), ":")[0] + if err := helmExportChart(strings.ReplaceAll(file, "oci://", ""), dest); err != nil { + log.Fatal(err.Error()) + } + return filepath.Join(dest, fileName) + default: + fileName, err := ociRefToFilename(file) + if err != nil { + log.Fatal(err.Error()) + } + if err := helmPullChart(file, dest); err != nil { + log.Fatal(err.Error()) + } + return filepath.Join(dest, fileName) } - return filepath.Join(dest, fileName) case "https", "http": if err := downloadFileFromURL(file, outfile); err != nil { log.Fatal(err.Error()) diff --git a/internal/app/utils_test.go b/internal/app/utils_test.go index ae079c70..1e282016 100644 --- a/internal/app/utils_test.go +++ b/internal/app/utils_test.go @@ -5,6 +5,46 @@ import ( "testing" ) +func TestOciRefToFilename(t *testing.T) { + tests := []struct { + name string + in string + want string + }{ + { + name: "no_repo", + in: "my-chart:1.2.3", + want: "my-chart-1.2.3.tgz", + }, + { + name: "two_colons", + in: "my:chart:1.2.3", + want: "my:chart-1.2.3.tgz", + }, + { + name: "with_Host", + in: "my-repo.example.com/charts/my-chart:1.2.3", + want: "my-chart-1.2.3.tgz", + }, + { + name: "full_url", + in: "oci://my-repo.example.com/charts/my-chart:1.2.3", + want: "my-chart-1.2.3.tgz", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ociRefToFilename(tt.in) + if err != nil { + t.Errorf("ociRefToFilename() unexpected error: %v", err) + } + if got != tt.want { + t.Errorf("ociRefToFilename() got = %v, want %v", got, tt.want) + } + }) + } +} + func Test_isOfType(t *testing.T) { type args struct { filename string