From 4e32ec7396db7ca66651f6c150142833098140c2 Mon Sep 17 00:00:00 2001 From: Sanskar Jaiswal Date: Mon, 22 Jan 2024 18:36:34 +0530 Subject: [PATCH 1/3] helm: add `arkade chart bump` to bump chart versions Add a new command `arkade chart bump --dir ./chart` to bump the minor of a Helm chart version. To bump multiple charts at once, specify the parent directory with the `--recursive` flag. By default, the changes are written to stdout. Specifying the `--write` flag writes the changes to the respective file. Signed-off-by: Sanskar Jaiswal njndjnd --- README.md | 22 ++++++++ cmd/chart/bump.go | 127 +++++++++++++++++++++++++++++++++++++++++++++ cmd/chart/chart.go | 1 + 3 files changed, 150 insertions(+) create mode 100644 cmd/chart/bump.go diff --git a/README.md b/README.md index 72d4d7c14..4d816ffa8 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ With over 120 CLIs and 55 Kubernetes apps (charts, manifests, installers) availa - [Download CLI tools with arkade](#download-cli-tools-with-arkade) - [Install System Packages](#install-system-packages) - [Install CLIs during CI with GitHub Actions](#install-clis-during-ci-with-github-actions) + - [Bump Helm chart versions](#bump-helm-chart-versions) - [Verify and upgrade images in Helm charts](#verify-and-upgrade-images-in-helm-charts) - [Upgrade images within a Helm chart](#upgrade-images-within-a-helm-chart) - [Verify images within a helm chart](#verify-images-within-a-helm-chart) @@ -283,6 +284,27 @@ If you just need system applications, you could also try "setup-arkade": arkade system install go ``` +## Bump Helm chart versions + +To bump the minor of your Helm chart version, run `arkade chart bump --dir ./chart`. This updates the minor component of the version specified in Chart.yaml. + +```bash +arkade chart bump --dir ~/Development/fluxcd/flagger/charts/flagger +/Users/sanskarjaiswal/Development/fluxcd/flagger/charts/flagger/Chart.yaml 1.35.0 => 1.36.0 +``` + +By default, the new version is written to stdout. To bump the version in the file, run the above command with the `--write` flag. +If you have a parent directory containing multiple charts, you can bump all charts by specifying the `--recursive` flag. + +```bash +❯ ./arkade chart bump --dir ~/Development/fluxcd/flagger/charts --recursive +Found 4 charts +/Users/sanskarjaiswal/Development/fluxcd/flagger/charts/flagger/Chart.yaml 1.35.0 => 1.36.0 +/Users/sanskarjaiswal/Development/fluxcd/flagger/charts/grafana/Chart.yaml 1.7.0 => 1.8.0 +/Users/sanskarjaiswal/Development/fluxcd/flagger/charts/loadtester/Chart.yaml 0.30.0 => 0.31.0 +/Users/sanskarjaiswal/Development/fluxcd/flagger/charts/podinfo/Chart.yaml 6.1.3 => 6.2.0 +``` + ## Verify and upgrade images in Helm charts There are two commands built into arkade designed for software vendors and open source maintainers. diff --git a/cmd/chart/bump.go b/cmd/chart/bump.go new file mode 100644 index 000000000..5a08d1e47 --- /dev/null +++ b/cmd/chart/bump.go @@ -0,0 +1,127 @@ +package chart + +import ( + "fmt" + "io/fs" + "log" + "os" + "path/filepath" + + "github.com/Masterminds/semver" + "github.com/alexellis/arkade/pkg/helm" + "github.com/spf13/cobra" +) + +const ( + versionKey = "version" + ChartYamlFileName = "Chart.yaml" + ChartYmlFileName = "Chart.yml" +) + +func MakeBump() *cobra.Command { + var command = &cobra.Command{ + Use: "bump", + Short: "Bump the version of the Helm chart(s)", + Long: `Bump the version present in the Chart.yaml of a Helm chart. +If the provided directory contains multiple charts, then the --recursive flag +can be used to bump the version in all charts.`, + Example: `arkade bump --dir ./chart + arkade --dir ./charts --recursive`, + SilenceUsage: true, + } + + command.Flags().StringP("dir", "d", "", "Path to the Helm chart directory or a directory containing Helm charts") + command.Flags().BoolP("recursive", "r", false, "Recursively iterate through directory while bumping chart versions") + command.Flags().BoolP("verbose", "v", false, "Verbose output") + command.Flags().BoolP("write", "w", false, "Write the updated values back to the file, or stdout when set to false") + + command.RunE = func(cmd *cobra.Command, args []string) error { + chartDir, err := cmd.Flags().GetString("dir") + if err != nil { + return fmt.Errorf("invalid value for --dir") + } + if chartDir == "" { + return fmt.Errorf("flag --dir is required") + } + verbose, _ := cmd.Flags().GetBool("verbose") + recursive, err := cmd.Flags().GetBool("recursive") + if err != nil { + return fmt.Errorf("invalid value for --recursive") + } + write, err := cmd.Flags().GetBool("write") + if err != nil { + return fmt.Errorf("invalid value for --write") + } + + // Map with key as the path to Chart.yaml and the value as the parsed contents of Chart.yaml + chartYamls := make(map[string]helm.ValuesMap, 0) + if !recursive { + chartYamlPath := filepath.Join(chartDir, ChartYamlFileName) + var values helm.ValuesMap + // Try to read a Chart.yaml, but if thats unsuccessful then fall back to Chart.yml + if values, err = helm.Load(chartYamlPath); err != nil { + if verbose { + log.Printf("unable to read %s, falling back to Chart.yml\n", chartYamlPath) + } + chartYamlPath = filepath.Join(chartDir, ChartYmlFileName) + if values, err = helm.Load(chartYamlPath); err != nil { + return fmt.Errorf("unable to read Chart.yaml or Chart.yml in directory %s", chartDir) + } + } + chartYamls[chartYamlPath] = values + } else { + filepath.WalkDir(chartDir, func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if d.Name() == ChartYamlFileName || d.Name() == ChartYmlFileName { + values, err := helm.Load(path) + if err != nil { + return err + } + chartYamls[path] = values + } + return nil + }) + if len(chartYamls) > 0 { + fmt.Printf("Found %d chart(s)\n", len(chartYamls)) + } + } + + for file, contents := range chartYamls { + // If the yaml does not contain a `version` key then skip it. + if val, ok := contents[versionKey]; !ok { + continue + } else { + version, ok := val.(string) + if !ok { + log.Printf("unable to find a valid version in %s", file) + continue + } + ver, err := semver.NewVersion(version) + if err != nil { + continue + } + newVer := ver.IncMinor() + fmt.Printf("%s %s => %s\n", file, ver.String(), newVer.String()) + if write { + if verbose { + log.Printf("Bumping version") + } + update := map[string]string{ + fmt.Sprintf("%s: %s", versionKey, ver.String()): fmt.Sprintf("%s: %s", versionKey, newVer.String()), + } + rawChartYaml, err := helm.ReplaceValuesInHelmValuesFile(update, file) + if err != nil { + return fmt.Errorf("unable to bump chart version in %s", file) + } + if err = os.WriteFile(file, []byte(rawChartYaml), 0600); err != nil { + return fmt.Errorf("unable to write updated yaml to %s", file) + } + } + } + } + return nil + } + return command +} diff --git a/cmd/chart/chart.go b/cmd/chart/chart.go index 3ac5bbd2d..d62525f6b 100644 --- a/cmd/chart/chart.go +++ b/cmd/chart/chart.go @@ -26,6 +26,7 @@ func MakeChart() *cobra.Command { command.AddCommand(MakeVerify()) command.AddCommand(MakeUpgrade()) + command.AddCommand(MakeBump()) return command } From bc9421dd702e67912cd10884646ac324ec298782 Mon Sep 17 00:00:00 2001 From: Sanskar Jaiswal Date: Thu, 25 Jan 2024 00:09:42 +0530 Subject: [PATCH 2/3] allow checking for value changes before bumping helm chart version Add flag `--check-for-value-updates` to `arkade chart bump` which skips bumping the chart version if the values file does not have any changes according to the Git. Signed-off-by: Sanskar Jaiswal --- README.md | 16 +++--- cmd/chart/bump.go | 135 ++++++++++++++++++++++++---------------------- 2 files changed, 79 insertions(+), 72 deletions(-) diff --git a/README.md b/README.md index 4d816ffa8..be6924777 100644 --- a/README.md +++ b/README.md @@ -289,22 +289,20 @@ If you just need system applications, you could also try "setup-arkade": To bump the minor of your Helm chart version, run `arkade chart bump --dir ./chart`. This updates the minor component of the version specified in Chart.yaml. ```bash -arkade chart bump --dir ~/Development/fluxcd/flagger/charts/flagger -/Users/sanskarjaiswal/Development/fluxcd/flagger/charts/flagger/Chart.yaml 1.35.0 => 1.36.0 +arkade chart bump --dir ./charts/flagger +charts/flagger/Chart.yaml 1.36.0 => 1.37.0 ``` By default, the new version is written to stdout. To bump the version in the file, run the above command with the `--write` flag. -If you have a parent directory containing multiple charts, you can bump all charts by specifying the `--recursive` flag. +To bump the version in the chart's Chart.yaml only if the adjacent values file has any changes, specify the `--check-for-value-updates` flag: ```bash -❯ ./arkade chart bump --dir ~/Development/fluxcd/flagger/charts --recursive -Found 4 charts -/Users/sanskarjaiswal/Development/fluxcd/flagger/charts/flagger/Chart.yaml 1.35.0 => 1.36.0 -/Users/sanskarjaiswal/Development/fluxcd/flagger/charts/grafana/Chart.yaml 1.7.0 => 1.8.0 -/Users/sanskarjaiswal/Development/fluxcd/flagger/charts/loadtester/Chart.yaml 0.30.0 => 0.31.0 -/Users/sanskarjaiswal/Development/fluxcd/flagger/charts/podinfo/Chart.yaml 6.1.3 => 6.2.0 +arkade chart bump --dir ./charts/flagger/ --check-for-value-updates values.yaml +no changes detected in charts/flagger/values.yaml; skipping version bump ``` +The directory that contains the Helm chart should be a Git repository. If the flag is specified, the command runs `git diff --exit-code ` to figure out if the file has any changes. + ## Verify and upgrade images in Helm charts There are two commands built into arkade designed for software vendors and open source maintainers. diff --git a/cmd/chart/bump.go b/cmd/chart/bump.go index 5a08d1e47..3e399c166 100644 --- a/cmd/chart/bump.go +++ b/cmd/chart/bump.go @@ -1,14 +1,15 @@ package chart import ( + "context" "fmt" - "io/fs" "log" "os" "path/filepath" "github.com/Masterminds/semver" "github.com/alexellis/arkade/pkg/helm" + "github.com/alexellis/go-execute/v2" "github.com/spf13/cobra" ) @@ -21,19 +22,21 @@ const ( func MakeBump() *cobra.Command { var command = &cobra.Command{ Use: "bump", - Short: "Bump the version of the Helm chart(s)", + Short: "Bump the version of the Helm chart.", Long: `Bump the version present in the Chart.yaml of a Helm chart. -If the provided directory contains multiple charts, then the --recursive flag -can be used to bump the version in all charts.`, - Example: `arkade bump --dir ./chart - arkade --dir ./charts --recursive`, +To bump the version only if the adjacent values file has changes then specify +the --check-for-value-updates flag. If the values file has no changes the command +returns early with an exit code zero. +`, + Example: `arkade chart bump --dir ./chart + arkade chart bump --dir ./charts --check-for-value-updates values.yaml`, SilenceUsage: true, } command.Flags().StringP("dir", "d", "", "Path to the Helm chart directory or a directory containing Helm charts") - command.Flags().BoolP("recursive", "r", false, "Recursively iterate through directory while bumping chart versions") command.Flags().BoolP("verbose", "v", false, "Verbose output") command.Flags().BoolP("write", "w", false, "Write the updated values back to the file, or stdout when set to false") + command.Flags().String("check-for-value-updates", "", "Name of the values file to check if the chart's values have been modified before bumping version") command.RunE = func(cmd *cobra.Command, args []string) error { chartDir, err := cmd.Flags().GetString("dir") @@ -44,80 +47,86 @@ can be used to bump the version in all charts.`, return fmt.Errorf("flag --dir is required") } verbose, _ := cmd.Flags().GetBool("verbose") - recursive, err := cmd.Flags().GetBool("recursive") - if err != nil { - return fmt.Errorf("invalid value for --recursive") - } write, err := cmd.Flags().GetBool("write") if err != nil { return fmt.Errorf("invalid value for --write") } + valuesFile, err := cmd.Flags().GetString("check-for-value-updates") + if err != nil { + return fmt.Errorf("invalid value for --check-for-value-updates") + } // Map with key as the path to Chart.yaml and the value as the parsed contents of Chart.yaml - chartYamls := make(map[string]helm.ValuesMap, 0) - if !recursive { - chartYamlPath := filepath.Join(chartDir, ChartYamlFileName) - var values helm.ValuesMap - // Try to read a Chart.yaml, but if thats unsuccessful then fall back to Chart.yml + chartYamlPath := filepath.Join(chartDir, ChartYamlFileName) + var values helm.ValuesMap + // Try to read a Chart.yaml, but if thats unsuccessful then fall back to Chart.yml + if values, err = helm.Load(chartYamlPath); err != nil { + if verbose { + log.Printf("unable to read %s, falling back to Chart.yml\n", chartYamlPath) + } + chartYamlPath = filepath.Join(chartDir, ChartYmlFileName) if values, err = helm.Load(chartYamlPath); err != nil { - if verbose { - log.Printf("unable to read %s, falling back to Chart.yml\n", chartYamlPath) - } - chartYamlPath = filepath.Join(chartDir, ChartYmlFileName) - if values, err = helm.Load(chartYamlPath); err != nil { - return fmt.Errorf("unable to read Chart.yaml or Chart.yml in directory %s", chartDir) - } + return fmt.Errorf("unable to read Chart.yaml or Chart.yml in directory %s", chartDir) } - chartYamls[chartYamlPath] = values + } + + // If the yaml does not contain a `version` key then error out. + if val, ok := values[versionKey]; !ok { + return fmt.Errorf("unable to find a version in %s", chartYamlPath) } else { - filepath.WalkDir(chartDir, func(path string, d fs.DirEntry, err error) error { + version, ok := val.(string) + if !ok { + log.Printf("unable to find a valid version in %s", chartYamlPath) + } + if valuesFile != "" { + absPath, err := filepath.Abs(chartDir) if err != nil { return err } - if d.Name() == ChartYamlFileName || d.Name() == ChartYmlFileName { - values, err := helm.Load(path) - if err != nil { - return err - } - chartYamls[path] = values + absValuesFile := filepath.Join(absPath, valuesFile) + _, err = os.Stat(absValuesFile) + if err != nil { + return fmt.Errorf("unable to find values file: %s", absValuesFile) + } + + // Run `git diff --exit-code ` to check if the values file has any changes. + // An exit code of 0 indicates that there are no changes, thus we skip bumping the + // version of the chart. + cmd := execute.ExecTask{ + Command: "git", + Args: []string{"diff", "--exit-code", valuesFile}, + Cwd: absPath, + } + res, err := cmd.Execute(context.Background()) + if err != nil { + return fmt.Errorf("could not check updates to chart values: %s", err) + } + + if res.ExitCode == 0 { + fmt.Printf("no changes detected in %s; skipping version bump\n", filepath.Join(chartDir, valuesFile)) + os.Exit(0) } - return nil - }) - if len(chartYamls) > 0 { - fmt.Printf("Found %d chart(s)\n", len(chartYamls)) } - } - for file, contents := range chartYamls { - // If the yaml does not contain a `version` key then skip it. - if val, ok := contents[versionKey]; !ok { - continue - } else { - version, ok := val.(string) - if !ok { - log.Printf("unable to find a valid version in %s", file) - continue + ver, err := semver.NewVersion(version) + if err != nil { + return fmt.Errorf("%s", err) + } + newVer := ver.IncMinor() + fmt.Printf("%s %s => %s\n", chartYamlPath, ver.String(), newVer.String()) + if write { + if verbose { + log.Printf("Bumping version") + } + update := map[string]string{ + fmt.Sprintf("%s: %s", versionKey, ver.String()): fmt.Sprintf("%s: %s", versionKey, newVer.String()), } - ver, err := semver.NewVersion(version) + rawChartYaml, err := helm.ReplaceValuesInHelmValuesFile(update, chartYamlPath) if err != nil { - continue + return fmt.Errorf("unable to bump chart version in %s", chartYamlPath) } - newVer := ver.IncMinor() - fmt.Printf("%s %s => %s\n", file, ver.String(), newVer.String()) - if write { - if verbose { - log.Printf("Bumping version") - } - update := map[string]string{ - fmt.Sprintf("%s: %s", versionKey, ver.String()): fmt.Sprintf("%s: %s", versionKey, newVer.String()), - } - rawChartYaml, err := helm.ReplaceValuesInHelmValuesFile(update, file) - if err != nil { - return fmt.Errorf("unable to bump chart version in %s", file) - } - if err = os.WriteFile(file, []byte(rawChartYaml), 0600); err != nil { - return fmt.Errorf("unable to write updated yaml to %s", file) - } + if err = os.WriteFile(chartYamlPath, []byte(rawChartYaml), 0600); err != nil { + return fmt.Errorf("unable to write updated yaml to %s", chartYamlPath) } } } From 6f1b328ff21ac2ac58c6a364a6defc52bc5eb378 Mon Sep 17 00:00:00 2001 From: Sanskar Jaiswal Date: Tue, 6 Feb 2024 12:07:03 +0530 Subject: [PATCH 3/3] increment patch version and make UX consistent Signed-off-by: Sanskar Jaiswal --- README.md | 8 ++++---- cmd/chart/bump.go | 45 +++++++++++++++++++++------------------------ 2 files changed, 25 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index be6924777..29949d197 100644 --- a/README.md +++ b/README.md @@ -286,18 +286,18 @@ If you just need system applications, you could also try "setup-arkade": ## Bump Helm chart versions -To bump the minor of your Helm chart version, run `arkade chart bump --dir ./chart`. This updates the minor component of the version specified in Chart.yaml. +To bump the patch version of your Helm chart, run `arkade chart bump -f ./chart/values.yaml`. This updates the patch component of the version specified in Chart.yaml. ```bash -arkade chart bump --dir ./charts/flagger +arkade chart bump -f ./charts/flagger/values.yaml charts/flagger/Chart.yaml 1.36.0 => 1.37.0 ``` By default, the new version is written to stdout. To bump the version in the file, run the above command with the `--write` flag. -To bump the version in the chart's Chart.yaml only if the adjacent values file has any changes, specify the `--check-for-value-updates` flag: +To bump the version in the chart's Chart.yaml only if the chart has any changes, specify the `--check-for-updates` flag: ```bash -arkade chart bump --dir ./charts/flagger/ --check-for-value-updates values.yaml +arkade chart bump -f ./charts/flagger/values.yaml --check-for-updates no changes detected in charts/flagger/values.yaml; skipping version bump ``` diff --git a/cmd/chart/bump.go b/cmd/chart/bump.go index 3e399c166..3d5294e67 100644 --- a/cmd/chart/bump.go +++ b/cmd/chart/bump.go @@ -22,42 +22,44 @@ const ( func MakeBump() *cobra.Command { var command = &cobra.Command{ Use: "bump", - Short: "Bump the version of the Helm chart.", + Short: "Bump the patch version of the Helm chart.", Long: `Bump the version present in the Chart.yaml of a Helm chart. -To bump the version only if the adjacent values file has changes then specify -the --check-for-value-updates flag. If the values file has no changes the command +To bump the version only if the chart has changes then specify the +--check-for-updates flag. If the chart has no changes the command returns early with an exit code zero. `, - Example: `arkade chart bump --dir ./chart - arkade chart bump --dir ./charts --check-for-value-updates values.yaml`, + Example: `arkade chart bump -f ./chart/values.yaml + arkade chart bump -f ./charts/values.yaml --check-for-updates`, SilenceUsage: true, } - command.Flags().StringP("dir", "d", "", "Path to the Helm chart directory or a directory containing Helm charts") + command.Flags().StringP("file", "f", "", "Path to values.yaml file") command.Flags().BoolP("verbose", "v", false, "Verbose output") command.Flags().BoolP("write", "w", false, "Write the updated values back to the file, or stdout when set to false") - command.Flags().String("check-for-value-updates", "", "Name of the values file to check if the chart's values have been modified before bumping version") + command.Flags().Bool("check-for-updates", false, "Check for updates to the chart before bumping its version") command.RunE = func(cmd *cobra.Command, args []string) error { - chartDir, err := cmd.Flags().GetString("dir") + valuesFile, err := cmd.Flags().GetString("file") if err != nil { - return fmt.Errorf("invalid value for --dir") + return fmt.Errorf("invalid value for --file") } - if chartDir == "" { - return fmt.Errorf("flag --dir is required") + if valuesFile == "" { + return fmt.Errorf("flag --file is required") } verbose, _ := cmd.Flags().GetBool("verbose") write, err := cmd.Flags().GetBool("write") if err != nil { return fmt.Errorf("invalid value for --write") } - valuesFile, err := cmd.Flags().GetString("check-for-value-updates") + checkForUpdates, err := cmd.Flags().GetBool("check-for-updates") if err != nil { - return fmt.Errorf("invalid value for --check-for-value-updates") + return fmt.Errorf("invalid value for --check-for-updates") } - // Map with key as the path to Chart.yaml and the value as the parsed contents of Chart.yaml + chartDir := filepath.Dir(valuesFile) chartYamlPath := filepath.Join(chartDir, ChartYamlFileName) + + // Map with key as the path to Chart.yaml and the value as the parsed contents of Chart.yaml var values helm.ValuesMap // Try to read a Chart.yaml, but if thats unsuccessful then fall back to Chart.yml if values, err = helm.Load(chartYamlPath); err != nil { @@ -78,23 +80,18 @@ returns early with an exit code zero. if !ok { log.Printf("unable to find a valid version in %s", chartYamlPath) } - if valuesFile != "" { + if checkForUpdates { absPath, err := filepath.Abs(chartDir) if err != nil { return err } - absValuesFile := filepath.Join(absPath, valuesFile) - _, err = os.Stat(absValuesFile) - if err != nil { - return fmt.Errorf("unable to find values file: %s", absValuesFile) - } - // Run `git diff --exit-code ` to check if the values file has any changes. + // Run `git diff --exit-code ` to check if any files in the chart dir changed. // An exit code of 0 indicates that there are no changes, thus we skip bumping the // version of the chart. cmd := execute.ExecTask{ Command: "git", - Args: []string{"diff", "--exit-code", valuesFile}, + Args: []string{"diff", "--exit-code", "."}, Cwd: absPath, } res, err := cmd.Execute(context.Background()) @@ -103,7 +100,7 @@ returns early with an exit code zero. } if res.ExitCode == 0 { - fmt.Printf("no changes detected in %s; skipping version bump\n", filepath.Join(chartDir, valuesFile)) + fmt.Printf("no changes detected in %s; skipping version bump\n", chartDir) os.Exit(0) } } @@ -112,7 +109,7 @@ returns early with an exit code zero. if err != nil { return fmt.Errorf("%s", err) } - newVer := ver.IncMinor() + newVer := ver.IncPatch() fmt.Printf("%s %s => %s\n", chartYamlPath, ver.String(), newVer.String()) if write { if verbose {