From e3bd366aacebdf119f8d10619f6edda6ae3aaa97 Mon Sep 17 00:00:00 2001 From: Yusuke Kuoka Date: Tue, 21 Jul 2020 07:11:02 +0900 Subject: [PATCH] feat: JSON output This adds the JSON output on top of #221, which can be enabled by passing `--output json`. Internally it works exactly the same as the `template` output format with the default template. ``` ******************** Release was not present in Helm. Diff will show entire contents as new. ******************** [{ "api": "v1", "kind": "Secret", "namespace": "default", "name": "mysql-1567775891", "change": "ADD" },{ "api": "v1", "kind": "ConfigMap", "namespace": "default", "name": "mysql-1567775891-test", "change": "ADD" },{ "api": "v1", "kind": "PersistentVolumeClaim", "namespace": "default", "name": "mysql-1567775891", "change": "ADD" },{ "api": "v1", "kind": "Service", "namespace": "default", "name": "mysql-1567775891", "change": "ADD" },{ "api": "apps", "kind": "Deployment", "namespace": "default", "name": "mysql-1567775891", "change": "ADD" }] ``` Ref https://github.com/databus23/helm-diff/pull/221#pullrequestreview-451199381 --- Makefile | 8 ++-- cmd/upgrade.go | 2 +- diff/constant.go | 10 ++-- diff/diff_test.go | 35 ++++++++++---- diff/report.go | 113 +++++++++++++++++++++++++++------------------- 5 files changed, 104 insertions(+), 64 deletions(-) diff --git a/Makefile b/Makefile index 5e731d1c..f0964f70 100644 --- a/Makefile +++ b/Makefile @@ -5,13 +5,13 @@ PKG:= github.com/databus23/helm-diff LDFLAGS := -X $(PKG)/cmd.Version=$(VERSION) # Clear the "unreleased" string in BuildMetadata -LDFLAGS += -X $(PKG)/vendor/k8s.io/helm/pkg/version.BuildMetadata= -LDFLAGS += -X $(PKG)/vendor/k8s.io/helm/pkg/version.Version=$(shell grep -A1 "package: k8s.io/helm" go.mod | sed -n -e 's/[ ]*version:.*\(v[.0-9]*\).*/\1/p') +LDFLAGS += -X k8s.io/helm/pkg/version.BuildMetadata= +LDFLAGS += -X k8s.io/helm/pkg/version.Version=$(shell ./scripts/dep-helm-version.sh) .PHONY: format format: - test -z "$$(find . -path ./vendor -prune -type f -o -name '*.go' -exec gofmt -d {} + | tee /dev/stderr)" || \ - test -z "$$(find . -path ./vendor -prune -type f -o -name '*.go' -exec gofmt -w {} + | tee /dev/stderr)" + test -z "$$(find . -type f -o -name '*.go' -exec gofmt -d {} + | tee /dev/stderr)" || \ + test -z "$$(find . -type f -o -name '*.go' -exec gofmt -w {} + | tee /dev/stderr)" .PHONY: install install: build diff --git a/cmd/upgrade.go b/cmd/upgrade.go index c6da20d3..bf9292ba 100644 --- a/cmd/upgrade.go +++ b/cmd/upgrade.go @@ -112,7 +112,7 @@ func newChartCommand() *cobra.Command { f.IntVarP(&diff.outputContext, "context", "C", -1, "output NUM lines of context around changes") f.BoolVar(&diff.disableOpenAPIValidation, "disable-openapi-validation", false, "disables rendered templates validation against the Kubernetes OpenAPI Schema") 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, template. When set to \"template\", use the env var HELM_DIFF_TPL to specify the template.") + 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.") if !isHelm3() { f.StringVar(&diff.namespace, "namespace", "default", "namespace to assume the release to be installed into") } diff --git a/diff/constant.go b/diff/constant.go index c67935ee..19c9dac5 100644 --- a/diff/constant.go +++ b/diff/constant.go @@ -4,10 +4,10 @@ const defaultTemplateReport = `[ {{- $global := . -}} {{- range $idx, $entry := . -}} { - "Api": "{{ $entry.API }}", - "Kind": "{{ $entry.Kind }}", - "Namespace": "{{ $entry.Namespace }}", - "Name": "{{ $entry.Name }}", - "Change": "{{ $entry.Change }}" + "api": "{{ $entry.API }}", + "kind": "{{ $entry.Kind }}", + "namespace": "{{ $entry.Namespace }}", + "name": "{{ $entry.Name }}", + "change": "{{ $entry.Change }}" }{{ if not (last $idx $global) }},{{ end }} {{- end }}]` diff --git a/diff/diff_test.go b/diff/diff_test.go index ad36b436..15d55365 100644 --- a/diff/diff_test.go +++ b/diff/diff_test.go @@ -216,12 +216,31 @@ Plan: 0 to add, 1 to change, 0 to destroy. } require.Equal(t, `[{ - "Api": "apps", - "Kind": "Deployment", - "Namespace": "default", - "Name": "nginx", - "Change": "MODIFY" -}]`, buf1.String()) + "api": "apps", + "kind": "Deployment", + "namespace": "default", + "name": "nginx", + "change": "MODIFY" +}] +`, buf1.String()) + }) + + t.Run("OnChangeJSON", func(t *testing.T) { + + var buf1 bytes.Buffer + + if changesSeen := Manifests(specBeta, specRelease, []string{}, true, 10, "json", &buf1); !changesSeen { + t.Error("Unexpected return value from Manifests: Expected the return value to be `true` to indicate that it has seen any change(s), but was `false`") + } + + require.Equal(t, `[{ + "api": "apps", + "kind": "Deployment", + "namespace": "default", + "name": "nginx", + "change": "MODIFY" +}] +`, buf1.String()) }) t.Run("OnNoChangeTemplate", func(t *testing.T) { @@ -231,7 +250,7 @@ Plan: 0 to add, 1 to change, 0 to destroy. t.Error("Unexpected return value from Manifests: Expected the return value to be `false` to indicate that it has NOT seen any change(s), but was `true`") } - require.Equal(t, "[]", buf2.String()) + require.Equal(t, "[]\n", buf2.String()) }) t.Run("OnChangeCustomTemplate", func(t *testing.T) { @@ -241,6 +260,6 @@ Plan: 0 to add, 1 to change, 0 to destroy. t.Error("Unexpected return value from Manifests: Expected the return value to be `false` to indicate that it has NOT seen any change(s), but was `true`") } - require.Equal(t, "Resource name: nginx", buf1.String()) + require.Equal(t, "Resource name: nginx\n", buf1.String()) }) } diff --git a/diff/report.go b/diff/report.go index d805f96d..4bfd830c 100644 --- a/diff/report.go +++ b/diff/report.go @@ -33,7 +33,7 @@ type ReportEntry struct { // ReportFormat to the context to make a changes report type ReportFormat struct { - output string + output func(r *Report, to io.Writer) changestyles map[string]ChangeStyle } @@ -61,6 +61,8 @@ func (r *Report) setupReportFormat(format string) { setupSimpleReport(&report) case "template": setupTemplateReport(&report) + case "json": + setupJSONReport(&report) default: setupDiffReport(&report) } @@ -81,14 +83,7 @@ func (r *Report) addEntry(key string, suppressedKinds []string, kind string, con // print: prints entries added to the report. func (r *Report) print(to io.Writer) { - switch r.format.output { - case "simple": - printSimpleReport(r, to) - case "template": - printTemplateReport(r, to) - default: - printDiffReport(r, to) - } + r.format.output(r, to) } // clean: needed for testing @@ -98,7 +93,7 @@ func (r *Report) clean() { // setup report for default output: diff func setupDiffReport(r *Report) { - r.format.output = "diff" + r.format.output = printDiffReport r.format.changestyles = make(map[string]ChangeStyle) r.format.changestyles["ADD"] = ChangeStyle{color: "green", message: "has been added:"} r.format.changestyles["REMOVE"] = ChangeStyle{color: "red", message: "has been removed:"} @@ -116,7 +111,7 @@ func printDiffReport(r *Report, to io.Writer) { // setup report for simple output. func setupSimpleReport(r *Report) { - r.format.output = "simple" + r.format.output = printSimpleReport r.format.changestyles = make(map[string]ChangeStyle) r.format.changestyles["ADD"] = ChangeStyle{color: "green", message: "to be added."} r.format.changestyles["REMOVE"] = ChangeStyle{color: "red", message: "to be removed."} @@ -140,9 +135,55 @@ func printSimpleReport(r *Report, to io.Writer) { fmt.Fprintf(to, "Plan: %d to add, %d to change, %d to destroy.\n", summary["ADD"], summary["MODIFY"], summary["REMOVE"]) } +func newTemplate(name string) *template.Template { + // Prepare template functions + var funcsMap = template.FuncMap{ + "last": func(x int, a interface{}) bool { + return x == reflect.ValueOf(a).Len()-1 + }, + } + + return template.New(name).Funcs(funcsMap) +} + +// setup report for json output +func setupJSONReport(r *Report) { + t, err := newTemplate("entries").Parse(defaultTemplateReport) + if err != nil { + log.Fatalf("Error loadding default template: %v", err) + } + + r.format.output = templateReportPrinter(t) + r.format.changestyles = make(map[string]ChangeStyle) + r.format.changestyles["ADD"] = ChangeStyle{color: "green", message: ""} + r.format.changestyles["REMOVE"] = ChangeStyle{color: "red", message: ""} + r.format.changestyles["MODIFY"] = ChangeStyle{color: "yellow", message: ""} +} + // setup report for template output func setupTemplateReport(r *Report) { - r.format.output = "template" + var tpl *template.Template + + { + tplFile, present := os.LookupEnv("HELM_DIFF_TPL") + if present { + t, err := newTemplate(filepath.Base(tplFile)).ParseFiles(tplFile) + if err != nil { + fmt.Println(err) + log.Fatalf("Error loadding custom template") + } + tpl = t + } else { + // Render + t, err := newTemplate("entries").Parse(defaultTemplateReport) + if err != nil { + log.Fatalf("Error loadding default template") + } + tpl = t + } + } + + r.format.output = templateReportPrinter(tpl) r.format.changestyles = make(map[string]ChangeStyle) r.format.changestyles["ADD"] = ChangeStyle{color: "green", message: ""} r.format.changestyles["REMOVE"] = ChangeStyle{color: "red", message: ""} @@ -165,42 +206,22 @@ func (t *ReportTemplateSpec) loadFromKey(key string) error { } // load and print report for template output -func printTemplateReport(r *Report, to io.Writer) { - var templateDataArray []ReportTemplateSpec - - for _, entry := range r.entries { - templateData := ReportTemplateSpec{} - err := templateData.loadFromKey(entry.key) - if err != nil { - log.Println("error processing report entry") - } else { - templateData.Change = entry.changeType - templateDataArray = append(templateDataArray, templateData) +func templateReportPrinter(t *template.Template) func(r *Report, to io.Writer) { + return func(r *Report, to io.Writer) { + var templateDataArray []ReportTemplateSpec + + for _, entry := range r.entries { + templateData := ReportTemplateSpec{} + err := templateData.loadFromKey(entry.key) + if err != nil { + log.Println("error processing report entry") + } else { + templateData.Change = entry.changeType + templateDataArray = append(templateDataArray, templateData) + } } - } - // Prepare template functions - var funcsMap = template.FuncMap{ - "last": func(x int, a interface{}) bool { - return x == reflect.ValueOf(a).Len()-1 - }, - } - - tplFile, present := os.LookupEnv("HELM_DIFF_TPL") - if present { - t, err := template.New(filepath.Base(tplFile)).Funcs(funcsMap).ParseFiles(tplFile) - if err != nil { - fmt.Println(err) - log.Fatalf("Error loadding custom template") - } t.Execute(to, templateDataArray) - } else { - // Render - t, err := template.New("entries").Funcs(funcsMap).Parse(defaultTemplateReport) - if err != nil { - log.Fatalf("Error loadding default template") - } else { - t.Execute(to, templateDataArray) - } + _, _ = to.Write([]byte("\n")) } }