diff --git a/helm/main.go b/helm/main.go index 62bac72..bd64273 100644 --- a/helm/main.go +++ b/helm/main.go @@ -24,6 +24,8 @@ type PushOpts struct { Oci bool `yaml:"oci"` Username string `yaml:"username"` Password *dagger.Secret + Version string `yaml:"version"` + AppVersion string `yaml:"appVersion"` } func (p PushOpts) getProtocol() string { @@ -42,6 +44,29 @@ func (p PushOpts) getChartFqdn(name string) string { return fmt.Sprintf("%s/%s", p.getRepoFqdn(), name) } +func (p PushOpts) getHelmPkgCmd() []string { + helmPkgCmd := []string{"helm", "package", "."} + if p.Version != "" { + helmPkgCmd = append(helmPkgCmd, "--version", p.Version) + } + if p.AppVersion != "" { + helmPkgCmd = append(helmPkgCmd, "--app-version", p.AppVersion) + } + return helmPkgCmd +} + +// Get and display the name of the Helm Chart located inside the given directory. +// +// Example usage: dagger call name --directory ./helm/examples/testdata/mychart/ +func (h *Helm) Name( + // method call context + ctx context.Context, + // directory that contains the Helm Chart + directory *dagger.Directory, +) (string, error) { + return h.queryChartWithYq(ctx, directory, ".name") +} + // Get and display the version of the Helm Chart located inside the given directory. // // Example usage: dagger call version --directory ./helm/examples/testdata/mychart/ @@ -51,13 +76,19 @@ func (h *Helm) Version( // directory that contains the Helm Chart directory *dagger.Directory, ) (string, error) { - c := h.createContainer(directory) - version, err := c.WithExec([]string{"sh", "-c", "helm show chart . | yq eval '.version' -"}).Stdout(ctx) - if err != nil { - return "", err - } + return h.queryChartWithYq(ctx, directory, ".version") +} - return strings.TrimSpace(version), nil +// Get and display the appVersion of the Helm Chart located inside the given directory. +// +// Example usage: dagger call app-version --directory ./helm/examples/testdata/mychart/ +func (h *Helm) AppVersion( + // method call context + ctx context.Context, + // directory that contains the Helm Chart + directory *dagger.Directory, +) (string, error) { + return h.queryChartWithYq(ctx, directory, ".appVersion") } // Packages and pushes a Helm chart to a specified OCI-compatible (by default) registry with authentication. @@ -102,13 +133,27 @@ func (h *Helm) PackagePush( // +optional // +default=false useNonOciHelmRepo bool, // Dev note: We are forced to use default=false due to https://github.com/dagger/dagger/issues/8810 + // set chart version when packaging + // +optional + // default="" + setVersionTo string, + // set chart appVersion when packaging + // +optional + // default="" + setAppVersionTo string, ) (bool, error) { + version, err := h.valueOrQueryChart(setVersionTo, ctx, directory, ".version") + if err != nil { + return false, err + } opts := PushOpts{ Registry: registry, Repository: repository, Oci: !useNonOciHelmRepo, Username: username, Password: password, + Version: version, + AppVersion: setAppVersionTo, } fmt.Fprintf(os.Stdout, "☸️ Helm package and Push") @@ -116,19 +161,12 @@ func (h *Helm) PackagePush( From("registry.puzzle.ch/cicd/alpine-base:latest"). WithDirectory("/helm", directory). WithWorkdir("/helm") - version, err := c.WithExec([]string{"sh", "-c", "helm show chart . | yq eval '.version' -"}).Stdout(ctx) - if err != nil { - return false, err - } - version = strings.TrimSpace(version) - - name, err := c.WithExec([]string{"sh", "-c", "helm show chart . | yq eval '.name' -"}).Stdout(ctx) + name, err := h.queryChartWithYq(ctx, directory, ".name") if err != nil { return false, err } - name = strings.TrimSpace(name) pkgFile := fmt.Sprintf("%s-%s.tgz", name, version) chartExists, err := h.doesChartExistOnRepo(ctx, c, &opts, name, version) @@ -141,7 +179,7 @@ func (h *Helm) PackagePush( } c, err = c.WithExec([]string{"helm", "dependency", "update", "."}). - WithExec([]string{"helm", "package", "."}). + WithExec(opts.getHelmPkgCmd()). WithExec([]string{"sh", "-c", "ls"}). Sync(ctx) @@ -310,13 +348,29 @@ func (h *Helm) doesChartExistOnRepo( return false, fmt.Errorf("Server returned error code %s checking for chart existence on server.", httpCode) } +func (h *Helm) queryChartWithYq( + // method call context + ctx context.Context, + // directory that contains the Helm Chart + directory *dagger.Directory, + yqQuery string, +) (string, error) { + c := h.createContainer(directory) + version, err := c.WithExec([]string{"sh", "-c", fmt.Sprintf(`helm show chart . | yq eval '%s' -`, yqQuery)}).Stdout(ctx) + if err != nil { + return "", err + } + + return strings.TrimSpace(version), nil +} + func (h *Helm) hasMissingDependencies( // method call context ctx context.Context, // directory that contains the Helm Chart directory *dagger.Directory, ) bool { - _, err := h.createContainer(directory).WithExec([]string{"sh", "-c", "helm dep list | grep missing"}).Stdout(ctx) + _, err := h.createContainer(directory).WithExec([]string{"sh", "-c", "helm dependency list | grep missing"}).Stdout(ctx) return err == nil } @@ -325,7 +379,7 @@ func (h *Helm) dependencyUpdate( directory *dagger.Directory, ) *dagger.Directory { c := h.createContainer(directory) - return c.WithExec([]string{"sh", "-c", "helm dep update"}).Directory("charts") + return c.WithExec([]string{"sh", "-c", "helm dependency update"}).Directory("charts") } func (h *Helm) createContainer( @@ -338,3 +392,16 @@ func (h *Helm) createContainer( WithWorkdir("/helm"). WithoutEntrypoint() } + +// coalesce returns the first non-empty string from the provided arguments. +func (h *Helm) valueOrQueryChart( + theValue string, + ctx context.Context, + directory *dagger.Directory, + yqQuery string, +) (string, error) { + if theValue != "" { + return strings.TrimSpace(theValue), nil + } + return h.queryChartWithYq(ctx, directory, yqQuery) +} diff --git a/tests/charts.go b/tests/charts.go new file mode 100644 index 0000000..d898ea3 --- /dev/null +++ b/tests/charts.go @@ -0,0 +1,109 @@ +package main + +import ( + "dagger/go/internal/dagger" + "fmt" +) + +const originalVersion = "0.1.1" +const originalAppVersion = "1.16.0" + +const mychartName = "dagger-module-helm-test" +const mychartDir = "./testdata/mychart/" +const mychartTemplate = ` +apiVersion: v2 +name: %s +description: A Helm chart +type: application +version: %s +appVersion: "%s" +` +const mydependentchartName = "dagger-module-helm-test-with-dependency" +const mydependentchartDir = "./testdata/mydependentchart/" +const mydependentchartTemplate = ` +apiVersion: v2 +name: %s +description: A Helm chart +type: application +version: %s +appVersion: "%s" +dependencies: + - name: dependency-track + version: 1.8.1 + repository: https://puzzle.github.io/dependencytrack-helm/ +` + +// Chart represents one of our test Helm charts +type Chart struct{ + Name string + Directory string + ContentTemplate string + OriginalContent string +} + +// Use the dagger module to get the dagger directory of the chart +func (c *Chart) DaggerDirectory( +) *dagger.Directory { + return dag.CurrentModule().Source().Directory(c.Directory) +} + +// Modify the chart by changing both version and appVersion +func (c *Chart) WithVersionAndAppVersion( + version string, + appVersion string, +) *Chart { + if version == "" { + version = originalVersion + } + if appVersion == "" { + appVersion = originalAppVersion + } + c.DaggerDirectory(). + WithoutFile("Chart.yaml"). + WithNewFile("Chart.yaml", + fmt.Sprintf(c.ContentTemplate, c.Name, version, appVersion)) + return c +} + +// Modify the chart by appending a suffix to the original version +func (c *Chart) WithOriginalVersionSuffix( + suffix string, +) *Chart { + versionWithSuffix := fmt.Sprintf("%s-%s", originalVersion, suffix) + return c.WithVersion(versionWithSuffix) +} + +// Modify the chart by changing only the version +func (c *Chart) WithVersion( + version string, +) *Chart { + return c.WithVersionAndAppVersion(version, "") +} + +// Modify the chart by changing only the appVersion +func (c *Chart) WithAppVersion( + appVersion string, +) *Chart { + return c.WithVersionAndAppVersion("", appVersion) +} + +// Reset the chart to its original content +func (c *Chart) Reset( +) *Chart { + return c.WithVersionAndAppVersion("", "") +} + +var ( + Mychart = Chart{ + Name: mychartName, + Directory: mychartDir, + ContentTemplate: mychartTemplate, + OriginalContent: fmt.Sprintf(mychartTemplate, mychartName, originalVersion, originalAppVersion), + } + Mydependentchart = Chart{ + Name: mydependentchartName, + Directory: mydependentchartDir, + ContentTemplate: mydependentchartTemplate, + OriginalContent: fmt.Sprintf(mydependentchartTemplate, mydependentchartName, originalVersion, originalAppVersion), + } +) diff --git a/tests/main.go b/tests/main.go index db8ed65..f8149ad 100644 --- a/tests/main.go +++ b/tests/main.go @@ -16,6 +16,7 @@ func (m *Go) All(ctx context.Context) error { p := pool.New().WithErrors().WithContext(ctx) p.Go(m.HelmVersion) + p.Go(m.HelmAppVersion) p.Go(m.HelmTest) p.Go(m.HelmLint) p.Go(m.HelmLintWithArg) @@ -24,6 +25,8 @@ func (m *Go) All(ctx context.Context) error { p.Go(m.HelmPackagePush) p.Go(m.HelmPackagePushNonOci) p.Go(m.HelmPackagePushWithExistingChart) + p.Go(m.HelmPackagePushWithVersion) + p.Go(m.HelmPackagePushWithAppVersion) return p.Wait() } @@ -32,8 +35,8 @@ func (m *Go) HelmVersion( // method call context ctx context.Context, ) error { - const expected = "0.1.1" - directory := dag.CurrentModule().Source().Directory("./testdata/mychart/") + const expected = originalVersion + directory := Mychart.DaggerDirectory() version, err := dag.Helm().Version(ctx, directory) if err != nil { @@ -47,6 +50,25 @@ func (m *Go) HelmVersion( return nil } +func (m *Go) HelmAppVersion( + // method call context + ctx context.Context, +) error { + const expected = originalAppVersion + directory := Mychart.DaggerDirectory() + version, err := dag.Helm().AppVersion(ctx, directory) + + if err != nil { + return err + } + + if version != expected { + return fmt.Errorf("expected %q, got %q", expected, version) + } + + return nil +} + // requires valid credentials, called from Github actions func (m *Go) HelmPackagepush( // method call context @@ -62,13 +84,19 @@ func (m *Go) HelmPackagepush( ) error { randomString := fmt.Sprintf("%d", time.Now().UnixNano())[0:8] // directory that contains the Helm Chart - directory := m.chartWithVersionSuffix(dag.CurrentModule().Source().Directory("./testdata/mychart/"), randomString) + directory := Mychart. + WithOriginalVersionSuffix(randomString). + DaggerDirectory() + _, err := dag.Helm().PackagePush(ctx, directory, registry, repository, username, password) if err != nil { return err } + // Reset version back to original + Mychart.Reset() + return nil } @@ -77,7 +105,7 @@ func (m *Go) HelmPackagePush( ctx context.Context, ) error { // directory that contains the Helm Chart - directory := dag.CurrentModule().Source().Directory("./testdata/mychart/") + directory := Mychart.DaggerDirectory() _, err := dag.Helm().PackagePush(ctx, directory, "ttl.sh", "helm", "username", dag.SetSecret("password", "secret")) if err != nil { @@ -92,7 +120,7 @@ func (m *Go) HelmPackagePushNonOci( ctx context.Context, ) error { // directory that contains the Helm Chart - directory := dag.CurrentModule().Source().Directory("./testdata/mychart/") + directory := Mychart.DaggerDirectory() _, err := dag.Helm().PackagePush(ctx, directory, "ttl.sh", "helm", "username", dag.SetSecret("password", "secret"), dagger.HelmPackagePushOpts{UseNonOciHelmRepo: true}) if err != nil { @@ -102,13 +130,72 @@ func (m *Go) HelmPackagePushNonOci( return nil } +func (m *Go) HelmPackagePushWithVersion( + // method call context + ctx context.Context, +) error { + // directory that contains the Helm Chart + const differentVersion = "0.6.7" + directory := Mychart.DaggerDirectory() + _, err := dag.Helm().PackagePush(ctx, directory, "ttl.sh", "helm", "username", dag.SetSecret("password", "secret"), dagger.HelmPackagePushOpts{Version: differentVersion}) + + if err != nil { + return err + } + + version, err := dag.Helm().Version(ctx, directory) + + if err != nil { + return err + } + + if version != differentVersion { + return fmt.Errorf("expected %q, got %q", differentVersion, version) + } + + // Reset version back to original + Mychart.Reset() + + return nil +} + +func (m *Go) HelmPackagePushWithAppVersion( + // method call context + ctx context.Context, +) error { + // directory that contains the Helm Chart + const differentAppVersion = "0.9.2" + directory := Mychart.DaggerDirectory() + _, err := dag.Helm().PackagePush(ctx, directory, "ttl.sh", "helm", "username", dag.SetSecret("password", "secret"), dagger.HelmPackagePushOpts{AppVersion: differentAppVersion}) + + if err != nil { + return err + } + + appVersion, err := dag.Helm().AppVersion(ctx, directory) + + if err != nil { + return err + } + + if appVersion != differentAppVersion { + return fmt.Errorf("expected %q, got %q", differentAppVersion, appVersion) + } + + // Reset version back to original + Mychart.Reset() + + return nil +} func (m *Go) HelmPackagePushWithExistingChart( // method call context ctx context.Context, ) error { randomString := fmt.Sprintf("%d", time.Now().UnixNano())[0:8] // directory that contains the Helm Chart - directory := m.chartWithVersionSuffix(dag.CurrentModule().Source().Directory("./testdata/mychart/"), randomString) + directory := Mychart. + WithOriginalVersionSuffix(randomString). + DaggerDirectory() returnValue, err := dag.Helm().PackagePush(ctx, directory, "ttl.sh", "helm", "username", dag.SetSecret("password", "secret")) if err != nil { @@ -126,6 +213,9 @@ func (m *Go) HelmPackagePushWithExistingChart( return fmt.Errorf("should return false because chart already exists") } + // Reset the chart to its original content + Mychart.Reset() + return nil } @@ -134,7 +224,7 @@ func (m *Go) HelmTest( ctx context.Context, ) error { args := []string{"."} - directory := dag.CurrentModule().Source().Directory("./testdata/mychart/") + directory := Mychart.DaggerDirectory() _, err := dag.Helm().Test(ctx, directory, args) if err != nil { @@ -148,7 +238,7 @@ func (m *Go) HelmLint( // method call context ctx context.Context, ) error { - directory := dag.CurrentModule().Source().Directory("./testdata/mychart/") + directory := Mychart.DaggerDirectory() _, err := dag.Helm().Lint(ctx, directory) if err != nil { @@ -163,7 +253,7 @@ func (m *Go) HelmLintWithArg( ctx context.Context, ) error { args := dagger.HelmLintOpts{Args: []string{"--quiet"}} - directory := dag.CurrentModule().Source().Directory("./testdata/mychart/") + directory := Mychart.DaggerDirectory() _, err := dag.Helm().Lint(ctx, directory, args) if err != nil { @@ -178,7 +268,7 @@ func (m *Go) HelmLintWithArgs( ctx context.Context, ) error { args := dagger.HelmLintOpts{Args: []string{"--strict", "--quiet"}} - directory := dag.CurrentModule().Source().Directory("./testdata/mychart/") + directory := Mychart.DaggerDirectory() _, err := dag.Helm().Lint(ctx, directory, args) if err != nil { @@ -192,7 +282,8 @@ func (m *Go) HelmLintWithMissingDependencies( // method call context ctx context.Context, ) error { - directory := dag.CurrentModule().Source().Directory("./testdata/mydependentchart/") + directory := Mydependentchart. + DaggerDirectory() _, err := dag.Helm().Lint(ctx, directory) if err != nil { @@ -202,22 +293,3 @@ func (m *Go) HelmLintWithMissingDependencies( return nil } -func (m *Go) chartWithVersionSuffix( - // directory that contains the Helm Chart - directory *dagger.Directory, - randomString string, -) *dagger.Directory { - // set name and version to arbitrary values - directory = directory. - WithoutFile("Chart.yaml"). - WithNewFile("Chart.yaml", -fmt.Sprintf(` -apiVersion: v2 -name: dagger-module-helm-test -description: A Helm chart -type: application -version: 0.1.1-%s -`, randomString)) - - return directory -} diff --git a/tests/testdata/mydependentchart/Chart.yaml b/tests/testdata/mydependentchart/Chart.yaml index 30cb06d..ebfdf72 100644 --- a/tests/testdata/mydependentchart/Chart.yaml +++ b/tests/testdata/mydependentchart/Chart.yaml @@ -1,5 +1,5 @@ apiVersion: v2 -name: dagger-module-helm-test +name: dagger-module-helm-test-with-dependency description: A Helm chart for Kubernetes # A chart can be either an 'application' or a 'library' chart.