Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Normalize YAML manifests before running diff #273

Merged
merged 2 commits into from
Jun 4, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 15 additions & 13 deletions cmd/release.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,16 @@ import (
)

type release struct {
client helm.Interface
detailedExitCode bool
suppressedKinds []string
releases []string
outputContext int
includeTests bool
showSecrets bool
output string
stripTrailingCR bool
client helm.Interface
detailedExitCode bool
suppressedKinds []string
releases []string
outputContext int
includeTests bool
showSecrets bool
output string
stripTrailingCR bool
normalizeManifests bool
}

const releaseCmdLongUsage = `
Expand Down Expand Up @@ -81,6 +82,7 @@ func releaseCmd() *cobra.Command {
releaseCmd.Flags().BoolVar(&diff.includeTests, "include-tests", false, "enable the diffing of the helm test hooks")
releaseCmd.Flags().StringVar(&diff.output, "output", "diff", "Possible values: diff, simple, template. When set to \"template\", use the env var HELM_DIFF_TPL to specify the template.")
releaseCmd.Flags().BoolVar(&diff.stripTrailingCR, "strip-trailing-cr", false, "strip trailing carriage return on input")
releaseCmd.Flags().BoolVar(&diff.normalizeManifests, "normalize-manifests", false, "normalize manifests before running diff to exclude style differences from the output")

releaseCmd.SuggestionsMinimumDistance = 1

Expand Down Expand Up @@ -117,8 +119,8 @@ func (d *release) differentiateHelm3() error {

if releaseChart1 == releaseChart2 {
seenAnyChanges := diff.Releases(
manifest.Parse(string(releaseResponse1), namespace, excludes...),
manifest.Parse(string(releaseResponse2), namespace, excludes...),
manifest.Parse(string(releaseResponse1), namespace, d.normalizeManifests, excludes...),
manifest.Parse(string(releaseResponse2), namespace, d.normalizeManifests, excludes...),
d.suppressedKinds,
d.showSecrets,
d.outputContext,
Expand Down Expand Up @@ -152,8 +154,8 @@ func (d *release) differentiate() error {

if releaseResponse1.Release.Chart.Metadata.Name == releaseResponse2.Release.Chart.Metadata.Name {
seenAnyChanges := diff.Releases(
manifest.ParseRelease(releaseResponse1.Release, d.includeTests),
manifest.ParseRelease(releaseResponse2.Release, d.includeTests),
manifest.ParseRelease(releaseResponse1.Release, d.includeTests, d.normalizeManifests),
manifest.ParseRelease(releaseResponse2.Release, d.includeTests, d.normalizeManifests),
d.suppressedKinds,
d.showSecrets,
d.outputContext,
Expand Down
38 changes: 20 additions & 18 deletions cmd/revision.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,17 @@ import (
)

type revision struct {
release string
client helm.Interface
detailedExitCode bool
suppressedKinds []string
revisions []string
outputContext int
includeTests bool
showSecrets bool
output string
stripTrailingCR bool
release string
client helm.Interface
detailedExitCode bool
suppressedKinds []string
revisions []string
outputContext int
includeTests bool
showSecrets bool
output string
stripTrailingCR bool
normalizeManifests bool
}

const revisionCmdLongUsage = `
Expand Down Expand Up @@ -91,6 +92,7 @@ func revisionCmd() *cobra.Command {
revisionCmd.Flags().BoolVar(&diff.includeTests, "include-tests", false, "enable the diffing of the helm test hooks")
revisionCmd.Flags().StringVar(&diff.output, "output", "diff", "Possible values: diff, simple, template. When set to \"template\", use the env var HELM_DIFF_TPL to specify the template.")
revisionCmd.Flags().BoolVar(&diff.stripTrailingCR, "strip-trailing-cr", false, "strip trailing carriage return on input")
revisionCmd.Flags().BoolVar(&diff.normalizeManifests, "normalize-manifests", false, "normalize manifests before running diff to exclude style differences from the output")

revisionCmd.SuggestionsMinimumDistance = 1

Expand Down Expand Up @@ -122,8 +124,8 @@ func (d *revision) differentiateHelm3() error {
}

diff.Manifests(
manifest.Parse(string(revisionResponse), namespace, excludes...),
manifest.Parse(string(releaseResponse), namespace, excludes...),
manifest.Parse(string(revisionResponse), namespace, d.normalizeManifests, excludes...),
manifest.Parse(string(releaseResponse), namespace, d.normalizeManifests, excludes...),
d.suppressedKinds,
d.showSecrets,
d.outputContext,
Expand All @@ -149,8 +151,8 @@ func (d *revision) differentiateHelm3() error {
}

seenAnyChanges := diff.Manifests(
manifest.Parse(string(revisionResponse1), namespace, excludes...),
manifest.Parse(string(revisionResponse2), namespace, excludes...),
manifest.Parse(string(revisionResponse1), namespace, d.normalizeManifests, excludes...),
manifest.Parse(string(revisionResponse2), namespace, d.normalizeManifests, excludes...),
d.suppressedKinds,
d.showSecrets,
d.outputContext,
Expand Down Expand Up @@ -189,8 +191,8 @@ func (d *revision) differentiate() error {
}

diff.Manifests(
manifest.ParseRelease(revisionResponse.Release, d.includeTests),
manifest.ParseRelease(releaseResponse.Release, d.includeTests),
manifest.ParseRelease(revisionResponse.Release, d.includeTests, d.normalizeManifests),
manifest.ParseRelease(releaseResponse.Release, d.includeTests, d.normalizeManifests),
d.suppressedKinds,
d.showSecrets,
d.outputContext,
Expand All @@ -216,8 +218,8 @@ func (d *revision) differentiate() error {
}

seenAnyChanges := diff.Manifests(
manifest.ParseRelease(revisionResponse1.Release, d.includeTests),
manifest.ParseRelease(revisionResponse2.Release, d.includeTests),
manifest.ParseRelease(revisionResponse1.Release, d.includeTests, d.normalizeManifests),
manifest.ParseRelease(revisionResponse2.Release, d.includeTests, d.normalizeManifests),
d.suppressedKinds,
d.showSecrets,
d.outputContext,
Expand Down
30 changes: 16 additions & 14 deletions cmd/rollback.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,17 @@ import (
)

type rollback struct {
release string
client helm.Interface
detailedExitCode bool
suppressedKinds []string
revisions []string
outputContext int
includeTests bool
showSecrets bool
output string
stripTrailingCR bool
release string
client helm.Interface
detailedExitCode bool
suppressedKinds []string
revisions []string
outputContext int
includeTests bool
showSecrets bool
output string
stripTrailingCR bool
normalizeManifests bool
}

const rollbackCmdLongUsage = `
Expand Down Expand Up @@ -83,6 +84,7 @@ func rollbackCmd() *cobra.Command {
rollbackCmd.Flags().BoolVar(&diff.includeTests, "include-tests", false, "enable the diffing of the helm test hooks")
rollbackCmd.Flags().StringVar(&diff.output, "output", "diff", "Possible values: diff, simple, template. When set to \"template\", use the env var HELM_DIFF_TPL to specify the template.")
rollbackCmd.Flags().BoolVar(&diff.stripTrailingCR, "strip-trailing-cr", false, "strip trailing carriage return on input")
rollbackCmd.Flags().BoolVar(&diff.normalizeManifests, "normalize-manifests", false, "normalize manifests before running diff to exclude style differences from the output")

rollbackCmd.SuggestionsMinimumDistance = 1

Expand Down Expand Up @@ -115,8 +117,8 @@ func (d *rollback) backcastHelm3() error {

// 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), namespace, excludes...),
manifest.Parse(string(revisionResponse), namespace, excludes...),
manifest.Parse(string(releaseResponse), namespace, d.normalizeManifests, excludes...),
manifest.Parse(string(revisionResponse), namespace, d.normalizeManifests, excludes...),
d.suppressedKinds,
d.showSecrets,
d.outputContext,
Expand Down Expand Up @@ -152,8 +154,8 @@ func (d *rollback) backcast() error {

// create a diff between the current manifest and the version of the manifest that a user is intended to rollback
seenAnyChanges := diff.Manifests(
manifest.ParseRelease(releaseResponse.Release, d.includeTests),
manifest.ParseRelease(revisionResponse.Release, d.includeTests),
manifest.ParseRelease(releaseResponse.Release, d.includeTests, d.normalizeManifests),
manifest.ParseRelease(revisionResponse.Release, d.includeTests, d.normalizeManifests),
d.suppressedKinds,
d.showSecrets,
d.outputContext,
Expand Down
20 changes: 11 additions & 9 deletions cmd/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ type diffCmd struct {
output string
install bool
stripTrailingCR bool
normalizeManifests bool
}

func (d *diffCmd) isAllowUnreleased() bool {
Expand Down Expand Up @@ -121,6 +122,7 @@ func newChartCommand() *cobra.Command {
f.StringVar(&diff.postRenderer, "post-renderer", "", "the path to an executable to be used for post rendering. If it exists in $PATH, the binary will be used, otherwise it will try to look for the executable at the given path")
f.StringVar(&diff.output, "output", "diff", "Possible values: diff, simple, json, template. When set to \"template\", use the env var HELM_DIFF_TPL to specify the template.")
f.BoolVar(&diff.stripTrailingCR, "strip-trailing-cr", false, "strip trailing carriage return on input")
f.BoolVar(&diff.normalizeManifests, "normalize-manifests", false, "normalize manifests before running diff to exclude style differences from the output")
if !isHelm3() {
f.StringVar(&diff.namespace, "namespace", "default", "namespace to assume the release to be installed into")
}
Expand Down Expand Up @@ -177,16 +179,16 @@ func (d *diffCmd) runHelm3() error {
releaseManifest = append(releaseManifest, hooks...)
}
if d.includeTests {
currentSpecs = manifest.Parse(string(releaseManifest), d.namespace)
currentSpecs = manifest.Parse(string(releaseManifest), d.namespace, d.normalizeManifests)
} else {
currentSpecs = manifest.Parse(string(releaseManifest), d.namespace, helm3TestHook, helm2TestSuccessHook)
currentSpecs = manifest.Parse(string(releaseManifest), d.namespace, d.normalizeManifests, helm3TestHook, helm2TestSuccessHook)
}
}
var newSpecs map[string]*manifest.MappingResult
if d.includeTests {
newSpecs = manifest.Parse(string(installManifest), d.namespace)
newSpecs = manifest.Parse(string(installManifest), d.namespace, d.normalizeManifests)
} else {
newSpecs = manifest.Parse(string(installManifest), d.namespace, helm3TestHook, helm2TestSuccessHook)
newSpecs = manifest.Parse(string(installManifest), d.namespace, d.normalizeManifests, helm3TestHook, helm2TestSuccessHook)
}
seenAnyChanges := diff.Manifests(currentSpecs, newSpecs, d.suppressedKinds, d.showSecrets, d.outputContext, d.output, d.stripTrailingCR, os.Stdout)

Expand Down Expand Up @@ -251,7 +253,7 @@ func (d *diffCmd) run() error {
}

currentSpecs = make(map[string]*manifest.MappingResult)
newSpecs = manifest.Parse(installResponse.Release.Manifest, installResponse.Release.Namespace)
newSpecs = manifest.Parse(installResponse.Release.Manifest, installResponse.Release.Namespace, d.normalizeManifests)
} else {
upgradeResponse, err := d.client.UpdateRelease(
d.release,
Expand All @@ -266,11 +268,11 @@ func (d *diffCmd) run() error {
}

if d.noHooks {
currentSpecs = manifest.Parse(releaseResponse.Release.Manifest, releaseResponse.Release.Namespace)
newSpecs = manifest.Parse(upgradeResponse.Release.Manifest, upgradeResponse.Release.Namespace)
currentSpecs = manifest.Parse(releaseResponse.Release.Manifest, releaseResponse.Release.Namespace, d.normalizeManifests)
newSpecs = manifest.Parse(upgradeResponse.Release.Manifest, upgradeResponse.Release.Namespace, d.normalizeManifests)
} else {
currentSpecs = manifest.ParseRelease(releaseResponse.Release, d.includeTests)
newSpecs = manifest.ParseRelease(upgradeResponse.Release, d.includeTests)
currentSpecs = manifest.ParseRelease(releaseResponse.Release, d.includeTests, d.normalizeManifests)
newSpecs = manifest.ParseRelease(upgradeResponse.Release, d.includeTests, d.normalizeManifests)
}
}

Expand Down
27 changes: 21 additions & 6 deletions manifest/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func splitSpec(token string) (string, string) {
}

// ParseRelease parses release objects into MappingResult
func ParseRelease(release *release.Release, includeTests bool) map[string]*MappingResult {
func ParseRelease(release *release.Release, includeTests bool, normalizeManifests bool) map[string]*MappingResult {
manifest := release.Manifest
for _, hook := range release.Hooks {
if !includeTests && isTestHook(hook.Events) {
Expand All @@ -79,11 +79,11 @@ func ParseRelease(release *release.Release, includeTests bool) map[string]*Mappi
manifest += fmt.Sprintf("# Source: %s\n", hook.Path)
manifest += hook.Manifest
}
return Parse(manifest, release.Namespace)
return Parse(manifest, release.Namespace, normalizeManifests)
}

// Parse parses manifest strings into MappingResult
func Parse(manifest string, defaultNamespace string, excludedHooks ...string) map[string]*MappingResult {
func Parse(manifest string, defaultNamespace string, normalizeManifests bool, excludedHooks ...string) map[string]*MappingResult {
// Ensure we have a newline in front of the yaml seperator
scanner := bufio.NewScanner(strings.NewReader("\n" + manifest))
scanner.Split(scanYamlSpecs)
Expand All @@ -100,7 +100,7 @@ func Parse(manifest string, defaultNamespace string, excludedHooks ...string) ma
continue
}

parsed, err := parseContent(content, defaultNamespace, excludedHooks...)
parsed, err := parseContent(content, defaultNamespace, normalizeManifests, excludedHooks...)
if err != nil {
log.Fatalf("%v", err)
}
Expand All @@ -121,7 +121,7 @@ func Parse(manifest string, defaultNamespace string, excludedHooks ...string) ma
return result
}

func parseContent(content string, defaultNamespace string, excludedHooks ...string) ([]*MappingResult, error) {
func parseContent(content string, defaultNamespace string, normalizeManifests bool, excludedHooks ...string) ([]*MappingResult, error) {
var parsedMetadata metadata
if err := yaml.Unmarshal([]byte(content), &parsedMetadata); err != nil {
log.Fatalf("YAML unmarshal error: %s\nCan't unmarshal %s", err, content)
Expand Down Expand Up @@ -152,7 +152,7 @@ func parseContent(content string, defaultNamespace string, excludedHooks ...stri
log.Printf("YAML marshal error: %s\nCan't marshal %v", err, item)
}

subs, err := parseContent(string(subcontent), defaultNamespace, excludedHooks...)
subs, err := parseContent(string(subcontent), defaultNamespace, normalizeManifests, excludedHooks...)
if err != nil {
return nil, fmt.Errorf("Parsing YAML list item: %v", err)
}
Expand All @@ -163,6 +163,21 @@ func parseContent(content string, defaultNamespace string, excludedHooks ...stri
return result, nil
}

if normalizeManifests {
// Unmarshal and marshal again content to normalize yaml structure
// This avoids style differences to show up as diffs but it can
// make the output different from the original template (since it is in normalized form)
var object map[interface{}]interface{}
if err := yaml.Unmarshal([]byte(content), &object); err != nil {
log.Fatalf("YAML unmarshal error: %s\nCan't unmarshal %s", err, content)
}
normalizedContent, err := yaml.Marshal(object)
if err != nil {
log.Fatalf("YAML marshal error: %s\nCan't marshal %v", err, object)
}
content = string(normalizedContent)
}

if isHook(parsedMetadata, excludedHooks...) {
return nil, nil
}
Expand Down
Loading