From 149a07bfa2a0684d8cfa273d7739be40dc8f0ce1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Feb 2026 06:34:20 +0000 Subject: [PATCH 1/5] Initial plan From e592f6ba0c7de58fd266cbc2df4eba5f21f7a074 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Feb 2026 06:41:10 +0000 Subject: [PATCH 2/5] Add ReuseValues flag to Helm upgrade to preserve existing values Co-authored-by: nicolejms <101607760+nicolejms@users.noreply.github.com> --- pkg/cli/helm/helmclient.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/cli/helm/helmclient.go b/pkg/cli/helm/helmclient.go index cd88e3b6ef..2fae70fc95 100644 --- a/pkg/cli/helm/helmclient.go +++ b/pkg/cli/helm/helmclient.go @@ -91,12 +91,14 @@ func (client *HelmClientImpl) RunHelmInstall(helmConf *helm.Configuration, helmC // RunHelmUpgrade upgrades an existing Helm release with a new chart version or configuration. // It recreates pods to ensure the new configuration is applied and optionally waits for the deployment to be ready. +// The upgrade reuses existing release values and merges them with any new values provided in helmChart.Values. func (client *HelmClientImpl) RunHelmUpgrade(helmConf *helm.Configuration, helmChart *chart.Chart, releaseName, namespace string, wait bool) (*release.Release, error) { upgradeClient := helm.NewUpgrade(helmConf) upgradeClient.Namespace = namespace upgradeClient.Wait = wait upgradeClient.Timeout = upgradeTimeout upgradeClient.Recreate = true + upgradeClient.ReuseValues = true return upgradeClient.Run(releaseName, helmChart, helmChart.Values) } From 5e33e8f920b32650e3db02698b1204aa167be28d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Feb 2026 06:42:21 +0000 Subject: [PATCH 3/5] Add test and documentation for value preservation in upgrades Co-authored-by: nicolejms <101607760+nicolejms@users.noreply.github.com> --- pkg/cli/cmd/upgrade/kubernetes/kubernetes.go | 17 +++++++++++- pkg/cli/helm/helmclient_test.go | 27 ++++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/pkg/cli/cmd/upgrade/kubernetes/kubernetes.go b/pkg/cli/cmd/upgrade/kubernetes/kubernetes.go index 3846b0f7df..6d70bb0472 100644 --- a/pkg/cli/cmd/upgrade/kubernetes/kubernetes.go +++ b/pkg/cli/cmd/upgrade/kubernetes/kubernetes.go @@ -41,6 +41,11 @@ func NewCommand(factory framework.Factory) (*cobra.Command, framework.Runner) { This command upgrades the Radius control plane in the cluster associated with the active workspace. To upgrade Radius in a different cluster, switch to the appropriate workspace first using 'rad workspace switch'. +The upgrade process preserves your existing Helm chart values (such as Azure Workload Identity settings, +database configuration, and custom container registries). The upgrade merges these existing values with +any new values specified via --set or --set-file flags, allowing you to override specific settings +without losing your configuration. + The upgrade process includes preflight checks to ensure the cluster is ready for upgrade. Preflight checks include: - Kubernetes connectivity and permissions @@ -52,6 +57,7 @@ Preflight checks include: Radius is installed in the 'radius-system' namespace. For more information visit https://docs.radapp.io/concepts/technical/architecture/. `, Example: `# Upgrade Radius in the cluster of the active workspace +# Existing values (e.g., from initial install) are automatically preserved rad upgrade kubernetes # Check which workspace is active @@ -61,9 +67,18 @@ rad workspace show rad workspace switch myworkspace rad upgrade kubernetes -# Upgrade Radius with custom configuration +# Upgrade Radius and override a specific value +# All other existing values from the previous installation are preserved rad upgrade kubernetes --set key=value +# Example: If you installed with Azure Workload Identity enabled: +# rad install kubernetes --set global.azureWorkloadIdentity.enabled=true +# Then upgrade without repeating the flag - the setting is preserved: +rad upgrade kubernetes + +# You can still override specific values during upgrade: +rad upgrade kubernetes --set global.imageTag=0.48 + # Upgrade Radius with a custom container registry # Images will be pulled as: myregistry.azurecr.io/controller, myregistry.azurecr.io/ucpd, etc. rad upgrade kubernetes --set global.imageRegistry=myregistry.azurecr.io diff --git a/pkg/cli/helm/helmclient_test.go b/pkg/cli/helm/helmclient_test.go index 2b01bdf175..ccc659b01e 100644 --- a/pkg/cli/helm/helmclient_test.go +++ b/pkg/cli/helm/helmclient_test.go @@ -105,3 +105,30 @@ func TestHelmClient_MockCompatibility(t *testing.T) { var rollbackFunc func(*helm.Configuration, string, int, bool) error = client.RunHelmRollback require.NotNil(t, rollbackFunc) } + +// TestHelmClient_UpgradeReusesValues verifies that RunHelmUpgrade sets ReuseValues flag. +// This test validates that the upgrade operation preserves existing release values +// by checking that the ReuseValues flag is properly set on the upgrade client. +// This addresses issue #11218 where upgrades were resetting values to chart defaults. +func TestHelmClient_UpgradeReusesValues(t *testing.T) { + // This test verifies the implementation detail that RunHelmUpgrade + // creates an upgrade client with ReuseValues=true. + // + // Note: This is a documentation test that validates the fix for issue #11218. + // The actual behavior is tested through integration tests in cluster_test.go, + // but this test documents the expected configuration of the upgrade client. + + client := &HelmClientImpl{} + + // Verify the method exists and has correct signature + require.NotNil(t, client.RunHelmUpgrade) + + // This test serves as documentation that RunHelmUpgrade should: + // 1. Set ReuseValues = true to preserve existing release values + // 2. Merge new values from helmChart.Values with existing values + // 3. Allow --set/--set-file CLI arguments to override specific values + // + // The behavior is validated by: + // - Integration tests in Test_Helm_UpgradeRadius + // - The Helm SDK's upgrade functionality with ReuseValues=true +} From b35f1473d5d58cdc028848013bf3e37b1aa8fc55 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Feb 2026 06:46:22 +0000 Subject: [PATCH 4/5] Address code review feedback with expanded test documentation Co-authored-by: nicolejms <101607760+nicolejms@users.noreply.github.com> --- pkg/cli/helm/helmclient_test.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pkg/cli/helm/helmclient_test.go b/pkg/cli/helm/helmclient_test.go index ccc659b01e..1deb30db95 100644 --- a/pkg/cli/helm/helmclient_test.go +++ b/pkg/cli/helm/helmclient_test.go @@ -117,6 +117,11 @@ func TestHelmClient_UpgradeReusesValues(t *testing.T) { // Note: This is a documentation test that validates the fix for issue #11218. // The actual behavior is tested through integration tests in cluster_test.go, // but this test documents the expected configuration of the upgrade client. + // + // We cannot easily test the ReuseValues field directly because: + // 1. It would require mocking Helm SDK internals (not recommended) + // 2. The field is set on the Helm action client, not exposed as a return value + // 3. Integration tests with real Helm operations verify the actual behavior client := &HelmClientImpl{} From 2f6fe5cacfa4076ea8051cb77917fb3e0595bdcc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Feb 2026 19:42:55 +0000 Subject: [PATCH 5/5] Add integration test to verify ReuseValues preserves existing Helm values Co-authored-by: nicolejms <101607760+nicolejms@users.noreply.github.com> --- pkg/cli/helm/helmclient_test.go | 147 +++++++++++++++++++++++++------- 1 file changed, 118 insertions(+), 29 deletions(-) diff --git a/pkg/cli/helm/helmclient_test.go b/pkg/cli/helm/helmclient_test.go index 1deb30db95..2effdc6b02 100644 --- a/pkg/cli/helm/helmclient_test.go +++ b/pkg/cli/helm/helmclient_test.go @@ -17,11 +17,19 @@ limitations under the License. package helm import ( + "io" "testing" "time" "github.com/stretchr/testify/require" helm "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v3/pkg/chart" + "helm.sh/helm/v3/pkg/chartutil" + kubefake "helm.sh/helm/v3/pkg/kube/fake" + "helm.sh/helm/v3/pkg/registry" + "helm.sh/helm/v3/pkg/release" + "helm.sh/helm/v3/pkg/storage" + "helm.sh/helm/v3/pkg/storage/driver" ) func TestHelmClientImpl_RunHelmHistory(t *testing.T) { @@ -106,34 +114,115 @@ func TestHelmClient_MockCompatibility(t *testing.T) { require.NotNil(t, rollbackFunc) } -// TestHelmClient_UpgradeReusesValues verifies that RunHelmUpgrade sets ReuseValues flag. -// This test validates that the upgrade operation preserves existing release values -// by checking that the ReuseValues flag is properly set on the upgrade client. -// This addresses issue #11218 where upgrades were resetting values to chart defaults. +// TestHelmClient_UpgradeReusesValues verifies that RunHelmUpgrade preserves existing values +// by using ReuseValues=true. This test validates the fix for issue #11218 where upgrades +// were resetting values to chart defaults. func TestHelmClient_UpgradeReusesValues(t *testing.T) { - // This test verifies the implementation detail that RunHelmUpgrade - // creates an upgrade client with ReuseValues=true. - // - // Note: This is a documentation test that validates the fix for issue #11218. - // The actual behavior is tested through integration tests in cluster_test.go, - // but this test documents the expected configuration of the upgrade client. - // - // We cannot easily test the ReuseValues field directly because: - // 1. It would require mocking Helm SDK internals (not recommended) - // 2. The field is set on the Helm action client, not exposed as a return value - // 3. Integration tests with real Helm operations verify the actual behavior - - client := &HelmClientImpl{} - - // Verify the method exists and has correct signature - require.NotNil(t, client.RunHelmUpgrade) - - // This test serves as documentation that RunHelmUpgrade should: - // 1. Set ReuseValues = true to preserve existing release values - // 2. Merge new values from helmChart.Values with existing values - // 3. Allow --set/--set-file CLI arguments to override specific values - // - // The behavior is validated by: - // - Integration tests in Test_Helm_UpgradeRadius - // - The Helm SDK's upgrade functionality with ReuseValues=true + t.Run("upgrade preserves existing values and merges new values", func(t *testing.T) { + // This is an integration test that validates the actual behavior of RunHelmUpgrade + // with ReuseValues=true by simulating an upgrade with Helm's in-memory storage. + + // Test data representing values from previous install/upgrade + existingValues := map[string]interface{}{ + "global": map[string]interface{}{ + "azureWorkloadIdentity": map[string]interface{}{ + "enabled": true, + }, + "imageRegistry": "myregistry.azurecr.io", + }, + "database": map[string]interface{}{ + "enabled": true, + }, + } + + // New values being applied in this upgrade + newValues := map[string]interface{}{ + "global": map[string]interface{}{ + "imageTag": "0.48.0", + }, + } + + // Expected merged result: existing values preserved + new values applied + expectedValues := map[string]interface{}{ + "global": map[string]interface{}{ + "azureWorkloadIdentity": map[string]interface{}{ + "enabled": true, + }, + "imageRegistry": "myregistry.azurecr.io", + "imageTag": "0.48.0", + }, + "database": map[string]interface{}{ + "enabled": true, + }, + } + + // Create an in-memory Helm configuration for testing (similar to Helm's own tests) + registryClient, err := registry.NewClient() + require.NoError(t, err, "Failed to create registry client") + + cfg := &helm.Configuration{ + Releases: storage.Init(driver.NewMemory()), + KubeClient: &kubefake.PrintingKubeClient{Out: io.Discard}, + Capabilities: chartutil.DefaultCapabilities, + RegistryClient: registryClient, + Log: func(format string, v ...interface{}) {}, + } + + // Create and store an initial release with existing values + initialRelease := &release.Release{ + Name: "test-release", + Namespace: "test-namespace", + Version: 1, + Config: existingValues, + Chart: &chart.Chart{ + Metadata: &chart.Metadata{ + Name: "test-chart", + Version: "1.0.0", + }, + }, + Info: &release.Info{ + Status: release.StatusDeployed, + }, + } + err = cfg.Releases.Create(initialRelease) + require.NoError(t, err, "Failed to create initial release") + + // Create a chart with new values + upgradeChart := &chart.Chart{ + Metadata: &chart.Metadata{ + Name: "test-chart", + Version: "1.1.0", + }, + Values: newValues, + } + + // Execute the upgrade using our RunHelmUpgrade implementation + client := &HelmClientImpl{} + upgradedRelease, err := client.RunHelmUpgrade(cfg, upgradeChart, "test-release", "test-namespace", false) + require.NoError(t, err, "RunHelmUpgrade should succeed") + require.NotNil(t, upgradedRelease, "Upgraded release should not be nil") + + // Verify the release was upgraded + require.Equal(t, 2, upgradedRelease.Version, "Release version should be incremented") + require.Equal(t, release.StatusDeployed, upgradedRelease.Info.Status, "Release should be deployed") + + // Verify that existing values were preserved and new values were merged + require.Equal(t, expectedValues, upgradedRelease.Config, + "Upgraded release should preserve existing values and merge new values") + + // Specifically verify key values that should be preserved (issue #11218) + globalMap, ok := upgradedRelease.Config["global"].(map[string]interface{}) + require.True(t, ok, "global key should exist and be a map") + + azureWIMap, ok := globalMap["azureWorkloadIdentity"].(map[string]interface{}) + require.True(t, ok, "global.azureWorkloadIdentity should exist") + require.Equal(t, true, azureWIMap["enabled"], "azureWorkloadIdentity.enabled should be preserved") + + require.Equal(t, "myregistry.azurecr.io", globalMap["imageRegistry"], "imageRegistry should be preserved") + require.Equal(t, "0.48.0", globalMap["imageTag"], "imageTag should be set from new values") + + dbMap, ok := upgradedRelease.Config["database"].(map[string]interface{}) + require.True(t, ok, "database key should exist and be a map") + require.Equal(t, true, dbMap["enabled"], "database.enabled should be preserved") + }) }