From 832cb6d51f20046d6625849409c480d94a578769 Mon Sep 17 00:00:00 2001 From: Russ Frizzell-Carlton Date: Tue, 14 Jan 2025 12:04:31 -0500 Subject: [PATCH 1/7] Added support for legacy Helm chart repositories This change adds support for legacy (non-OCI) Helm chart repositories in the least-disruptive way to the existing API. --- helm/main.go | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/helm/main.go b/helm/main.go index bc38104..697471b 100644 --- a/helm/main.go +++ b/helm/main.go @@ -58,7 +58,7 @@ func (h *Helm) Version( return strings.TrimSpace(version), nil } -// Packages and pushes a Helm chart to a specified OCI-compatible registry with authentication. +// Packages and pushes a Helm chart to a specified OCI-compatible (by default) registry with authentication. // // Returns true if the chart was successfully pushed, or false if the chart already exists, with error handling for push failures. // @@ -70,6 +70,16 @@ func (h *Helm) Version( // --username $REGISTRY_HELM_USER \ // --password env:REGISTRY_HELM_PASSWORD \ // --directory ./examples/testdata/mychart/ +// +// Example usage for pushing to a legacy (non-OCI) Helm repository: +// +// dagger call package-push \ +// --registry registry.puzzle.ch \ +// --repository helm \ +// --username $REGISTRY_HELM_USER \ +// --password env:REGISTRY_HELM_PASSWORD \ +// --directory ./examples/testdata/mychart/ \ +// --oci false func (h *Helm) PackagePush( // method call context ctx context.Context, @@ -83,11 +93,15 @@ func (h *Helm) PackagePush( username string, // registry login password password *dagger.Secret, + // Set to false to use legacy Helm repository + // +optional + // +default=true + oci bool, ) (bool, error) { opts := PushOpts{ Registry: registry, Repository: repository, - Oci: true, + Oci: oci, Username: username, Password: password, } From 759160d72aae95003263f8ec58963f4c6fa5bd42 Mon Sep 17 00:00:00 2001 From: "Frizzell-Carlton, Russ" Date: Tue, 21 Jan 2025 07:26:30 -0500 Subject: [PATCH 2/7] Added code to support uploading chart with curl. --- helm/main.go | 56 ++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 46 insertions(+), 10 deletions(-) diff --git a/helm/main.go b/helm/main.go index 697471b..f640ff1 100644 --- a/helm/main.go +++ b/helm/main.go @@ -33,6 +33,14 @@ func (p PushOpts) getChartFqdn(name string) string { return fmt.Sprintf("%s/%s/%s", p.Registry, p.Repository, name) } +func (p PushOpts) getChartFqdnForCurl(name string) string { + baseString := p.getChartFqdn(name) + if p.Oci { + return baseString + } + return fmt.Sprintf("https://%s", baseString) +} + func (p PushOpts) getRepoFqdn() string { if p.Oci { return fmt.Sprintf("oci://%s/%s", p.Registry, p.Repository) @@ -71,15 +79,17 @@ func (h *Helm) Version( // --password env:REGISTRY_HELM_PASSWORD \ // --directory ./examples/testdata/mychart/ // -// Example usage for pushing to a legacy (non-OCI) Helm repository: +// Example usage for pushing to a legacy (non-OCI) Helm repository assuming the repo name is 'helm'. If your target URL +// requires a vendor-specific path prefix then add it before the repository name. If you want to put the chart in a subpath in +// the repository, then append that to the end of the repository name. // // dagger call package-push \ -// --registry registry.puzzle.ch \ -// --repository helm \ -// --username $REGISTRY_HELM_USER \ -// --password env:REGISTRY_HELM_PASSWORD \ -// --directory ./examples/testdata/mychart/ \ -// --oci false +// --registry registry.puzzle.ch \ +// --repository vendor-specific-prefix/helm/optional/subpath/in/repository \ +// --username $REGISTRY_HELM_USER \ +// --password env:REGISTRY_HELM_PASSWORD \ +// --directory ./examples/testdata/mychart/ \ +// --oci false func (h *Helm) PackagePush( // method call context ctx context.Context, @@ -93,7 +103,6 @@ func (h *Helm) PackagePush( username string, // registry login password password *dagger.Secret, - // Set to false to use legacy Helm repository // +optional // +default=true oci bool, @@ -151,11 +160,38 @@ func (h *Helm) PackagePush( return false, nil } - _, err = c.WithExec([]string{"helm", "dependency", "update", "."}). + c, err = c.WithExec([]string{"helm", "dependency", "update", "."}). WithExec([]string{"helm", "package", "."}). WithExec([]string{"sh", "-c", "ls"}). - WithExec([]string{"helm", "push", fmt.Sprintf("%s-%s.tgz", name, version), opts.getRepoFqdn()}). Sync(ctx) + + if err != nil { + return false, err + } + + pkgFile := fmt.Sprintf("%s-%s.tgz", name, version) + + if oci { + curlCmd := []string{ + `curl --variable %REGISTRY_USERNAME`, + `--variable %REGISTRY_PASSWORD`, + `--expand-user "{{REGISTRY_USERNAME}}:{{REGISTRY_PASSWORD}}"`, + `-T ${PKGFILE}`, + opts.getChartFqdnForCurl(name), + } + + c, err = c. + WithEnvVariable("REGISTRY_USERNAME", opts.Username). + WithEnvVariable("PKGFILE", pkgFile). + WithSecretVariable("REGISTRY_PASSWORD", opts.Password). + WithExec([]string{"sh", "-c", strings.Join(curlCmd, " ")}). + Sync(ctx) + } else { + c, err = c. + WithExec([]string{"helm", "push", pkgFile, opts.getRepoFqdn()}). + Sync(ctx) + } + if err != nil { return false, err } From 41339f3db9b14bbc70dd67d9f9d31666f7c93065 Mon Sep 17 00:00:00 2001 From: "Frizzell-Carlton, Russ" Date: Sat, 25 Jan 2025 11:41:57 -0500 Subject: [PATCH 3/7] Added non-OCI chart exists logic and refactored that logic for both into a private function. --- helm/dagger.json | 3 +- helm/main.go | 136 ++++++++++++++++++++++++++++++++--------------- 2 files changed, 95 insertions(+), 44 deletions(-) diff --git a/helm/dagger.json b/helm/dagger.json index 57366b6..e9f91a4 100644 --- a/helm/dagger.json +++ b/helm/dagger.json @@ -1,5 +1,6 @@ { "name": "helm", + "engineVersion": "v0.14.0", "sdk": "go", - "engineVersion": "v0.14.0" + "source": "." } diff --git a/helm/main.go b/helm/main.go index f640ff1..2bdd23f 100644 --- a/helm/main.go +++ b/helm/main.go @@ -26,26 +26,20 @@ type PushOpts struct { Password *dagger.Secret } -func (p PushOpts) getChartFqdn(name string) string { +func (p PushOpts) getProtocol() string { if p.Oci { - return fmt.Sprintf("oci://%s/%s/%s", p.Registry, p.Repository, name) + return "oci" + } else { + return "https" } - return fmt.Sprintf("%s/%s/%s", p.Registry, p.Repository, name) } -func (p PushOpts) getChartFqdnForCurl(name string) string { - baseString := p.getChartFqdn(name) - if p.Oci { - return baseString - } - return fmt.Sprintf("https://%s", baseString) +func (p PushOpts) getRepoFqdn() string { + return fmt.Sprintf("%s://%s/%s", p.getProtocol(), p.Registry, p.Repository) } -func (p PushOpts) getRepoFqdn() string { - if p.Oci { - return fmt.Sprintf("oci://%s/%s", p.Registry, p.Repository) - } - return fmt.Sprintf("%s/%s", p.Registry, p.Repository) +func (p PushOpts) getChartFqdn(name string) string { + return fmt.Sprintf("%s/%s", p.getRepoFqdn(), name) } // Get and display the version of the Helm Chart located inside the given directory. @@ -80,7 +74,8 @@ func (h *Helm) Version( // --directory ./examples/testdata/mychart/ // // Example usage for pushing to a legacy (non-OCI) Helm repository assuming the repo name is 'helm'. If your target URL -// requires a vendor-specific path prefix then add it before the repository name. If you want to put the chart in a subpath in +// requires a vendor-specific path prefix (for example, JFrog Artifactory usually requires 'artifactory' before the repo +// name) then add it before the repository name. If you want to put the chart in a subpath in // the repository, then append that to the end of the repository name. // // dagger call package-push \ @@ -89,7 +84,7 @@ func (h *Helm) Version( // --username $REGISTRY_HELM_USER \ // --password env:REGISTRY_HELM_PASSWORD \ // --directory ./examples/testdata/mychart/ \ -// --oci false +// --use-non-oci-helm-repo true func (h *Helm) PackagePush( // method call context ctx context.Context, @@ -103,14 +98,15 @@ func (h *Helm) PackagePush( username string, // registry login password password *dagger.Secret, + // use a non-OCI (legacy) Helm repository // +optional - // +default=true - oci bool, + // +default=false + useNonOciHelmRepo bool, // Dev note: We are forced to use default=false due to https://github.com/dagger/dagger/issues/8810 ) (bool, error) { opts := PushOpts{ Registry: registry, Repository: repository, - Oci: oci, + Oci: !useNonOciHelmRepo, Username: username, Password: password, } @@ -133,31 +129,15 @@ func (h *Helm) PackagePush( } name = strings.TrimSpace(name) + pkgFile := fmt.Sprintf("%s-%s.tgz", name, version) - c, err = c. - WithEnvVariable("REGISTRY_URL", opts.Registry). - WithEnvVariable("REGISTRY_USERNAME", opts.Username). - WithSecretVariable("REGISTRY_PASSWORD", opts.Password). - WithExec([]string{"sh", "-c", `echo ${REGISTRY_PASSWORD} | helm registry login ${REGISTRY_URL} --username ${REGISTRY_USERNAME} --password-stdin`}). - Sync(ctx) - if err != nil { - return false, err - } - - //TODO: Refactor with return - c, err = c.WithExec([]string{"sh", "-c", fmt.Sprintf("helm show chart %s --version %s; echo -n $? > /ec", opts.getChartFqdn(name), version)}).Sync(ctx) - if err != nil { - return false, err - } - - exc, err := c.File("/ec").Contents(ctx) + chartExists, err := h.doesChartExistOnRepo(ctx, c, &opts, name, version) if err != nil { return false, err } - if exc == "0" { - //Chart exists - return false, nil + if chartExists { + return false, fmt.Errorf("Chart already exists on server.") } c, err = c.WithExec([]string{"helm", "dependency", "update", "."}). @@ -169,15 +149,13 @@ func (h *Helm) PackagePush( return false, err } - pkgFile := fmt.Sprintf("%s-%s.tgz", name, version) - - if oci { + if useNonOciHelmRepo { curlCmd := []string{ `curl --variable %REGISTRY_USERNAME`, `--variable %REGISTRY_PASSWORD`, `--expand-user "{{REGISTRY_USERNAME}}:{{REGISTRY_PASSWORD}}"`, `-T ${PKGFILE}`, - opts.getChartFqdnForCurl(name), + opts.getChartFqdn(name), } c, err = c. @@ -253,6 +231,78 @@ func (h *Helm) Lint( return out, nil } +func (h *Helm) doesChartExistOnRepo( + ctx context.Context, + c *dagger.Container, + opts *PushOpts, + name string, + version string, +) (bool, error) { + if opts.Oci { + c, err := c. + WithEnvVariable("REGISTRY_URL", opts.Registry). + WithEnvVariable("REGISTRY_USERNAME", opts.Username). + WithSecretVariable("REGISTRY_PASSWORD", opts.Password). + WithExec([]string{"sh", "-c", `echo ${REGISTRY_PASSWORD} | helm registry login ${REGISTRY_URL} --username ${REGISTRY_USERNAME} --password-stdin`}). + Sync(ctx) + if err != nil { + return false, err + } + + //TODO: Refactor with return + c, err = c.WithExec([]string{"sh", "-c", fmt.Sprintf("helm show chart %s --version %s; echo -n $? > /ec", opts.getChartFqdn(name), version)}).Sync(ctx) + if err != nil { + return false, err + } + + exc, err := c.File("/ec").Contents(ctx) + if err != nil { + return false, err + } + + if exc == "0" { + //Chart exists + return true, nil + } + + return false, nil + } + // else non-OCI + pkgFile := fmt.Sprintf("%s-%s.tgz", name, version) + curlCmd := []string{ + `curl --variable %REGISTRY_USERNAME`, + `--variable %REGISTRY_PASSWORD`, + `--expand-user "{{REGISTRY_USERNAME}}:{{REGISTRY_PASSWORD}}"`, + opts.getChartFqdn(pkgFile), + `--output /dev/null`, + `--silent -Iw '%{http_code}`, + } + + httpCode, err := c. + WithEnvVariable("REGISTRY_USERNAME", opts.Username). + WithEnvVariable("PKGFILE", pkgFile). + WithSecretVariable("REGISTRY_PASSWORD", opts.Password). + WithExec([]string{"sh", "-c", strings.Join(curlCmd, " ")}). + Stdout(ctx) + + if err != nil { + return false, err + } + + httpCode = strings.TrimSpace(httpCode) + + if httpCode == "200" { + // Chart exists + return true, nil + } + + if httpCode == "404" { + return false, nil + } + + return false, fmt.Errorf("Server returned error code %s checking for chart existence on server.", httpCode) +} + func (h *Helm) hasMissingDependencies( // method call context ctx context.Context, From 4686d4a207f4aedcbfcc43b469baa2c94c9cfe07 Mon Sep 17 00:00:00 2001 From: "Frizzell-Carlton, Russ" Date: Sat, 25 Jan 2025 11:46:47 -0500 Subject: [PATCH 4/7] Added comment. --- helm/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helm/main.go b/helm/main.go index 2bdd23f..792b651 100644 --- a/helm/main.go +++ b/helm/main.go @@ -269,6 +269,7 @@ func (h *Helm) doesChartExistOnRepo( } // else non-OCI pkgFile := fmt.Sprintf("%s-%s.tgz", name, version) + // Do a GET of the chart but with response headers only so we do not download the chart curlCmd := []string{ `curl --variable %REGISTRY_USERNAME`, `--variable %REGISTRY_PASSWORD`, @@ -280,7 +281,6 @@ func (h *Helm) doesChartExistOnRepo( httpCode, err := c. WithEnvVariable("REGISTRY_USERNAME", opts.Username). - WithEnvVariable("PKGFILE", pkgFile). WithSecretVariable("REGISTRY_PASSWORD", opts.Password). WithExec([]string{"sh", "-c", strings.Join(curlCmd, " ")}). Stdout(ctx) From 3ea5aa0f95c04f48d260dcd8afadbfba12443a49 Mon Sep 17 00:00:00 2001 From: "Frizzell-Carlton, Russ" Date: Sat, 25 Jan 2025 12:35:40 -0500 Subject: [PATCH 5/7] Final fixes. Tests for non-OCI successful. --- helm/main.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/helm/main.go b/helm/main.go index 792b651..58db429 100644 --- a/helm/main.go +++ b/helm/main.go @@ -152,15 +152,15 @@ func (h *Helm) PackagePush( if useNonOciHelmRepo { curlCmd := []string{ `curl --variable %REGISTRY_USERNAME`, - `--variable %REGISTRY_PASSWORD`, - `--expand-user "{{REGISTRY_USERNAME}}:{{REGISTRY_PASSWORD}}"`, - `-T ${PKGFILE}`, - opts.getChartFqdn(name), + `--variable %REGISTRY_PASSWORD`, + `--expand-user "{{REGISTRY_USERNAME}}:{{REGISTRY_PASSWORD}}"`, + `-T`, + pkgFile, + opts.getRepoFqdn() + "/", } c, err = c. WithEnvVariable("REGISTRY_USERNAME", opts.Username). - WithEnvVariable("PKGFILE", pkgFile). WithSecretVariable("REGISTRY_PASSWORD", opts.Password). WithExec([]string{"sh", "-c", strings.Join(curlCmd, " ")}). Sync(ctx) @@ -276,7 +276,7 @@ func (h *Helm) doesChartExistOnRepo( `--expand-user "{{REGISTRY_USERNAME}}:{{REGISTRY_PASSWORD}}"`, opts.getChartFqdn(pkgFile), `--output /dev/null`, - `--silent -Iw '%{http_code}`, + `--silent -Iw '%{http_code}'`, } httpCode, err := c. From 43caffb236eeb316deb9dbd7772a873d4ccedcf0 Mon Sep 17 00:00:00 2001 From: "Frizzell-Carlton, Russ" Date: Sat, 25 Jan 2025 12:45:25 -0500 Subject: [PATCH 6/7] Fixed documentation to add = --- helm/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helm/main.go b/helm/main.go index 58db429..64fb532 100644 --- a/helm/main.go +++ b/helm/main.go @@ -84,7 +84,7 @@ func (h *Helm) Version( // --username $REGISTRY_HELM_USER \ // --password env:REGISTRY_HELM_PASSWORD \ // --directory ./examples/testdata/mychart/ \ -// --use-non-oci-helm-repo true +// --use-non-oci-helm-repo=true func (h *Helm) PackagePush( // method call context ctx context.Context, From 70722ec73725d3eb4a87f8bb0b6b52c0f63f529c Mon Sep 17 00:00:00 2001 From: "Frizzell-Carlton, Russ" Date: Thu, 30 Jan 2025 08:15:38 -0500 Subject: [PATCH 7/7] Removed the return of an error for chart exists. --- helm/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helm/main.go b/helm/main.go index 64fb532..7a2af4c 100644 --- a/helm/main.go +++ b/helm/main.go @@ -137,7 +137,7 @@ func (h *Helm) PackagePush( } if chartExists { - return false, fmt.Errorf("Chart already exists on server.") + return false, nil } c, err = c.WithExec([]string{"helm", "dependency", "update", "."}).