From 0d43d9541af0c188753f7717c85a73094f2024a8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 8 Sep 2025 01:11:35 +0000 Subject: [PATCH 1/3] Initial plan From b542ea9eac159256ef53c499b9dc1348e620757c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 8 Sep 2025 01:26:40 +0000 Subject: [PATCH 2/3] Implement --kube-context flag support for all helm-diff commands Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com> --- cmd/helm3.go | 29 ++++++++++++-- cmd/kube_context_test.go | 86 ++++++++++++++++++++++++++++++++++++++++ cmd/release.go | 10 +++-- cmd/revision.go | 10 +++-- cmd/rollback.go | 6 ++- cmd/upgrade.go | 7 ++-- 6 files changed, 131 insertions(+), 17 deletions(-) create mode 100644 cmd/kube_context_test.go diff --git a/cmd/helm3.go b/cmd/helm3.go index f624656a..a55f91ad 100644 --- a/cmd/helm3.go +++ b/cmd/helm3.go @@ -65,38 +65,50 @@ func compatibleHelm3Version() error { return nil } -func getRelease(release, namespace string) ([]byte, error) { +func getRelease(release, namespace, kubeContext string) ([]byte, error) { args := []string{"get", "manifest", release} if namespace != "" { args = append(args, "--namespace", namespace) } + if kubeContext != "" { + args = append(args, "--kube-context", kubeContext) + } cmd := exec.Command(os.Getenv("HELM_BIN"), args...) return outputWithRichError(cmd) } -func getHooks(release, namespace string) ([]byte, error) { +func getHooks(release, namespace, kubeContext string) ([]byte, error) { args := []string{"get", "hooks", release} if namespace != "" { args = append(args, "--namespace", namespace) } + if kubeContext != "" { + args = append(args, "--kube-context", kubeContext) + } cmd := exec.Command(os.Getenv("HELM_BIN"), args...) return outputWithRichError(cmd) } -func getRevision(release string, revision int, namespace string) ([]byte, error) { +func getRevision(release string, revision int, namespace, kubeContext string) ([]byte, error) { args := []string{"get", "manifest", release, "--revision", strconv.Itoa(revision)} if namespace != "" { args = append(args, "--namespace", namespace) } + if kubeContext != "" { + args = append(args, "--kube-context", kubeContext) + } cmd := exec.Command(os.Getenv("HELM_BIN"), args...) return outputWithRichError(cmd) } -func getChart(release, namespace string) (string, error) { +func getChart(release, namespace, kubeContext string) (string, error) { args := []string{"get", "all", release, "--template", "{{.Release.Chart.Name}}"} if namespace != "" { args = append(args, "--namespace", namespace) } + if kubeContext != "" { + args = append(args, "--kube-context", kubeContext) + } cmd := exec.Command(os.Getenv("HELM_BIN"), args...) out, err := outputWithRichError(cmd) if err != nil { @@ -125,6 +137,9 @@ func (d *diffCmd) template(isUpgrade bool) ([]byte, error) { if d.namespace != "" { flags = append(flags, "--namespace", d.namespace) } + if d.kubeContext != "" { + flags = append(flags, "--kube-context", d.kubeContext) + } if d.postRenderer != "" { flags = append(flags, "--post-renderer", d.postRenderer) } @@ -341,6 +356,12 @@ 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 d.kubeContext != "" { + args = append(args, "--kube-context", d.kubeContext) + } cmd := exec.Command(os.Getenv("HELM_BIN"), args...) debugPrint("Executing %s", strings.Join(cmd.Args, " ")) defer func() { diff --git a/cmd/kube_context_test.go b/cmd/kube_context_test.go new file mode 100644 index 00000000..67a6cd62 --- /dev/null +++ b/cmd/kube_context_test.go @@ -0,0 +1,86 @@ +package cmd + +import ( + "testing" +) + +func TestKubeContextFlag(t *testing.T) { + tests := []struct { + name string + kubeContext string + expected bool + }{ + { + name: "with kube-context", + kubeContext: "test-context", + expected: true, + }, + { + name: "without kube-context", + kubeContext: "", + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Test diffCmd struct + d := &diffCmd{ + kubeContext: tt.kubeContext, + namespace: "test-namespace", + } + + // Verify the field is set correctly + if (d.kubeContext != "") != tt.expected { + t.Errorf("kubeContext field: expected %v, got %v", tt.expected, d.kubeContext != "") + } + + // Test release struct + r := &release{ + kubeContext: tt.kubeContext, + } + + if (r.kubeContext != "") != tt.expected { + t.Errorf("release kubeContext field: expected %v, got %v", tt.expected, r.kubeContext != "") + } + + // Test revision struct + rev := &revision{ + kubeContext: tt.kubeContext, + } + + if (rev.kubeContext != "") != tt.expected { + t.Errorf("revision kubeContext field: expected %v, got %v", tt.expected, rev.kubeContext != "") + } + + // Test rollback struct + rb := &rollback{ + kubeContext: tt.kubeContext, + } + + if (rb.kubeContext != "") != tt.expected { + t.Errorf("rollback kubeContext field: expected %v, got %v", tt.expected, rb.kubeContext != "") + } + }) + } +} + +func TestKubeContextInDiffCmd(t *testing.T) { + // Test that the diffCmd has the kubeContext field in the struct + d := diffCmd{ + kubeContext: "test-context", + namespace: "test-namespace", + release: "test-release", + chart: "test-chart", + } + + if d.kubeContext != "test-context" { + t.Errorf("Expected kubeContext to be 'test-context', got '%s'", d.kubeContext) + } + + // Test that the field can be set to empty + d.kubeContext = "" + if d.kubeContext != "" { + t.Errorf("Expected kubeContext to be empty, got '%s'", d.kubeContext) + } +} \ No newline at end of file diff --git a/cmd/release.go b/cmd/release.go index f0286e58..ae191332 100644 --- a/cmd/release.go +++ b/cmd/release.go @@ -17,6 +17,7 @@ type release struct { releases []string includeTests bool normalizeManifests bool + kubeContext string diff.Options } @@ -63,6 +64,7 @@ func releaseCmd() *cobra.Command { releaseCmd.Flags().BoolVar(&diff.detailedExitCode, "detailed-exitcode", false, "return a non-zero exit code when there are changes") releaseCmd.Flags().BoolVar(&diff.includeTests, "include-tests", false, "enable the diffing of the helm test hooks") releaseCmd.Flags().BoolVar(&diff.normalizeManifests, "normalize-manifests", false, "normalize manifests before running diff to exclude style differences from the output") + releaseCmd.Flags().StringVar(&diff.kubeContext, "kube-context", "", "name of the kubeconfig context to use") AddDiffOptions(releaseCmd.Flags(), &diff.Options) releaseCmd.SuggestionsMinimumDistance = 1 @@ -82,11 +84,11 @@ func (d *release) differentiateHelm3() error { namespace1 = strings.Split(release1, "/")[0] release1 = strings.Split(release1, "/")[1] } - releaseResponse1, err := getRelease(release1, namespace1) + releaseResponse1, err := getRelease(release1, namespace1, d.kubeContext) if err != nil { return err } - releaseChart1, err := getChart(release1, namespace1) + releaseChart1, err := getChart(release1, namespace1, d.kubeContext) if err != nil { return err } @@ -97,11 +99,11 @@ func (d *release) differentiateHelm3() error { namespace2 = strings.Split(release2, "/")[0] release2 = strings.Split(release2, "/")[1] } - releaseResponse2, err := getRelease(release2, namespace2) + releaseResponse2, err := getRelease(release2, namespace2, d.kubeContext) if err != nil { return err } - releaseChart2, err := getChart(release2, namespace2) + releaseChart2, err := getChart(release2, namespace2, d.kubeContext) if err != nil { return err } diff --git a/cmd/revision.go b/cmd/revision.go index 8599789b..a85c018b 100644 --- a/cmd/revision.go +++ b/cmd/revision.go @@ -18,6 +18,7 @@ type revision struct { revisions []string includeTests bool normalizeManifests bool + kubeContext string diff.Options } @@ -70,6 +71,7 @@ func revisionCmd() *cobra.Command { revisionCmd.Flags().BoolVar(&diff.detailedExitCode, "detailed-exitcode", false, "return a non-zero exit code when there are changes") revisionCmd.Flags().BoolVar(&diff.includeTests, "include-tests", false, "enable the diffing of the helm test hooks") revisionCmd.Flags().BoolVar(&diff.normalizeManifests, "normalize-manifests", false, "normalize manifests before running diff to exclude style differences from the output") + revisionCmd.Flags().StringVar(&diff.kubeContext, "kube-context", "", "name of the kubeconfig context to use") AddDiffOptions(revisionCmd.Flags(), &diff.Options) revisionCmd.SuggestionsMinimumDistance = 1 @@ -85,14 +87,14 @@ func (d *revision) differentiateHelm3() error { } switch len(d.revisions) { case 1: - releaseResponse, err := getRelease(d.release, namespace) + releaseResponse, err := getRelease(d.release, namespace, d.kubeContext) if err != nil { return err } revision, _ := strconv.Atoi(d.revisions[0]) - revisionResponse, err := getRevision(d.release, revision, namespace) + revisionResponse, err := getRevision(d.release, revision, namespace, d.kubeContext) if err != nil { return err } @@ -110,12 +112,12 @@ func (d *revision) differentiateHelm3() error { revision1, revision2 = revision2, revision1 } - revisionResponse1, err := getRevision(d.release, revision1, namespace) + revisionResponse1, err := getRevision(d.release, revision1, namespace, d.kubeContext) if err != nil { return err } - revisionResponse2, err := getRevision(d.release, revision2, namespace) + revisionResponse2, err := getRevision(d.release, revision2, namespace, d.kubeContext) if err != nil { return err } diff --git a/cmd/rollback.go b/cmd/rollback.go index 6f8d6d17..2484bbcb 100644 --- a/cmd/rollback.go +++ b/cmd/rollback.go @@ -18,6 +18,7 @@ type rollback struct { revisions []string includeTests bool normalizeManifests bool + kubeContext string diff.Options } @@ -60,6 +61,7 @@ func rollbackCmd() *cobra.Command { rollbackCmd.Flags().BoolVar(&diff.detailedExitCode, "detailed-exitcode", false, "return a non-zero exit code when there are changes") rollbackCmd.Flags().BoolVar(&diff.includeTests, "include-tests", false, "enable the diffing of the helm test hooks") rollbackCmd.Flags().BoolVar(&diff.normalizeManifests, "normalize-manifests", false, "normalize manifests before running diff to exclude style differences from the output") + rollbackCmd.Flags().StringVar(&diff.kubeContext, "kube-context", "", "name of the kubeconfig context to use") AddDiffOptions(rollbackCmd.Flags(), &diff.Options) rollbackCmd.SuggestionsMinimumDistance = 1 @@ -74,7 +76,7 @@ func (d *rollback) backcastHelm3() error { excludes = []string{} } // get manifest of the latest release - releaseResponse, err := getRelease(d.release, namespace) + releaseResponse, err := getRelease(d.release, namespace, d.kubeContext) if err != nil { return err @@ -82,7 +84,7 @@ func (d *rollback) backcastHelm3() error { // get manifest of the release to rollback revision, _ := strconv.Atoi(d.revisions[0]) - revisionResponse, err := getRevision(d.release, revision, namespace) + revisionResponse, err := getRevision(d.release, revision, namespace, d.kubeContext) if err != nil { return err } diff --git a/cmd/upgrade.go b/cmd/upgrade.go index 48945cf8..93354af1 100644 --- a/cmd/upgrade.go +++ b/cmd/upgrade.go @@ -41,6 +41,7 @@ type diffCmd struct { enableDNS bool SkipSchemaValidation bool namespace string // namespace to assume the release to be installed into. Defaults to the current kube config namespace. + kubeContext string // name of the kubeconfig context to use valueFiles valueFiles values []string stringValues []string @@ -211,7 +212,7 @@ func newChartCommand() *cobra.Command { var kubeconfig string f.StringVar(&kubeconfig, "kubeconfig", "", "This flag is ignored, to allow passing of this top level flag to helm") f.BoolVar(&diff.threeWayMerge, "three-way-merge", false, "use three-way-merge to compute patch and generate diff output") - // f.StringVar(&diff.kubeContext, "kube-context", "", "name of the kubeconfig context to use") + f.StringVar(&diff.kubeContext, "kube-context", "", "name of the kubeconfig context to use") f.StringVar(&diff.chartVersion, "version", "", "specify the exact chart version to use. If this is not specified, the latest version is used") f.StringVar(&diff.chartRepo, "repo", "", "specify the chart repository url to locate the requested chart") f.BoolVar(&diff.detailedExitCode, "detailed-exitcode", false, "return a non-zero exit code when there are changes") @@ -270,7 +271,7 @@ func (d *diffCmd) runHelm3() error { } if d.clusterAccessAllowed() { - releaseManifest, err = getRelease(d.release, d.namespace) + releaseManifest, err = getRelease(d.release, d.namespace, d.kubeContext) } var newInstall bool @@ -313,7 +314,7 @@ func (d *diffCmd) runHelm3() error { currentSpecs := make(map[string]*manifest.MappingResult) if !newInstall && d.clusterAccessAllowed() { if !d.noHooks && !d.threeWayMerge { - hooks, err := getHooks(d.release, d.namespace) + hooks, err := getHooks(d.release, d.namespace, d.kubeContext) if err != nil { return err } From d0b0f72c81957cd336a4642aa05e7a79b7eb0839 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 8 Sep 2025 01:28:30 +0000 Subject: [PATCH 3/3] Update README documentation for --kube-context flag Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com> --- README.md | 4 ++++ cmd/kube_context_test.go | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6c29ac35..78747be8 100644 --- a/README.md +++ b/README.md @@ -148,6 +148,9 @@ Usage: Examples: helm diff upgrade my-release stable/postgresql --values values.yaml + # Use a specific kubeconfig context + helm diff upgrade my-release stable/postgresql --kube-context production + # Set HELM_DIFF_IGNORE_UNKNOWN_FLAGS=true to ignore unknown flags # It's useful when you're using `helm-diff` in a `helm upgrade` wrapper. # See https://github.com/databus23/helm-diff/issues/278 for more information. @@ -191,6 +194,7 @@ 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 --no-hooks disable diffing of hooks diff --git a/cmd/kube_context_test.go b/cmd/kube_context_test.go index 67a6cd62..b165179a 100644 --- a/cmd/kube_context_test.go +++ b/cmd/kube_context_test.go @@ -83,4 +83,4 @@ func TestKubeContextInDiffCmd(t *testing.T) { if d.kubeContext != "" { t.Errorf("Expected kubeContext to be empty, got '%s'", d.kubeContext) } -} \ No newline at end of file +}