diff --git a/README.md b/README.md index 8eef7144..55f7460f 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,102 @@ # Helm Diff Plugin -This is a Helm plugin giving your a preview of what a `helm upgrade` would change. -It basically generates a diff between the latest deployed version of a release -and a `helm upgrade --debug --dry-run` +This is a Helm plugin giving your a preview of what a `helm upgrade` or `helm rollback` +would change. It basically generates a diff between the latest deployed version of a +releaseand a `helm upgrade --debug --dry-run` when used with `upgrade` command or with +the specified rollback revision of release. This can also be used to compare two +revisions/versions of your helm release. ## Usage ``` -$ helm diff [flags] RELEASE CHART +$ helm diff +The Helm Diff Plugin + +* Shows a diff explaing what a helm upgrade would change: + This fetches the currently deployed version of a release + and compares it to a local chart plus values. This can be + used visualize what changes a helm upgrade will perform. + +* Shows a diff explaing what a helm rollback would change: + This fetches the currently deployed version of a release + and compares it rollback revision. This can be + used visualize what changes a helm rollback will perform. + +* Shows a diff explaing what had changed between two revisions: + This fetches previously deployed versions of a release + and compares them. This can be used visualize what changes + were made during revision change. + +Usage: + diff [command] + +Available Commands: + revision Shows diff between revision's manifests + rollback visualize changes, that a helm rollback could perform + upgrade visualize changes, that a helm upgrade could perform + version print the version information + +Flags: + -h, --help help for diff + +Use "diff [command] --help" for more information about a command. +``` + +## Commands: + +### upgrade: + +``` +$ helm diff upgrade -h +This command compares the manifests details of a named release +with values generated form charts. + +It forecasts/visualizes changes, that a helm upgrade could perform. + +Usage: + diff upgrade [flags] [RELEASE] [CHART] + +Flags: + --reuse-values reuse the last release's values and merge in any new values + --set stringArray set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2) + --suppress stringArray allows suppression of the values listed in the diff output + -q, --suppress-secrets suppress secrets in the output + -f, --values valueFiles specify values in a YAML file (can specify multiple) (default []) +``` + +### rollback: + ``` +$ helm diff rollback -h +This command compares the laset manifests details of a named release +with specific revision values to rollback. -### Flags: +It forecasts/visualizes changes, that a helm rollback could perform. + +Usage: + diff rollback [flags] [RELEASE] REVISION ``` - --set string set values on the command line. See 'helm install -h' - -f, --values valueFiles specify one or more YAML files of values (default []) + +### revision: + ``` +$ helm diff revision -h +This command compares the manifests details of a named release. +It can be used to compare the manifests of + + - lastest REVISION with specified REVISION + $ helm diff revision [flags] [RELEASE] REVISION + + - REVISION1 with REVISION2 + $ helm diff revision [flags] [RELEASE] REVISION1 REVISION1 + +Usage: + diff revision [flags] [RELEASE] REVISION1 REVISION2 +``` ## Install diff --git a/helm.go b/helm.go index db804ec7..1edb777e 100644 --- a/helm.go +++ b/helm.go @@ -76,7 +76,7 @@ func locateChartPath(name, version string, verify bool, keyring string) (string, dl.Verify = downloader.VerifyAlways } - filename, _, err := dl.DownloadTo(name, version, ".") + filename, _, err := dl.DownloadTo(name, version, helmpath.Home(homePath()).Archive()) if err == nil { lname, err := filepath.Abs(filename) if err != nil { diff --git a/main.go b/main.go index 5fb5152b..d3383576 100644 --- a/main.go +++ b/main.go @@ -1,23 +1,35 @@ package main import ( + "errors" "fmt" - "os" - "github.com/mgutz/ansi" "github.com/spf13/cobra" "k8s.io/helm/pkg/helm" + "os" + "strconv" "github.com/databus23/helm-diff/manifest" ) const globalUsage = ` -Show a diff explaining what a helm upgrade would change. -This fetches the currently deployed version of a release -and compares it to a local chart plus values. -This can be used visualize what changes a helm upgrade will -perform. +The Helm Diff Plugin + +* Shows a diff explaing what a helm upgrade would change: + This fetches the currently deployed version of a release + and compares it to a local chart plus values. This can be + used visualize what changes a helm upgrade will perform. + +* Shows a diff explaing what a helm rollback would change: + This fetches the currently deployed version of a release + and compares it rollback revision. This can be + used visualize what changes a helm rollback will perform. + +* Shows a diff explaing what had changed between two revisions: + This fetches previously deployed versions of a release + and compares them. This can be used visualize what changes + were made during revision change. ` // Version identifier populated via the CI/CD process. @@ -31,15 +43,22 @@ type diffCmd struct { values []string reuseValues bool suppressedKinds []string + revisions []string } func main() { diff := diffCmd{} - cmd := &cobra.Command{ - Use: "diff [flags] [RELEASE] [CHART]", - Short: "Show manifest differences", - Long: globalUsage, + const upgradeCmdLongUsage = ` +This command compares the manifests details of a named release +with values generated form charts. + +It forecasts/visualizes changes, that a helm upgrade could perform. +` + upgradeCmd := &cobra.Command{ + Use: "upgrade [RELEASE] [CHART] [flags]", + Short: "visualize changes, that a helm upgrade could perform", + Long: upgradeCmdLongUsage, RunE: func(cmd *cobra.Command, args []string) error { if v, _ := cmd.Flags().GetBool("version"); v { fmt.Println(Version) @@ -54,34 +73,202 @@ func main() { diff.suppressedKinds = append(diff.suppressedKinds, "Secret") } - if nc, _ := cmd.Flags().GetBool("no-color"); nc { - ansi.DisableColors(true) - } - diff.release = args[0] diff.chart = args[1] + if diff.client == nil { diff.client = helm.NewClient(helm.Host(os.Getenv("TILLER_HOST"))) } - return diff.run() + + return diff.forecast() }, } - f := cmd.Flags() - f.BoolP("version", "v", false, "show version") - f.BoolP("suppress-secrets", "q", false, "suppress secrets in the output") - f.Bool("no-color", false, "remove colors from the output") + upgradeCmd.SuggestionsMinimumDistance = 1 + f := upgradeCmd.Flags() f.VarP(&diff.valueFiles, "values", "f", "specify values in a YAML file (can specify multiple)") f.StringArrayVar(&diff.values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") + f.BoolP("suppress-secrets", "q", false, "suppress secrets in the output") f.BoolVar(&diff.reuseValues, "reuse-values", false, "reuse the last release's values and merge in any new values") f.StringArrayVar(&diff.suppressedKinds, "suppress", []string{}, "allows suppression of the values listed in the diff output") - if err := cmd.Execute(); err != nil { - os.Exit(1) + const rollbackCmdLongUsage = ` +This command compares the laset manifests details of a named release +with specific revision values to rollback. + +It forecasts/visualizes changes, that a helm rollback could perform. +` + rollbackCmd := &cobra.Command{ + Use: "rollback [flags] [RELEASE] [REVISION]", + Short: "visualize changes, that a helm rollback could perform", + Long: rollbackCmdLongUsage, + RunE: func(cmd *cobra.Command, args []string) error { + if v, _ := cmd.Flags().GetBool("version"); v { + fmt.Println(Version) + return nil + } + + if err := checkArgsLength(len(args), "release name", "revision number"); err != nil { + return err + } + + if q, _ := cmd.Flags().GetBool("suppress-secrets"); q { + diff.suppressedKinds = append(diff.suppressedKinds, "Secret") + } + + diff.release = args[0] + diff.revisions = args[1:] + + if diff.client == nil { + diff.client = helm.NewClient(helm.Host(os.Getenv("TILLER_HOST"))) + } + + return diff.backcast() + }, + } + + rollbackCmd.SuggestionsMinimumDistance = 1 + + const revisionCmdLongUsage = ` +This command compares the manifests details of a named release. + +It can be used to compare the manifests of + + - lastest REVISION with specified REVISION + $ helm diff revision [flags] [RELEASE] [REVISION] + + - REVISION1 with REVISION2 + $ helm diff revision [flags] [RELEASE] [REVISION1] [REVISION2] +` + + revisionCmd := &cobra.Command{ + Use: "revision [flags] [RELEASE] REVISION1 REVISION2", + Short: "Shows diff between revision's manifests", + Long: revisionCmdLongUsage, + RunE: func(cmd *cobra.Command, args []string) error { + if v, _ := cmd.Flags().GetBool("version"); v { + fmt.Println(Version) + return nil + } + + switch { + case len(args) < 2: + return errors.New("Too few arguments to Command \"revision\".\nMinimum 2 arguments required: release name, revision") + case len(args) > 3: + return errors.New("Too many arguments to Command \"revision\".\nMaximum 3 arguments allowed: release name, revision1, revision2") + } + + if q, _ := cmd.Flags().GetBool("suppress-secrets"); q { + diff.suppressedKinds = append(diff.suppressedKinds, "Secret") + } + + if nc, _ := cmd.Flags().GetBool("no-color"); nc { + ansi.DisableColors(true) + } + + diff.release = args[0] + diff.revisions = args[1:] + if diff.client == nil { + diff.client = helm.NewClient(helm.Host(os.Getenv("TILLER_HOST"))) + } + return diff.differentiate() + }, } + + revisionCmd.SuggestionsMinimumDistance = 1 + + versionCmd := &cobra.Command{ + Use: "version", + Short: "print the version information", + RunE: func(cmd *cobra.Command, args []string) error { + fmt.Println(Version) + return nil + }, + } + + rootCmd := &cobra.Command{ + Use: "diff", + Short: "Show differences between release revisions", + Long: globalUsage, + SilenceUsage: true, + } + + rootCmd.AddCommand( + upgradeCmd, + revisionCmd, + rollbackCmd, + versionCmd, + ) + + if err := rootCmd.Execute(); err != nil { + os.Exit(0) + } +} + +func (d *diffCmd) backcast() error { + + releaseResponse, err := d.client.ReleaseContent(d.release) + + if err != nil { + return prettyError(err) + } + + revision, _ := strconv.Atoi(d.revisions[0]) + revisionResponse, err := d.client.ReleaseContent(d.release, helm.ContentReleaseVersion(int32(revision))) + if err != nil { + return prettyError(err) + } + + diffManifests(manifest.Parse(revisionResponse.Release.Manifest), manifest.Parse(releaseResponse.Release.Manifest), d.suppressedKinds, os.Stdout) + + return nil +} + +func (d *diffCmd) differentiate() error { + + switch len(d.revisions) { + case 1: + releaseResponse, err := d.client.ReleaseContent(d.release) + + if err != nil { + return prettyError(err) + } + + revision, _ := strconv.Atoi(d.revisions[0]) + revisionResponse, err := d.client.ReleaseContent(d.release, helm.ContentReleaseVersion(int32(revision))) + if err != nil { + return prettyError(err) + } + + diffManifests(manifest.Parse(revisionResponse.Release.Manifest), manifest.Parse(releaseResponse.Release.Manifest), d.suppressedKinds, os.Stdout) + + case 2: + revision1, _ := strconv.Atoi(d.revisions[0]) + revision2, _ := strconv.Atoi(d.revisions[1]) + if revision1 > revision2 { + revision1, revision2 = revision2, revision1 + } + + revisionResponse1, err := d.client.ReleaseContent(d.release, helm.ContentReleaseVersion(int32(revision1))) + if err != nil { + return prettyError(err) + } + + revisionResponse2, err := d.client.ReleaseContent(d.release, helm.ContentReleaseVersion(int32(revision2))) + if err != nil { + return prettyError(err) + } + + diffManifests(manifest.Parse(revisionResponse1.Release.Manifest), manifest.Parse(revisionResponse2.Release.Manifest), d.suppressedKinds, os.Stdout) + + default: + return errors.New("Invalid Arguments") + } + + return nil } -func (d *diffCmd) run() error { +func (d *diffCmd) forecast() error { chartPath, err := locateChartPath(d.chart, "", false, "") if err != nil { return err