Skip to content
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
46 changes: 46 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ jobs:
- name: Run unit tests
run: make test

- name: Verify README flag tables
run: make verify-readme

- name: Verify installation
run: |
mkdir -p helmhome
Expand Down Expand Up @@ -150,3 +153,46 @@ jobs:

- name: helm diff upgrade -C 3 --set replicaCount=2 --install helm-diff ./helm-diff
run: helm diff upgrade -C 3 --set replicaCount=2 --install helm-diff ./helm-diff

# Flux-style setup: the release storage (sh.helm.release.v1.* secrets) lives
# in a separate storage namespace (flux-system) while the workloads are
# deployed into the target namespace (prod-apps). Exercises --storage-namespace,
# HELM_DIFF_STORAGE_NAMESPACE and the three-way-merge pipeline against a
# real cluster.
- name: Install release into prod-apps and move its storage to flux-system
run: |
helm upgrade -i storage-diff ./helm-diff -n prod-apps --create-namespace
kubectl create namespace flux-system
for secret in $(kubectl get secrets -n prod-apps -o name | grep '^secret/sh\.helm\.release\.v1\.storage-diff\.'); do
kubectl get -n prod-apps "${secret}" -o json \
| jq --arg ns flux-system '.metadata.namespace = $ns | del(.metadata.resourceVersion, .metadata.uid, .metadata.creationTimestamp)' \
| kubectl apply -f -
kubectl delete -n prod-apps "${secret}"
done

- name: Verify release storage is only visible in flux-system
run: |
if helm get manifest storage-diff -n prod-apps >/dev/null 2>&1; then
echo "unexpected: release storage still found in prod-apps" >&2
exit 1
fi
helm get manifest storage-diff -n flux-system >/dev/null

- name: helm diff upgrade fails without --storage-namespace
run: |
if helm diff upgrade storage-diff ./helm-diff -n prod-apps >/dev/null 2>&1; then
echo "unexpected: diff succeeded although the release storage is not in prod-apps" >&2
exit 1
fi

- name: helm diff upgrade --storage-namespace
run: helm diff upgrade storage-diff ./helm-diff -n prod-apps --storage-namespace flux-system

- name: helm diff upgrade with HELM_DIFF_STORAGE_NAMESPACE env var
run: HELM_DIFF_STORAGE_NAMESPACE=flux-system helm diff upgrade storage-diff ./helm-diff -n prod-apps

- name: helm diff upgrade --storage-namespace --three-way-merge
run: |
set -o pipefail
helm diff upgrade storage-diff ./helm-diff -n prod-apps --storage-namespace flux-system --three-way-merge --set replicaCount=2 | tee /tmp/three-way.out
grep -q 'replicas: 2' /tmp/three-way.out
9 changes: 9 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,15 @@ test:
go test -v ./... -coverprofile cover.out -race
go tool cover -func cover.out

.PHONY: readme
readme: build
scripts/gen-readme.sh bin/diff

.PHONY: verify-readme
verify-readme: build
scripts/gen-readme.sh bin/diff
git diff --exit-code README.md

