@@ -26,6 +26,7 @@ import (
2626 "github.com/skyhook-io/radar/pkg/helmhistory"
2727
2828 "helm.sh/helm/v3/pkg/action"
29+ "helm.sh/helm/v3/pkg/chart"
2930 "helm.sh/helm/v3/pkg/chart/loader"
3031 "helm.sh/helm/v3/pkg/cli"
3132 "helm.sh/helm/v3/pkg/registry"
@@ -2315,6 +2316,32 @@ func (c *Client) UpgradeAsUser(namespace, name, targetVersion, repositoryName st
23152316 return c .upgradeWith (actionConfig , name , targetVersion , repositoryName , noop )
23162317}
23172318
2319+ // UpgradeWithValuesProgress upgrades a release to a target version applying the
2320+ // supplied user values (WYSIWYG), reporting progress via a channel.
2321+ func (c * Client ) UpgradeWithValuesProgress (namespace , name , targetVersion , repositoryName string , newValues map [string ]any , progressCh chan <- InstallProgress ) error {
2322+ sendProgress := progressSender (progressCh )
2323+ sendProgress ("preparing" , fmt .Sprintf ("Getting current release %s..." , name ), "" )
2324+
2325+ actionConfig , err := c .getActionConfig (namespace )
2326+ if err != nil {
2327+ return err
2328+ }
2329+ return c .upgradeWithValues (actionConfig , name , targetVersion , repositoryName , newValues , sendProgress )
2330+ }
2331+
2332+ // UpgradeWithValuesProgressAsUser upgrades a release to a target version applying
2333+ // the supplied user values with K8s impersonation and progress reporting.
2334+ func (c * Client ) UpgradeWithValuesProgressAsUser (namespace , name , targetVersion , repositoryName string , newValues map [string ]any , username string , groups []string , progressCh chan <- InstallProgress ) error {
2335+ sendProgress := progressSender (progressCh )
2336+ sendProgress ("preparing" , fmt .Sprintf ("Getting current release %s..." , name ), "" )
2337+
2338+ actionConfig , err := c .getActionConfigForUser (namespace , username , groups )
2339+ if err != nil {
2340+ return err
2341+ }
2342+ return c .upgradeWithValues (actionConfig , name , targetVersion , repositoryName , newValues , sendProgress )
2343+ }
2344+
23182345func progressSender (progressCh chan <- InstallProgress ) func (phase , message , detail string ) {
23192346 return func (phase , message , detail string ) {
23202347 if progressCh == nil {
@@ -2335,16 +2362,11 @@ func (c *Client) upgradeWith(actionConfig *action.Configuration, name, targetVer
23352362 return fmt .Errorf ("failed to get current release: %w" , err )
23362363 }
23372364
2338- chartName := rel .Chart .Metadata .Name
2339- sendProgress ("resolving" , fmt .Sprintf ("Finding %s version %s in repositories..." , chartName , targetVersion ), "" )
2340-
2341- chartPath , resolvedRepo , err := c .resolveUpgradeChartPath (chartName , targetVersion , repositoryName , chartSourceHosts (rel .Chart .Metadata .Home , rel .Chart .Metadata .Sources ))
2365+ targetChart , err := c .chartForUpgradeTarget (actionConfig , rel , targetVersion , repositoryName , sendProgress )
23422366 if err != nil {
23432367 return err
23442368 }
23452369
2346- sendProgress ("downloading" , fmt .Sprintf ("Downloading %s-%s from %s..." , chartName , targetVersion , resolvedRepo ), chartPath )
2347-
23482370 // Create upgrade action — don't use Wait=true because Radar already
23492371 // shows real-time resource status via SSE. Waiting blocks the dialog
23502372 // for minutes with zero feedback; users can monitor the rollout in the UI.
@@ -2357,49 +2379,105 @@ func (c *Client) upgradeWith(actionConfig *action.Configuration, name, targetVer
23572379 // for keys a newer chart added (a cross-version upgrade footgun).
23582380 upgradeAction .ResetThenReuseValues = true
23592381
2382+ sendProgress ("upgrading" , fmt .Sprintf ("Applying %s %s..." , rel .Chart .Metadata .Name , targetVersion ), "" )
2383+
2384+ // Run the upgrade
2385+ _ , err = upgradeAction .Run (name , targetChart , rel .Config )
2386+ if err != nil {
2387+ return fmt .Errorf ("upgrade failed: %w" , err )
2388+ }
2389+
2390+ sendProgress ("complete" , fmt .Sprintf ("Successfully upgraded %s to %s" , name , targetVersion ), "" )
2391+ return nil
2392+ }
2393+
2394+ // upgradeWithValues upgrades a release to a target chart version applying exactly
2395+ // the supplied user values (WYSIWYG — ResetValues, no merge with prior overrides).
2396+ // The plain upgradeWith path keeps ResetThenReuseValues for the blind carry-over case.
2397+ func (c * Client ) upgradeWithValues (actionConfig * action.Configuration , name , targetVersion , repositoryName string , newValues map [string ]any , sendProgress func (phase , message , detail string )) error {
2398+ getAction := action .NewGet (actionConfig )
2399+ rel , err := getAction .Run (name )
2400+ if err != nil {
2401+ return fmt .Errorf ("failed to get current release: %w" , err )
2402+ }
2403+
2404+ targetChart , err := c .chartForUpgradeTarget (actionConfig , rel , targetVersion , repositoryName , sendProgress )
2405+ if err != nil {
2406+ return err
2407+ }
2408+
2409+ upgradeAction := action .NewUpgrade (actionConfig )
2410+ upgradeAction .Namespace = rel .Namespace
2411+ upgradeAction .Timeout = 120 * time .Second
2412+ upgradeAction .ResetValues = true // WYSIWYG: apply only the edited values
2413+
2414+ sendProgress ("upgrading" , fmt .Sprintf ("Applying %s %s..." , rel .Chart .Metadata .Name , targetVersion ), "" )
2415+
2416+ _ , err = upgradeAction .Run (name , targetChart , newValues )
2417+ if err != nil {
2418+ return fmt .Errorf ("upgrade failed: %w" , err )
2419+ }
2420+
2421+ sendProgress ("complete" , fmt .Sprintf ("Successfully upgraded %s to %s" , name , targetVersion ), "" )
2422+ return nil
2423+ }
2424+
2425+ func (c * Client ) chartForUpgradeTarget (actionConfig * action.Configuration , rel * release.Release , targetVersion , repositoryName string , sendProgress func (phase , message , detail string )) (* chart.Chart , error ) {
2426+ if targetVersion == "" || targetVersion == rel .Chart .Metadata .Version {
2427+ return rel .Chart , nil
2428+ }
2429+ return c .loadTargetChart (actionConfig , rel , targetVersion , repositoryName , sendProgress )
2430+ }
2431+
2432+ // loadTargetChart resolves, downloads and loads the chart for a target upgrade
2433+ // version, refusing a silent chart-swap (the resolved source must publish the
2434+ // same chart the release runs). Shared by the upgrade and preview paths so they
2435+ // resolve the target version identically.
2436+ func (c * Client ) loadTargetChart (actionConfig * action.Configuration , rel * release.Release , targetVersion , repositoryName string , sendProgress func (phase , message , detail string )) (* chart.Chart , error ) {
2437+ chartName := rel .Chart .Metadata .Name
2438+ sendProgress ("resolving" , fmt .Sprintf ("Finding %s version %s in repositories..." , chartName , targetVersion ), "" )
2439+
2440+ chartPath , resolvedRepo , err := c .resolveUpgradeChartPath (chartName , targetVersion , repositoryName , chartSourceHosts (rel .Chart .Metadata .Home , rel .Chart .Metadata .Sources ))
2441+ if err != nil {
2442+ return nil , err
2443+ }
2444+
2445+ sendProgress ("downloading" , fmt .Sprintf ("Downloading %s-%s from %s..." , chartName , targetVersion , resolvedRepo ), chartPath )
2446+
23602447 // Use ChartPathOptions to locate/download the chart
2361- client := action .NewInstall (actionConfig )
2362- client .Version = targetVersion
2448+ locate := action .NewInstall (actionConfig )
2449+ locate .Version = targetVersion
23632450
23642451 // OCI pulls need a registry client on the action; Radar's action config
23652452 // doesn't carry one by default. Wire it from the user's helm registry login.
23662453 if registry .IsOCI (chartPath ) {
23672454 rc , err := c .newRegistryClientConcrete ()
23682455 if err != nil {
2369- return fmt .Errorf ("failed to build OCI registry client: %w" , err )
2456+ return nil , fmt .Errorf ("failed to build OCI registry client: %w" , err )
23702457 }
2371- client .SetRegistryClient (rc )
2458+ locate .SetRegistryClient (rc )
23722459 }
23732460
2374- cp , err := client .ChartPathOptions .LocateChart (chartPath , c .settings )
2461+ cp , err := locate .ChartPathOptions .LocateChart (chartPath , c .settings )
23752462 if err != nil {
2376- return fmt .Errorf ("failed to locate chart: %w" , err )
2463+ return nil , fmt .Errorf ("failed to locate chart: %w" , err )
23772464 }
23782465
23792466 sendProgress ("loading" , "Loading chart..." , cp )
23802467
2381- chart , err := loader .Load (cp )
2468+ targetChart , err := loader .Load (cp )
23822469 if err != nil {
2383- return fmt .Errorf ("failed to load chart: %w" , err )
2470+ return nil , fmt .Errorf ("failed to load chart: %w" , err )
23842471 }
23852472
23862473 // Refuse a silent chart-swap: the resolved source must publish the SAME chart
23872474 // the release runs, not merely a chart at the same version. Matters most for
23882475 // OCI prefix probing, where "<prefix>/<chartName>" is derived, not asserted.
2389- if chart .Metadata != nil && chart .Metadata .Name != chartName {
2390- return fmt .Errorf ("resolved chart is %q but release %q runs chart %q — refusing to swap charts" , chart .Metadata .Name , name , chartName )
2391- }
2392-
2393- sendProgress ("upgrading" , fmt .Sprintf ("Applying %s %s..." , chartName , targetVersion ), "" )
2394-
2395- // Run the upgrade
2396- _ , err = upgradeAction .Run (name , chart , rel .Config )
2397- if err != nil {
2398- return fmt .Errorf ("upgrade failed: %w" , err )
2476+ if targetChart .Metadata != nil && targetChart .Metadata .Name != chartName {
2477+ return nil , fmt .Errorf ("resolved chart is %q but release %q runs chart %q — refusing to swap charts" , targetChart .Metadata .Name , rel .Name , chartName )
23992478 }
24002479
2401- sendProgress ("complete" , fmt .Sprintf ("Successfully upgraded %s to %s" , name , targetVersion ), "" )
2402- return nil
2480+ return targetChart , nil
24032481}
24042482
24052483type chartPathCandidate struct {
@@ -2689,13 +2767,26 @@ func (c *Client) batchCheckUpgrades(namespace, username string, groups []string)
26892767 return result , nil
26902768}
26912769
2692- // PreviewValuesChange previews the effect of new values on a release via dry-run
2693- func (c * Client ) PreviewValuesChange (namespace , name string , newValues map [string ]any ) (* ValuesPreviewResponse , error ) {
2770+ // PreviewValuesChange previews the effect of new values on a release via dry-run.
2771+ // When targetVersion is non-empty and differs from the running chart version, the
2772+ // dry-run renders against that target chart instead of the current chart.
2773+ func (c * Client ) PreviewValuesChange (namespace , name string , newValues map [string ]any , targetVersion , repositoryName string ) (* ValuesPreviewResponse , error ) {
26942774 actionConfig , err := c .getActionConfig (namespace )
26952775 if err != nil {
26962776 return nil , err
26972777 }
2778+ return c .previewValuesChangeWith (actionConfig , name , newValues , targetVersion , repositoryName )
2779+ }
26982780
2781+ func (c * Client ) PreviewValuesChangeAsUser (namespace , name string , newValues map [string ]any , targetVersion , repositoryName string , username string , groups []string ) (* ValuesPreviewResponse , error ) {
2782+ actionConfig , err := c .getActionConfigForUser (namespace , username , groups )
2783+ if err != nil {
2784+ return nil , err
2785+ }
2786+ return c .previewValuesChangeWith (actionConfig , name , newValues , targetVersion , repositoryName )
2787+ }
2788+
2789+ func (c * Client ) previewValuesChangeWith (actionConfig * action.Configuration , name string , newValues map [string ]any , targetVersion , repositoryName string ) (* ValuesPreviewResponse , error ) {
26992790 // Get the current release
27002791 getAction := action .NewGet (actionConfig )
27012792 rel , err := getAction .Run (name )
@@ -2713,6 +2804,12 @@ func (c *Client) PreviewValuesChange(namespace, name string, newValues map[strin
27132804 // Get current manifest
27142805 currentManifest := rel .Manifest
27152806
2807+ noop := func (phase , message , detail string ) {}
2808+ previewChart , err := c .chartForUpgradeTarget (actionConfig , rel , targetVersion , repositoryName , noop )
2809+ if err != nil {
2810+ return nil , err
2811+ }
2812+
27162813 // Perform a dry-run upgrade with the new values
27172814 upgradeAction := action .NewUpgrade (actionConfig )
27182815 upgradeAction .Namespace = rel .Namespace
@@ -2721,7 +2818,7 @@ func (c *Client) PreviewValuesChange(namespace, name string, newValues map[strin
27212818 upgradeAction .ResetValues = true // Use only the provided values, don't merge
27222819
27232820 // Run the dry-run upgrade
2724- newRel , err := upgradeAction .Run (name , rel . Chart , newValues )
2821+ newRel , err := upgradeAction .Run (name , previewChart , newValues )
27252822 if err != nil {
27262823 return nil , fmt .Errorf ("failed to preview values change: %w" , err )
27272824 }
0 commit comments