diff --git a/README.md b/README.md index 72d4d7c14..29949d197 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,25 @@ If you just need system applications, you could also try "setup-arkade": arkade system install go ``` +## Bump Helm chart versions + +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 -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 chart has any changes, specify the `--check-for-updates` flag: + +```bash +arkade chart bump -f ./charts/flagger/values.yaml --check-for-updates +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 new file mode 100644 index 000000000..3d5294e67 --- /dev/null +++ b/cmd/chart/bump.go @@ -0,0 +1,133 @@ +package chart + +import ( + "context" + "fmt" + "log" + "os" + "path/filepath" + + "github.com/Masterminds/semver" + "github.com/alexellis/arkade/pkg/helm" + "github.com/alexellis/go-execute/v2" + "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 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 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 -f ./chart/values.yaml + arkade chart bump -f ./charts/values.yaml --check-for-updates`, + SilenceUsage: true, + } + + 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().Bool("check-for-updates", false, "Check for updates to the chart before bumping its version") + + command.RunE = func(cmd *cobra.Command, args []string) error { + valuesFile, err := cmd.Flags().GetString("file") + if err != nil { + return fmt.Errorf("invalid value for --file") + } + 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") + } + checkForUpdates, err := cmd.Flags().GetBool("check-for-updates") + if err != nil { + return fmt.Errorf("invalid value for --check-for-updates") + } + + 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 { + 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) + } + } + + // 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 { + version, ok := val.(string) + if !ok { + log.Printf("unable to find a valid version in %s", chartYamlPath) + } + if checkForUpdates { + absPath, err := filepath.Abs(chartDir) + if err != nil { + return err + } + + // 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", "."}, + 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", chartDir) + os.Exit(0) + } + } + + ver, err := semver.NewVersion(version) + if err != nil { + return fmt.Errorf("%s", err) + } + newVer := ver.IncPatch() + 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()), + } + rawChartYaml, err := helm.ReplaceValuesInHelmValuesFile(update, chartYamlPath) + if err != nil { + return fmt.Errorf("unable to bump chart version in %s", chartYamlPath) + } + if err = os.WriteFile(chartYamlPath, []byte(rawChartYaml), 0600); err != nil { + return fmt.Errorf("unable to write updated yaml to %s", chartYamlPath) + } + } + } + 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 }