From a990cbad1841a20f2730cab72a222318bcbbb8e9 Mon Sep 17 00:00:00 2001 From: Jonas Rutishauser Date: Wed, 11 Sep 2019 22:47:36 +0200 Subject: [PATCH 1/2] support helm 3 using exec - uses 'helm template' for update - uses 'helm get' to retrieve releases and revisions --- cmd/helm3.go | 98 +++++++++++++++++++++++++++++++++++++++++++++++ cmd/helpers.go | 4 ++ cmd/release.go | 50 +++++++++++++++++++++++- cmd/revision.go | 71 +++++++++++++++++++++++++++++++++- cmd/rollback.go | 45 +++++++++++++++++++++- cmd/upgrade.go | 68 ++++++++++++++++++++++++++++++-- manifest/parse.go | 4 +- 7 files changed, 328 insertions(+), 12 deletions(-) create mode 100644 cmd/helm3.go diff --git a/cmd/helm3.go b/cmd/helm3.go new file mode 100644 index 00000000..29495dd4 --- /dev/null +++ b/cmd/helm3.go @@ -0,0 +1,98 @@ +package cmd + +import ( + "io/ioutil" + "os" + "os/exec" + "strconv" +) + +func getRelease(release, namespace string) ([]byte, error) { + args := []string{"get", "manifest", release} + if namespace != "" { + args = append(args, "--namespace", namespace) + } + cmd := exec.Command(os.Getenv("HELM_BIN"), args...) + return cmd.Output() +} + +func getHooks(release, namespace string) ([]byte, error) { + args := []string{"get", "hooks", release} + if namespace != "" { + args = append(args, "--namespace", namespace) + } + cmd := exec.Command(os.Getenv("HELM_BIN"), args...) + return cmd.Output() +} + +func getRevision(release string, revision int, namespace string) ([]byte, error) { + args := []string{"get", "manifest", release, "--revision", strconv.Itoa(revision)} + if namespace != "" { + args = append(args, "--namespace", namespace) + } + cmd := exec.Command(os.Getenv("HELM_BIN"), args...) + return cmd.Output() +} + +func getChart(release, namespace string) (string, error) { + args := []string{"get", release, "--template", "{{.Release.Chart.Name}}"} + if namespace != "" { + args = append(args, "--namespace", namespace) + } + cmd := exec.Command(os.Getenv("HELM_BIN"), args...) + out, err := cmd.Output() + if err != nil { + return "", err + } + return string(out), nil +} + +func (d *diffCmd) template() ([]byte, error) { + flags := []string{} + if d.devel { + flags = append(flags, "--devel") + } + if d.noHooks { + flags = append(flags, "--no-hooks") + } + if d.chartVersion != "" { + flags = append(flags, "--version", d.chartVersion) + } + if d.namespace != "" { + flags = append(flags, "--namespace", d.namespace) + } + if !d.resetValues { + if d.reuseValues { + tmpfile, err := ioutil.TempFile("", "existing-values") + if err != nil { + return nil, err + } + defer os.Remove(tmpfile.Name()) + flags = append(flags, "--values", tmpfile.Name()) + } + for _, value := range d.values { + flags = append(flags, "--set", value) + } + for _, stringValue := range d.stringValues { + flags = append(flags, "--set-string", stringValue) + } + for _, valueFile := range d.valueFiles { + flags = append(flags, "--values", valueFile) + } + for _, fileValue := range d.fileValues { + flags = append(flags, "--set-file", fileValue) + } + } + + args := []string{"template", d.release, d.chart} + args = append(args, flags...) + cmd := exec.Command(os.Getenv("HELM_BIN"), args...) + return cmd.Output() +} + +func (d *diffCmd) existingValues(f *os.File) error { + cmd := exec.Command(os.Getenv("HELM_BIN"), "get", "values", d.release, "--all") + defer f.Close() + cmd.Stdout = f + return cmd.Run() +} diff --git a/cmd/helpers.go b/cmd/helpers.go index 28ceaf47..803a2ceb 100644 --- a/cmd/helpers.go +++ b/cmd/helpers.go @@ -24,6 +24,10 @@ var ( DefaultHelmHome = filepath.Join(homedir.HomeDir(), ".helm") ) +func isHelm3() bool { + return os.Getenv("TILLER_HOST") == "" +} + func addCommonCmdOptions(f *flag.FlagSet) { settings.AddFlagsTLS(f) settings.InitTLS(f) diff --git a/cmd/release.go b/cmd/release.go index 4635cd03..b3ce8833 100644 --- a/cmd/release.go +++ b/cmd/release.go @@ -60,6 +60,9 @@ func releaseCmd() *cobra.Command { } diff.releases = args[0:] + if isHelm3() { + return diff.differentiateHelm3() + } if diff.client == nil { diff.client = createHelmClient() } @@ -70,14 +73,57 @@ func releaseCmd() *cobra.Command { 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") + if !isHelm3() { + releaseCmd.Flags().BoolVar(&diff.includeTests, "include-tests", false, "enable the diffing of the helm test hooks") + } releaseCmd.SuggestionsMinimumDistance = 1 - addCommonCmdOptions(releaseCmd.Flags()) + if !isHelm3() { + addCommonCmdOptions(releaseCmd.Flags()) + } return releaseCmd } +func (d *release) differentiateHelm3() error { + releaseResponse1, err := getRelease(d.releases[0], "") + if err != nil { + return err + } + releaseChart1, err := getChart(d.releases[0], "") + if err != nil { + return err + } + + releaseResponse2, err := getRelease(d.releases[1], "") + if err != nil { + return err + } + releaseChart2, err := getChart(d.releases[1], "") + if err != nil { + return err + } + + if releaseChart1 == releaseChart2 { + seenAnyChanges := diff.Releases( + manifest.Parse(string(releaseResponse1), ""), + manifest.Parse(string(releaseResponse2), ""), + 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", releaseChart1, releaseChart2) + } + return nil +} + func (d *release) differentiate() error { releaseResponse1, err := d.client.ReleaseContent(d.releases[0]) diff --git a/cmd/revision.go b/cmd/revision.go index 22be25ec..74cdea39 100644 --- a/cmd/revision.go +++ b/cmd/revision.go @@ -70,6 +70,9 @@ func revisionCmd() *cobra.Command { diff.release = args[0] diff.revisions = args[1:] + if isHelm3() { + return diff.differentiateHelm3() + } if diff.client == nil { diff.client = createHelmClient() } @@ -80,14 +83,78 @@ func revisionCmd() *cobra.Command { revisionCmd.Flags().BoolP("suppress-secrets", "q", false, "suppress secrets in the output") revisionCmd.Flags().StringArrayVar(&diff.suppressedKinds, "suppress", []string{}, "allows suppression of the values listed in the diff output") revisionCmd.Flags().IntVarP(&diff.outputContext, "context", "C", -1, "output NUM lines of context around changes") - revisionCmd.Flags().BoolVar(&diff.includeTests, "include-tests", false, "enable the diffing of the helm test hooks") + if !isHelm3() { + revisionCmd.Flags().BoolVar(&diff.includeTests, "include-tests", false, "enable the diffing of the helm test hooks") + } revisionCmd.SuggestionsMinimumDistance = 1 - addCommonCmdOptions(revisionCmd.Flags()) + if !isHelm3() { + addCommonCmdOptions(revisionCmd.Flags()) + } return revisionCmd } +func (d *revision) differentiateHelm3() error { + switch len(d.revisions) { + case 1: + releaseResponse, err := getRelease(d.release, "") + + if err != nil { + return err + } + + revision, _ := strconv.Atoi(d.revisions[0]) + revisionResponse, err := getRevision(d.release, revision, "") + if err != nil { + return err + } + + diff.Manifests( + manifest.Parse(string(revisionResponse), ""), + manifest.Parse(string(releaseResponse), ""), + d.suppressedKinds, + d.outputContext, + 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 := getRevision(d.release, revision1, "") + if err != nil { + return prettyError(err) + } + + revisionResponse2, err := getRevision(d.release, revision2, "") + if err != nil { + return prettyError(err) + } + + seenAnyChanges := diff.Manifests( + manifest.Parse(string(revisionResponse1), ""), + manifest.Parse(string(revisionResponse2), ""), + 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, + } + } + + default: + return errors.New("Invalid Arguments") + } + + return nil +} + func (d *revision) differentiate() error { switch len(d.revisions) { diff --git a/cmd/rollback.go b/cmd/rollback.go index 59f89966..f73626c7 100644 --- a/cmd/rollback.go +++ b/cmd/rollback.go @@ -60,6 +60,10 @@ func rollbackCmd() *cobra.Command { diff.release = args[0] diff.revisions = args[1:] + if isHelm3() { + return diff.backcastHelm3() + } + if diff.client == nil { diff.client = createHelmClient() } @@ -71,14 +75,51 @@ func rollbackCmd() *cobra.Command { rollbackCmd.Flags().BoolP("suppress-secrets", "q", false, "suppress secrets in the output") rollbackCmd.Flags().StringArrayVar(&diff.suppressedKinds, "suppress", []string{}, "allows suppression of the values listed in the diff output") rollbackCmd.Flags().IntVarP(&diff.outputContext, "context", "C", -1, "output NUM lines of context around changes") - rollbackCmd.Flags().BoolVar(&diff.includeTests, "include-tests", false, "enable the diffing of the helm test hooks") + if !isHelm3() { + rollbackCmd.Flags().BoolVar(&diff.includeTests, "include-tests", false, "enable the diffing of the helm test hooks") + } rollbackCmd.SuggestionsMinimumDistance = 1 - addCommonCmdOptions(rollbackCmd.Flags()) + if !isHelm3() { + addCommonCmdOptions(rollbackCmd.Flags()) + } return rollbackCmd } +func (d *rollback) backcastHelm3() error { + // get manifest of the latest release + releaseResponse, err := getRelease(d.release, "") + + if err != nil { + return err + } + + // get manifest of the release to rollback + revision, _ := strconv.Atoi(d.revisions[0]) + revisionResponse, err := getRevision(d.release, revision, "") + if err != nil { + return err + } + + // create a diff between the current manifest and the version of the manifest that a user is intended to rollback + seenAnyChanges := diff.Manifests( + manifest.Parse(string(releaseResponse), ""), + manifest.Parse(string(revisionResponse), ""), + 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, + } + } + + return nil +} + func (d *rollback) backcast() error { // get manifest of the latest release diff --git a/cmd/upgrade.go b/cmd/upgrade.go index 7dde2ec2..493006e5 100644 --- a/cmd/upgrade.go +++ b/cmd/upgrade.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "os" + "os/exec" "strings" "github.com/spf13/cobra" @@ -43,7 +44,9 @@ perform. ` func newChartCommand() *cobra.Command { - diff := diffCmd{} + diff := diffCmd{ + namespace: os.Getenv("HELM_NAMESPACE"), + } cmd := &cobra.Command{ Use: "upgrade [flags] [RELEASE] [CHART]", @@ -66,6 +69,9 @@ func newChartCommand() *cobra.Command { diff.release = args[0] diff.chart = args[1] + if isHelm3() { + return diff.runHelm3() + } if diff.client == nil { diff.client = createHelmClient() } @@ -85,18 +91,72 @@ func newChartCommand() *cobra.Command { f.BoolVar(&diff.resetValues, "reset-values", false, "reset the values to the ones built into the chart and merge in any new values") f.BoolVar(&diff.allowUnreleased, "allow-unreleased", false, "enables diffing of releases that are not yet deployed via Helm") f.BoolVar(&diff.noHooks, "no-hooks", false, "disable diffing of hooks") - f.BoolVar(&diff.includeTests, "include-tests", false, "enable the diffing of the helm test hooks") + if !isHelm3() { + f.BoolVar(&diff.includeTests, "include-tests", false, "enable the diffing of the helm test hooks") + } f.BoolVar(&diff.devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored.") f.StringArrayVar(&diff.suppressedKinds, "suppress", []string{}, "allows suppression of the values listed in the diff output") f.IntVarP(&diff.outputContext, "context", "C", -1, "output NUM lines of context around changes") - f.StringVar(&diff.namespace, "namespace", "default", "namespace to assume the release to be installed into") + if !isHelm3() { + f.StringVar(&diff.namespace, "namespace", "default", "namespace to assume the release to be installed into") + } - addCommonCmdOptions(f) + if !isHelm3() { + addCommonCmdOptions(f) + } return cmd } +func (d *diffCmd) runHelm3() error { + releaseManifest, err := getRelease(d.release, d.namespace) + + var newInstall bool + if err != nil && strings.Contains(string(err.(*exec.ExitError).Stderr), "release: not found") { + if d.allowUnreleased { + fmt.Printf("********************\n\n\tRelease was not present in Helm. Diff will show entire contents as new.\n\n********************\n") + err = nil + } else { + fmt.Printf("********************\n\n\tRelease was not present in Helm. Include the `--allow-unreleased` to perform diff without exiting in error.\n\n********************\n") + return err + } + + } + if err != nil { + return err + } + + installManifest, err := d.template() + if err != nil { + return err + } + + currentSpecs := make(map[string]*manifest.MappingResult) + if !newInstall { + if !d.noHooks { + hooks, err := getHooks(d.release, d.namespace) + if err != nil { + return err + } + releaseManifest = append(releaseManifest, hooks...) + } + currentSpecs = manifest.Parse(string(releaseManifest), d.namespace) + } + newSpecs := manifest.Parse(string(installManifest), d.namespace) + + seenAnyChanges := diff.Manifests(currentSpecs, newSpecs, 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, + } + } + + return nil +} + func (d *diffCmd) run() error { if d.chartVersion == "" && d.devel { d.chartVersion = ">0.0.0-0" diff --git a/manifest/parse.go b/manifest/parse.go index a2287c86..196b8643 100644 --- a/manifest/parse.go +++ b/manifest/parse.go @@ -89,8 +89,8 @@ func Parse(manifest string, defaultNamespace string) map[string]*MappingResult { result := make(map[string]*MappingResult) for scanner.Scan() { - content := scanner.Text() - if strings.TrimSpace(content) == "" { + content := strings.TrimSpace(scanner.Text()) + if content == "" { continue } var parsedMetadata metadata From 2448af6eed89675eb20b175aea4b64f5ec02541b Mon Sep 17 00:00:00 2001 From: Jonas Rutishauser Date: Tue, 15 Oct 2019 19:28:54 +0200 Subject: [PATCH 2/2] Support --include-tests in helm3 mode Signed-off-by: Jonas Rutishauser --- cmd/helpers.go | 3 +++ cmd/release.go | 21 ++++++++++++--------- cmd/revision.go | 25 ++++++++++++++----------- cmd/rollback.go | 17 ++++++++++------- cmd/upgrade.go | 17 ++++++++++++----- manifest/parse.go | 25 +++++++++++++++++++++---- manifest/parse_test.go | 20 ++++++++++++++++++++ manifest/testdata/pod_hook.yaml | 15 +++++++++++++++ 8 files changed, 107 insertions(+), 36 deletions(-) create mode 100644 manifest/testdata/pod_hook.yaml diff --git a/cmd/helpers.go b/cmd/helpers.go index 803a2ceb..e8a40ecb 100644 --- a/cmd/helpers.go +++ b/cmd/helpers.go @@ -16,6 +16,9 @@ const ( tlsCaCertDefault = "$HELM_HOME/ca.pem" tlsCertDefault = "$HELM_HOME/cert.pem" tlsKeyDefault = "$HELM_HOME/key.pem" + + helm2TestSuccessHook = "test-success" + helm3TestHook = "test" ) var ( diff --git a/cmd/release.go b/cmd/release.go index b3ce8833..b2fbc563 100644 --- a/cmd/release.go +++ b/cmd/release.go @@ -73,9 +73,7 @@ func releaseCmd() *cobra.Command { 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") - if !isHelm3() { - releaseCmd.Flags().BoolVar(&diff.includeTests, "include-tests", false, "enable the diffing of the helm test hooks") - } + releaseCmd.Flags().BoolVar(&diff.includeTests, "include-tests", false, "enable the diffing of the helm test hooks") releaseCmd.SuggestionsMinimumDistance = 1 if !isHelm3() { @@ -86,28 +84,33 @@ func releaseCmd() *cobra.Command { } func (d *release) differentiateHelm3() error { - releaseResponse1, err := getRelease(d.releases[0], "") + namespace := os.Getenv("HELM_NAMESPACE") + excludes := []string{helm3TestHook, helm2TestSuccessHook} + if d.includeTests { + excludes = []string{} + } + releaseResponse1, err := getRelease(d.releases[0], namespace) if err != nil { return err } - releaseChart1, err := getChart(d.releases[0], "") + releaseChart1, err := getChart(d.releases[0], namespace) if err != nil { return err } - releaseResponse2, err := getRelease(d.releases[1], "") + releaseResponse2, err := getRelease(d.releases[1], namespace) if err != nil { return err } - releaseChart2, err := getChart(d.releases[1], "") + releaseChart2, err := getChart(d.releases[1], namespace) if err != nil { return err } if releaseChart1 == releaseChart2 { seenAnyChanges := diff.Releases( - manifest.Parse(string(releaseResponse1), ""), - manifest.Parse(string(releaseResponse2), ""), + manifest.Parse(string(releaseResponse1), namespace, excludes...), + manifest.Parse(string(releaseResponse2), namespace, excludes...), d.suppressedKinds, d.outputContext, os.Stdout) diff --git a/cmd/revision.go b/cmd/revision.go index 74cdea39..46ba5f43 100644 --- a/cmd/revision.go +++ b/cmd/revision.go @@ -83,9 +83,7 @@ func revisionCmd() *cobra.Command { revisionCmd.Flags().BoolP("suppress-secrets", "q", false, "suppress secrets in the output") revisionCmd.Flags().StringArrayVar(&diff.suppressedKinds, "suppress", []string{}, "allows suppression of the values listed in the diff output") revisionCmd.Flags().IntVarP(&diff.outputContext, "context", "C", -1, "output NUM lines of context around changes") - if !isHelm3() { - revisionCmd.Flags().BoolVar(&diff.includeTests, "include-tests", false, "enable the diffing of the helm test hooks") - } + revisionCmd.Flags().BoolVar(&diff.includeTests, "include-tests", false, "enable the diffing of the helm test hooks") revisionCmd.SuggestionsMinimumDistance = 1 if !isHelm3() { @@ -96,23 +94,28 @@ func revisionCmd() *cobra.Command { } func (d *revision) differentiateHelm3() error { + namespace := os.Getenv("HELM_NAMESPACE") + excludes := []string{helm3TestHook, helm2TestSuccessHook} + if d.includeTests { + excludes = []string{} + } switch len(d.revisions) { case 1: - releaseResponse, err := getRelease(d.release, "") + releaseResponse, err := getRelease(d.release, namespace) if err != nil { return err } revision, _ := strconv.Atoi(d.revisions[0]) - revisionResponse, err := getRevision(d.release, revision, "") + revisionResponse, err := getRevision(d.release, revision, namespace) if err != nil { return err } diff.Manifests( - manifest.Parse(string(revisionResponse), ""), - manifest.Parse(string(releaseResponse), ""), + manifest.Parse(string(revisionResponse), namespace, excludes...), + manifest.Parse(string(releaseResponse), namespace, excludes...), d.suppressedKinds, d.outputContext, os.Stdout) @@ -124,19 +127,19 @@ func (d *revision) differentiateHelm3() error { revision1, revision2 = revision2, revision1 } - revisionResponse1, err := getRevision(d.release, revision1, "") + revisionResponse1, err := getRevision(d.release, revision1, namespace) if err != nil { return prettyError(err) } - revisionResponse2, err := getRevision(d.release, revision2, "") + revisionResponse2, err := getRevision(d.release, revision2, namespace) if err != nil { return prettyError(err) } seenAnyChanges := diff.Manifests( - manifest.Parse(string(revisionResponse1), ""), - manifest.Parse(string(revisionResponse2), ""), + manifest.Parse(string(revisionResponse1), namespace, excludes...), + manifest.Parse(string(revisionResponse2), namespace, excludes...), d.suppressedKinds, d.outputContext, os.Stdout) diff --git a/cmd/rollback.go b/cmd/rollback.go index f73626c7..74d0fea9 100644 --- a/cmd/rollback.go +++ b/cmd/rollback.go @@ -75,9 +75,7 @@ func rollbackCmd() *cobra.Command { rollbackCmd.Flags().BoolP("suppress-secrets", "q", false, "suppress secrets in the output") rollbackCmd.Flags().StringArrayVar(&diff.suppressedKinds, "suppress", []string{}, "allows suppression of the values listed in the diff output") rollbackCmd.Flags().IntVarP(&diff.outputContext, "context", "C", -1, "output NUM lines of context around changes") - if !isHelm3() { - rollbackCmd.Flags().BoolVar(&diff.includeTests, "include-tests", false, "enable the diffing of the helm test hooks") - } + rollbackCmd.Flags().BoolVar(&diff.includeTests, "include-tests", false, "enable the diffing of the helm test hooks") rollbackCmd.SuggestionsMinimumDistance = 1 if !isHelm3() { @@ -88,8 +86,13 @@ func rollbackCmd() *cobra.Command { } func (d *rollback) backcastHelm3() error { + namespace := os.Getenv("HELM_NAMESPACE") + excludes := []string{helm3TestHook, helm2TestSuccessHook} + if d.includeTests { + excludes = []string{} + } // get manifest of the latest release - releaseResponse, err := getRelease(d.release, "") + releaseResponse, err := getRelease(d.release, namespace) if err != nil { return err @@ -97,15 +100,15 @@ func (d *rollback) backcastHelm3() error { // get manifest of the release to rollback revision, _ := strconv.Atoi(d.revisions[0]) - revisionResponse, err := getRevision(d.release, revision, "") + revisionResponse, err := getRevision(d.release, revision, namespace) if err != nil { return err } // create a diff between the current manifest and the version of the manifest that a user is intended to rollback seenAnyChanges := diff.Manifests( - manifest.Parse(string(releaseResponse), ""), - manifest.Parse(string(revisionResponse), ""), + manifest.Parse(string(releaseResponse), namespace, excludes...), + manifest.Parse(string(revisionResponse), namespace, excludes...), d.suppressedKinds, d.outputContext, os.Stdout) diff --git a/cmd/upgrade.go b/cmd/upgrade.go index 493006e5..e41c8b01 100644 --- a/cmd/upgrade.go +++ b/cmd/upgrade.go @@ -91,9 +91,7 @@ func newChartCommand() *cobra.Command { f.BoolVar(&diff.resetValues, "reset-values", false, "reset the values to the ones built into the chart and merge in any new values") f.BoolVar(&diff.allowUnreleased, "allow-unreleased", false, "enables diffing of releases that are not yet deployed via Helm") f.BoolVar(&diff.noHooks, "no-hooks", false, "disable diffing of hooks") - if !isHelm3() { - f.BoolVar(&diff.includeTests, "include-tests", false, "enable the diffing of the helm test hooks") - } + f.BoolVar(&diff.includeTests, "include-tests", false, "enable the diffing of the helm test hooks") f.BoolVar(&diff.devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored.") f.StringArrayVar(&diff.suppressedKinds, "suppress", []string{}, "allows suppression of the values listed in the diff output") f.IntVarP(&diff.outputContext, "context", "C", -1, "output NUM lines of context around changes") @@ -141,9 +139,18 @@ func (d *diffCmd) runHelm3() error { } releaseManifest = append(releaseManifest, hooks...) } - currentSpecs = manifest.Parse(string(releaseManifest), d.namespace) + if d.includeTests { + currentSpecs = manifest.Parse(string(releaseManifest), d.namespace) + } else { + currentSpecs = manifest.Parse(string(releaseManifest), d.namespace, helm3TestHook, helm2TestSuccessHook) + } + } + var newSpecs map[string]*manifest.MappingResult + if d.includeTests { + newSpecs = manifest.Parse(string(installManifest), d.namespace) + } else { + newSpecs = manifest.Parse(string(installManifest), d.namespace, helm3TestHook, helm2TestSuccessHook) } - newSpecs := manifest.Parse(string(installManifest), d.namespace) seenAnyChanges := diff.Manifests(currentSpecs, newSpecs, d.suppressedKinds, d.outputContext, os.Stdout) diff --git a/manifest/parse.go b/manifest/parse.go index 196b8643..912ab077 100644 --- a/manifest/parse.go +++ b/manifest/parse.go @@ -11,6 +11,10 @@ import ( "k8s.io/helm/pkg/proto/hapi/release" ) +const ( + hookAnnotation = "helm.sh/hook" +) + var yamlSeperator = []byte("\n---\n") // MappingResult to store result of diff @@ -24,8 +28,9 @@ type metadata struct { APIVersion string `yaml:"apiVersion"` Kind string Metadata struct { - Namespace string - Name string + Namespace string + Name string + Annotations map[string]string } } @@ -78,7 +83,7 @@ func ParseRelease(release *release.Release, includeTests bool) map[string]*Mappi } // Parse parses manifest strings into MappingResult -func Parse(manifest string, defaultNamespace string) map[string]*MappingResult { +func Parse(manifest string, defaultNamespace string, excludedHooks ...string) map[string]*MappingResult { scanner := bufio.NewScanner(strings.NewReader(manifest)) scanner.Split(scanYamlSpecs) //Allow for tokens (specs) up to 1M in size @@ -100,7 +105,10 @@ func Parse(manifest string, defaultNamespace string) map[string]*MappingResult { //Skip content without any metadata. It is probably a template that //only contains comments in the current state. - if (metadata{}) == parsedMetadata { + if parsedMetadata.APIVersion == "" && parsedMetadata.Kind == "" { + continue + } + if isHook(parsedMetadata, excludedHooks...) { continue } @@ -124,6 +132,15 @@ func Parse(manifest string, defaultNamespace string) map[string]*MappingResult { return result } +func isHook(metadata metadata, hooks ...string) bool { + for _, hook := range hooks { + if metadata.Metadata.Annotations[hookAnnotation] == hook { + return true + } + } + return false +} + func isTestHook(hookEvents []release.Hook_Event) bool { for _, event := range hookEvents { if event == release.Hook_RELEASE_TEST_FAILURE || event == release.Hook_RELEASE_TEST_SUCCESS { diff --git a/manifest/parse_test.go b/manifest/parse_test.go index 9c4cb3cf..731b5bc2 100644 --- a/manifest/parse_test.go +++ b/manifest/parse_test.go @@ -39,6 +39,26 @@ func TestPodNamespace(t *testing.T) { ) } +func TestPodHook(t *testing.T) { + spec, err := ioutil.ReadFile("testdata/pod_hook.yaml") + require.NoError(t, err) + + require.Equal(t, + []string{"default, nginx, Pod (v1)"}, + foundObjects(Parse(string(spec), "default")), + ) + + require.Equal(t, + []string{"default, nginx, Pod (v1)"}, + foundObjects(Parse(string(spec), "default", "test-success")), + ) + + require.Equal(t, + []string{}, + foundObjects(Parse(string(spec), "default", "test")), + ) +} + func TestDeployV1(t *testing.T) { spec, err := ioutil.ReadFile("testdata/deploy_v1.yaml") require.NoError(t, err) diff --git a/manifest/testdata/pod_hook.yaml b/manifest/testdata/pod_hook.yaml new file mode 100644 index 00000000..1e7ac447 --- /dev/null +++ b/manifest/testdata/pod_hook.yaml @@ -0,0 +1,15 @@ + +--- +# Source: nginx/pod.yaml +apiVersion: v1 +kind: Pod +metadata: + name: nginx + annotations: + helm.sh/hook: test +spec: + containers: + - name: nginx + image: nginx:1.7.9 + ports: + - containerPort: 80