diff --git a/README.md b/README.md index e39b31ab..d33d345a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ # Helm Diff Plugin +[![Go Report Card](https://goreportcard.com/badge/github.com/databus23/helm-diff)](https://goreportcard.com/report/github.com/databus23/helm-diff) +[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/databus23/helm-diff/blob/master/LICENSE) 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 @@ -33,6 +35,7 @@ Usage: diff [command] Available Commands: + release Shows diff between release's manifests revision Shows diff between revision's manifests rollback Show a diff explaining what a helm rollback could perform upgrade Show a diff explaining what a helm upgrade would change. @@ -89,6 +92,41 @@ Global Flags: --no-color remove colors from the output ``` +### release: + +``` +$ helm diff release -h + +This command compares the manifests details of a different releases created from the same chart + +It can be used to compare the manifests of + + - release1 with release2 + $ helm diff release [flags] release1 release2 + Example: + $ helm diff release my-prod my-stage + +Usage: + diff release [flags] RELEASE release1 [release2] + +Flags: + -C, --context int output NUM lines of context around changes (default -1) + -h, --help help for release + --home string location of your Helm config. Overrides $HELM_HOME (default "/home/aananth/.helm") + --include-tests enable the diffing of the helm test hooks + --suppress stringArray allows suppression of the values listed in the diff output + -q, --suppress-secrets suppress secrets in the output + --tls enable TLS for request + --tls-ca-cert string path to TLS CA certificate file (default "$HELM_HOME/ca.pem") + --tls-cert string path to TLS certificate file (default "$HELM_HOME/cert.pem") + --tls-hostname string the server name used to verify the hostname on the returned certificates from the server + --tls-key string path to TLS key file (default "$HELM_HOME/key.pem") + --tls-verify enable TLS for request and verify remote + +Global Flags: + --no-color remove colors from the output +``` + ### revision: ``` diff --git a/cmd/release.go b/cmd/release.go new file mode 100644 index 00000000..8b55c572 --- /dev/null +++ b/cmd/release.go @@ -0,0 +1,111 @@ +package cmd + +import ( + "errors" + "fmt" + "os" + + "github.com/spf13/cobra" + "k8s.io/helm/pkg/helm" + + "github.com/databus23/helm-diff/diff" + "github.com/databus23/helm-diff/manifest" +) + +type release struct { + client helm.Interface + detailedExitCode bool + suppressedKinds []string + releases []string + outputContext int + includeTests bool +} + +const releaseCmdLongUsage = ` +This command compares the manifests details of a different releases created from the same chart + +It can be used to compare the manifests of + + - release1 with release2 + $ helm diff release [flags] release1 release2 + Example: + $ helm diff release my-prod my-stage +` + +func releaseCmd() *cobra.Command { + diff := release{} + releaseCmd := &cobra.Command{ + Use: "release [flags] RELEASE release1 [release2]", + Short: "Shows diff between release's manifests", + Long: releaseCmdLongUsage, + PreRun: func(*cobra.Command, []string) { + expandTLSPaths() + }, + RunE: func(cmd *cobra.Command, args []string) error { + // Suppress the command usage on error. See #77 for more info + cmd.SilenceUsage = true + + 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 \"release\".\nMinimum 2 arguments required: release name-1, release name-2") + } + + if q, _ := cmd.Flags().GetBool("suppress-secrets"); q { + diff.suppressedKinds = append(diff.suppressedKinds, "Secret") + } + + diff.releases = args[0:] + if diff.client == nil { + diff.client = createHelmClient() + } + return diff.differentiate() + }, + } + + releaseCmd.Flags().BoolP("suppress-secrets", "q", false, "suppress secrets in the output") + releaseCmd.Flags().StringArrayVar(&diff.suppressedKinds, "suppress", []string{}, "allows suppression of the values listed in the diff output") + releaseCmd.Flags().IntVarP(&diff.outputContext, "context", "C", -1, "output NUM lines of context around changes") + releaseCmd.Flags().BoolVar(&diff.includeTests, "include-tests", false, "enable the diffing of the helm test hooks") + releaseCmd.SuggestionsMinimumDistance = 1 + + addCommonCmdOptions(releaseCmd.Flags()) + + return releaseCmd +} + +func (d *release) differentiate() error { + + releaseResponse1, err := d.client.ReleaseContent(d.releases[0]) + if err != nil { + return prettyError(err) + } + + releaseResponse2, err := d.client.ReleaseContent(d.releases[1]) + if err != nil { + return prettyError(err) + } + + if releaseResponse1.Release.Chart.Metadata.Name == releaseResponse2.Release.Chart.Metadata.Name { + seenAnyChanges := diff.DiffReleases( + manifest.ParseRelease(releaseResponse1.Release, d.includeTests), + manifest.ParseRelease(releaseResponse2.Release, d.includeTests), + d.suppressedKinds, + d.outputContext, + os.Stdout) + + if d.detailedExitCode && seenAnyChanges { + return Error{ + error: errors.New("identified at least one change, exiting with non-zero exit code (detailed-exitcode parameter enabled)"), + Code: 2, + } + } + } else { + fmt.Printf("Error : Incomparable Releases \n Unable to compare releases from two different charts \"%s\", \"%s\". \n try helm diff release --help to know more \n", releaseResponse1.Release.Chart.Metadata.Name, releaseResponse2.Release.Chart.Metadata.Name) + } + return nil +} diff --git a/cmd/root.go b/cmd/root.go index 0d6f9957..5e04320b 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -56,6 +56,7 @@ func New() *cobra.Command { cmd.AddCommand( revisionCmd(), rollbackCmd(), + releaseCmd(), ) cmd.SetHelpCommand(&cobra.Command{}) // Disable the help command return cmd diff --git a/diff/diff.go b/diff/diff.go index baac52ba..80cafb79 100644 --- a/diff/diff.go +++ b/diff/diff.go @@ -4,6 +4,7 @@ import ( "fmt" "io" "math" + "sort" "strings" "github.com/aryann/difflib" @@ -51,6 +52,13 @@ func DiffManifests(oldIndex, newIndex map[string]*manifest.MappingResult, suppre return seenAnyChanges } +// DiffReleases reindex the content based on the template names and pass it to DiffManifests +func DiffReleases(oldIndex, newIndex map[string]*manifest.MappingResult, suppressedKinds []string, context int, to io.Writer) bool { + oldIndex = reIndexForRelease(oldIndex) + newIndex = reIndexForRelease(newIndex) + return DiffManifests(oldIndex, newIndex, suppressedKinds, context, to) +} + func diffMappingResults(oldContent *manifest.MappingResult, newContent *manifest.MappingResult) []difflib.DiffRecord { return diffStrings(oldContent.Content, newContent.Content) } @@ -137,3 +145,34 @@ func calculateDistances(diffs []difflib.DiffRecord) map[int]int { return distances } + +// reIndexForRelease based on template names +func reIndexForRelease(index map[string]*manifest.MappingResult) map[string]*manifest.MappingResult { + + // sort the index to iterate map in the same order + var keys []string + for key := range index { + keys = append(keys, key) + } + sort.Strings(keys) + + // holds number of object in a single file + count := make(map[string]int) + + newIndex := make(map[string]*manifest.MappingResult) + + for key := range keys { + + str := strings.Replace(strings.Split(index[keys[key]].Content, "\n")[0], "# Source: ", "", 1) + + if _, ok := newIndex[str]; ok { + count[str]++ + str += fmt.Sprintf(" %d", count[str]) + newIndex[str] = index[keys[key]] + } else { + newIndex[str] = index[keys[key]] + count[str]++ + } + } + return newIndex +} diff --git a/glide.lock b/glide.lock index 6857e813..d8f0aaa3 100644 --- a/glide.lock +++ b/glide.lock @@ -1,8 +1,6 @@ hash: 41869ddd7c45fba60173a6fcf13ef7aa202e3f2581dfebed02e4f9fd57a364be -updated: 2018-10-11T10:43:24.76598+02:00 +updated: 2019-04-01T09:44:39.823854424+05:30 imports: -- name: github.com/aokoli/goutils - version: 3391d3790d23d03408670993e957e8f408993c34 - name: github.com/aryann/difflib version: a1a4dd44eb11820695fbe83e00fb2301ee6eb54c - name: github.com/BurntSushi/toml @@ -20,7 +18,7 @@ imports: - util/runes - util/strings - name: github.com/golang/protobuf - version: 1643683e1b54a9e88ad26d98f81400c8c9d9f4f9 + version: b4deda0973fb4c70b50d226b1af49f3da59f5265 subpackages: - proto - ptypes @@ -30,15 +28,17 @@ imports: - name: github.com/google/uuid version: 064e2069ce9c359c118179501254f67d7d37ba24 - name: github.com/huandu/xstrings - version: 8bbcf2f9ccb55755e748b7644164cd4bdce94c1d + version: f02667b379e2fb5916c3cda2cf31e0eb885d79f8 - name: github.com/imdario/mergo version: 6633656539c1639d9d78127b7d47c622b5d7b6dc - name: github.com/inconshreveable/mousetrap version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75 +- name: github.com/Masterminds/goutils + version: 41ac8693c5c10a92ea1ff5ac3a7f95646f6123b0 - name: github.com/Masterminds/semver version: 517734cc7d6470c0d07130e40fd40bdeb9bcd3fd - name: github.com/Masterminds/sprig - version: 15f9564e7e9cf0da02a48e0d25f12a7b83559aa6 + version: 9f8fceff796fb9f4e992cd2bece016be0121ab74 - name: github.com/mattn/go-colorable version: ded68f7a9561c023e790de24279db7ebf473ea80 - name: github.com/mattn/go-isatty @@ -50,7 +50,7 @@ imports: - name: github.com/spf13/pflag version: 583c0c0531f06d5278b7d917446061adc344b5cd - name: golang.org/x/crypto - version: 81e90905daefcd6fd217b62423c0908922eadb30 + version: 49796115aa4b964c318aad4f3084fdb41e9aa067 subpackages: - cast5 - openpgp @@ -86,7 +86,7 @@ imports: - unicode/bidi - unicode/norm - name: google.golang.org/genproto - version: 09f6ed296fc66555a25fe4ce95173148778dfa85 + version: d831d65fe17df2e52bcc4316d4a9f7a418701f43 subpackages: - googleapis/rpc/status - name: google.golang.org/grpc @@ -110,7 +110,7 @@ imports: - tap - transport - name: gopkg.in/yaml.v2 - version: 670d4cfef0544295bc27a114dbac37980d83185a + version: 53feefa2559fb8dfa8d81baad31be332c97d6c77 - name: k8s.io/api version: 2d6f90ab1293a1fb871cf149423ebb72aa7423aa - name: k8s.io/apimachinery @@ -118,7 +118,7 @@ imports: subpackages: - pkg/version - name: k8s.io/client-go - version: 23781f4d6632d88e869066eaebb743857aa1ef9b + version: 59698c7d9724b0f95f9dc9e7f7dfdcc3dfeceb82 subpackages: - util/homedir - name: k8s.io/helm @@ -159,7 +159,7 @@ testImports: subpackages: - difflib - name: github.com/stretchr/testify - version: f35b8ab0b5a2cef36673838d662e249dd9c94686 + version: ffdc059bfe9ce6a4e144ba849dbedead332c6053 subpackages: - assert - require