.PHONY: docker-run-release
docker-run-release: export pkg=/go/src/github.com/databus23/helm-diff
docker-run-release:
Expand Down
37 changes: 35 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,10 @@ Flags:
--include-tests enable the diffing of the helm test hooks
--insecure-skip-tls-verify skip tls certificate checks for the chart download
--install enables diffing of releases that are not yet deployed via Helm (equivalent to --allow-unreleased, added to match "helm upgrade --install" command
--kube-context string name of the kubeconfig context to use
--kube-version string Kubernetes version used for Capabilities.KubeVersion
--kubeconfig string This flag is ignored, to allow passing of this top level flag to helm
-n, --namespace string namespace to assume the release to be installed into. Defaults to the current kube config namespace.
--no-color remove colors from the output. If both --no-color and --color are unspecified, coloring enabled only when the stdout is a term and TERM is not "dumb"
--no-hooks disable diffing of hooks
--normalize-manifests normalize manifests before running diff to exclude style differences from the output
Expand All @@ -178,6 +180,7 @@ Flags:
--show-secrets do not redact secret values in the output
--show-secrets-decoded decode secret values in the output
--skip-schema-validation skip validation of the rendered manifests against the Kubernetes OpenAPI schema
--storage-namespace string namespace where the helm release storage (Secret/ConfigMap) is located. Defaults to the target namespace (-n/--namespace)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: the newly added -n, --namespace flag on upgrade/revision/rollback isn't listed in these flag tables (only the --storage-namespace rows were inserted). Consider regenerating the help blocks so the tables match the actual --help output.

--strip-trailing-cr strip trailing carriage return on input
--suppress stringArray allows suppression of the kinds listed in the diff output (can specify multiple, like '--suppress Deployment --suppress Service')
--suppress-output-line-regex stringArray a regex to suppress diff output lines that match
Expand All @@ -187,7 +190,7 @@ Flags:
-f, --values valueFiles specify values in a YAML file (can specify multiple) (default [])
--version string specify the exact chart version to use. If this is not specified, the latest version is used

Additional help topcis:
Additional help topics:
diff

Use "diff [command] --help" for more information about a command.
Expand Down Expand Up @@ -323,6 +326,17 @@ Examples:
# Read the flag usage below for more information on --context.
HELM_DIFF_OUTPUT_CONTEXT=5 helm diff upgrade my-release datadog/datadog

# Set HELM_DIFF_STORAGE_NAMESPACE=flux-system to
# fetch release manifests/values/hooks from a storage namespace different from the target namespace.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be worth a caveat here: HELM_DIFF_USE_UPGRADE_DRY_RUN=true doesn't honor the storage/target separation — rendering then goes through helm upgrade --dry-run --namespace <target>, and helm resolves the release storage in the target namespace. With the release only present in the storage namespace, that invocation fails with "has no deployed releases" unless --install/--allow-unreleased is set (in which case it renders as a fresh install, which is still usable for diffing, just slightly different semantics for .Release.Revision etc.). A one-line note would help Flux users hitting this combination.

# This is equivalent to specifying the --storage-namespace flag.
HELM_DIFF_STORAGE_NAMESPACE=flux-system helm diff upgrade -n prod-apps my-release datadog/datadog

# NOTE: The storage namespace separation is not supported in combination with
# HELM_DIFF_USE_UPGRADE_DRY_RUN=true, because rendering then goes through
# `helm upgrade --dry-run`, which resolves the release storage in the target
# namespace. If the release exists only in the storage namespace, keep the
# default `helm template` based rendering instead.

Flags:
--allow-unreleased enables diffing of releases that are not yet deployed via Helm
-a, --api-versions stringArray Kubernetes api versions used for Capabilities.APIVersions
Expand All @@ -339,8 +353,10 @@ Flags:
--include-tests enable the diffing of the helm test hooks
--insecure-skip-tls-verify skip tls certificate checks for the chart download
--install enables diffing of releases that are not yet deployed via Helm (equivalent to --allow-unreleased, added to match "helm upgrade --install" command
--kube-context string name of the kubeconfig context to use
--kube-version string Kubernetes version used for Capabilities.KubeVersion
--kubeconfig string This flag is ignored, to allow passing of this top level flag to helm
-n, --namespace string namespace to assume the release to be installed into. Defaults to the current kube config namespace.
--no-hooks disable diffing of hooks
--normalize-manifests normalize manifests before running diff to exclude style differences from the output
--output string Possible values: diff, simple, template, json, structured, dyff. When set to "template", use the env var HELM_DIFF_TPL to specify the template. (default "diff")
Expand All @@ -360,6 +376,7 @@ Flags:
--show-secrets do not redact secret values in the output
--show-secrets-decoded decode secret values in the output
--skip-schema-validation skip validation of the rendered manifests against the Kubernetes OpenAPI schema
--storage-namespace string namespace where the helm release storage (Secret/ConfigMap) is located. Defaults to the target namespace (-n/--namespace)
--strip-trailing-cr strip trailing carriage return on input
--suppress stringArray allows suppression of the kinds listed in the diff output (can specify multiple, like '--suppress Deployment --suppress Service')
--suppress-output-line-regex stringArray a regex to suppress diff output lines that match
Expand Down Expand Up @@ -399,9 +416,11 @@ Flags:
-D, --find-renames float32 Enable rename detection if set to any value greater than 0. If specified, the value denotes the maximum fraction of changed content as lines added + removed compared to total lines in a diff for considering it a rename. Only objects of the same Kind are attempted to be matched
-h, --help help for release
--include-tests enable the diffing of the helm test hooks
--kube-context string name of the kubeconfig context to use
--normalize-manifests normalize manifests before running diff to exclude style differences from the output
--output string Possible values: diff, simple, template, json, structured, dyff. When set to "template", use the env var HELM_DIFF_TPL to specify the template. (default "diff")
--show-secrets do not redact secret values in the output
--show-secrets-decoded decode secret values in the output
--strip-trailing-cr strip trailing carriage return on input
--suppress stringArray allows suppression of the kinds listed in the diff output (can specify multiple, like '--suppress Deployment --suppress Service')
--suppress-output-line-regex stringArray a regex to suppress diff output lines that match
Expand Down Expand Up @@ -436,15 +455,17 @@ Usage:

Flags:
-C, --context int output NUM lines of context around changes (default -1)
--show-secrets-decoded decode secret values in the output
--detailed-exitcode return a non-zero exit code when there are changes
-D, --find-renames float32 Enable rename detection if set to any value greater than 0. If specified, the value denotes the maximum fraction of changed content as lines added + removed compared to total lines in a diff for considering it a rename. Only objects of the same Kind are attempted to be matched
-h, --help help for revision
--include-tests enable the diffing of the helm test hooks
--kube-context string name of the kubeconfig context to use
-n, --namespace string namespace to assume the release to be installed into. Defaults to the current kube config namespace.
--normalize-manifests normalize manifests before running diff to exclude style differences from the output
--output string Possible values: diff, simple, template, json, structured, dyff. When set to "template", use the env var HELM_DIFF_TPL to specify the template. (default "diff")
--show-secrets do not redact secret values in the output
--show-secrets-decoded decode secret values in the output
--storage-namespace string namespace where the helm release storage (Secret/ConfigMap) is located. Defaults to the target namespace (-n/--namespace)
--strip-trailing-cr strip trailing carriage return on input
--suppress stringArray allows suppression of the kinds listed in the diff output (can specify multiple, like '--suppress Deployment --suppress Service')
--suppress-output-line-regex stringArray a regex to suppress diff output lines that match
Expand Down Expand Up @@ -477,10 +498,13 @@ Flags:
-D, --find-renames float32 Enable rename detection if set to any value greater than 0. If specified, the value denotes the maximum fraction of changed content as lines added + removed compared to total lines in a diff for considering it a rename. Only objects of the same Kind are attempted to be matched
-h, --help help for rollback
--include-tests enable the diffing of the helm test hooks
--kube-context string name of the kubeconfig context to use
-n, --namespace string namespace to assume the release to be installed into. Defaults to the current kube config namespace.
--normalize-manifests normalize manifests before running diff to exclude style differences from the output
--output string Possible values: diff, simple, template, json, structured, dyff. When set to "template", use the env var HELM_DIFF_TPL to specify the template. (default "diff")
--show-secrets do not redact secret values in the output
--show-secrets-decoded decode secret values in the output
--storage-namespace string namespace where the helm release storage (Secret/ConfigMap) is located. Defaults to the target namespace (-n/--namespace)
--strip-trailing-cr strip trailing carriage return on input
--suppress stringArray allows suppression of the kinds listed in the diff output (can specify multiple, like '--suppress Deployment --suppress Service')
--suppress-output-line-regex stringArray a regex to suppress diff output lines that match
Expand Down Expand Up @@ -517,6 +541,15 @@ To run all tests:
go test -v ./...
```

### Updating the flag tables in this README

The per-command `Flags:` tables above are generated from the actual `--help`
output. After adding or changing a command flag, regenerate them with:
```
make readme
```
CI fails if the committed tables do not match the binary (`make verify-readme`).

## Release

Bump `version` in `plugin.yaml`:
Expand Down
4 changes: 2 additions & 2 deletions cmd/helm.go
Original file line number Diff line number Diff line change
Expand Up @@ -428,8 +428,8 @@ func (d *diffCmd) writeExistingValues(f *os.File, all bool) error {
if all {
args = append(args, "--all")
}
if d.namespace != "" {
args = append(args, "--namespace", d.namespace)
if storageNs := d.storage(); storageNs != "" {
args = append(args, "--namespace", storageNs)
}
if d.kubeContext != "" {
args = append(args, "--kube-context", d.kubeContext)
Expand Down
70 changes: 65 additions & 5 deletions cmd/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ package cmd
import (
"fmt"
"os"
"os/exec"
"strings"
"testing"

"github.com/stretchr/testify/require"
)

func shouldRunFakeHelm() bool {
Expand All @@ -17,6 +20,28 @@ func shouldRunFakeHelm() bool {
return !strings.HasPrefix(os.Args[1], "-test.")
}

// printFakeHelmVersion prints helm version build info, so that the version
// checks in cmd (see getHelmVersion) work against the fake helm.
// The version output can be overridden via HELM_DIFF_FAKE_VERSION_OUTPUT.
func printFakeHelmVersion() {
if v := os.Getenv("HELM_DIFF_FAKE_VERSION_OUTPUT"); v != "" {
fmt.Print(v)
return
}
fmt.Println(`version.BuildInfo{Version:"v3.18.0"}`)
}

// printFakeHelmOutput prints the output for a fake helm invocation.
// A `helm version` call prints helm version build info; any other
// invocation prints HELM_DIFF_FAKE_OUTPUT.
func printFakeHelmOutput() {
if len(os.Args) > 1 && os.Args[1] == "version" {
printFakeHelmVersion()
return
}
fmt.Print(os.Getenv("HELM_DIFF_FAKE_OUTPUT"))
}

func TestMain(m *testing.M) {
if shouldRunFakeHelm() {
mode := os.Getenv("HELM_DIFF_FAKE_HELM_MODE")
Expand Down Expand Up @@ -51,16 +76,51 @@ func TestMain(m *testing.M) {
case "capture_args":
argsFile := os.Getenv("HELM_DIFF_FAKE_ARGS_FILE")
if argsFile != "" {
if err := os.WriteFile(argsFile, []byte(strings.Join(os.Args[1:], " ")), 0644); err != nil {
fmt.Fprintf(os.Stderr, "failed to write fake helm args file %q: %v\n", argsFile, err)
os.Exit(1)
f, err := os.OpenFile(argsFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err == nil {
_, _ = fmt.Fprintln(f, strings.Join(os.Args[1:], " "))
_ = f.Close()
}
}
fmt.Print(os.Getenv("HELM_DIFF_FAKE_OUTPUT"))
printFakeHelmOutput()
default:
fmt.Print(os.Getenv("HELM_DIFF_FAKE_OUTPUT"))
printFakeHelmOutput()
}
os.Exit(0)
}
os.Exit(m.Run())
}

func TestFakeHelmVersionOutput(t *testing.T) {
exe, err := os.Executable()
require.NoError(t, err)

t.Run("custom version output via HELM_DIFF_FAKE_VERSION_OUTPUT", func(t *testing.T) {
t.Setenv("HELM_DIFF_FAKE_HELM", "1")
t.Setenv("HELM_DIFF_FAKE_HELM_MODE", "default")
t.Setenv("HELM_DIFF_FAKE_VERSION_OUTPUT", `version.BuildInfo{Version:"v3.99.0"}`)

out, err := exec.Command(exe, "version").CombinedOutput()
require.NoError(t, err)
require.Equal(t, `version.BuildInfo{Version:"v3.99.0"}`, string(out))
})

t.Run("default version build info", func(t *testing.T) {
t.Setenv("HELM_DIFF_FAKE_HELM", "1")
t.Setenv("HELM_DIFF_FAKE_HELM_MODE", "capture_args")

out, err := exec.Command(exe, "version").CombinedOutput()
require.NoError(t, err)
require.Equal(t, "version.BuildInfo{Version:\"v3.18.0\"}\n", string(out))
})

t.Run("non-version invocations print HELM_DIFF_FAKE_OUTPUT", func(t *testing.T) {
t.Setenv("HELM_DIFF_FAKE_HELM", "1")
t.Setenv("HELM_DIFF_FAKE_HELM_MODE", "default")
t.Setenv("HELM_DIFF_FAKE_OUTPUT", "manifest-output")

out, err := exec.Command(exe, "get", "manifest").CombinedOutput()
require.NoError(t, err)
require.Equal(t, "manifest-output", string(out))
})
}
Loading