From 6ce3a9e502e8f315715a8ad3e298467f3e71abf3 Mon Sep 17 00:00:00 2001 From: MrJack <36191829+biagiopietro@users.noreply.github.com> Date: Sun, 7 Dec 2025 18:04:05 +0100 Subject: [PATCH 1/8] fix(action): enable server-side validation for dry-run=server When using --dry-run=server with --server-side=true, Helm now properly validates manifests against the Kubernetes API server. Previously, the dry-run would return early without calling the API, missing validation errors like unknown fields in the spec. This fix ensures that DryRunServer mode calls KubeClient.Create/Update with the dry-run option, matching the behavior of kubectl apply --dry-run=server. Fixes: helm/helm#31505 Signed-off-by: MrJack <36191829+biagiopietro@users.noreply.github.com> --- pkg/action/install.go | 20 +++++++++++++ pkg/action/install_test.go | 40 ++++++++++++++++++++++++++ pkg/action/upgrade.go | 13 +++++++++ pkg/action/upgrade_test.go | 57 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 130 insertions(+) diff --git a/pkg/action/install.go b/pkg/action/install.go index fcfae7214..d53baa3c4 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -421,6 +421,26 @@ func (i *Install) RunWithContext(ctx context.Context, ch ci.Charter, vals map[st // Bail out here if it is a dry run if isDryRun(i.DryRunStrategy) { + // For server-side dry-run, validate resources against the API server + if i.DryRunStrategy == DryRunServer && len(resources) > 0 { + if len(toBeAdopted) == 0 { + _, err = i.cfg.KubeClient.Create( + resources, + kube.ClientCreateOptionServerSideApply(i.ServerSideApply, false), + kube.ClientCreateOptionDryRun(true), + ) + } else { + _, err = i.cfg.KubeClient.Update( + toBeAdopted, + resources, + kube.ClientUpdateOptionServerSideApply(i.ServerSideApply, i.ForceConflicts), + kube.ClientUpdateOptionDryRun(true), + ) + } + if err != nil { + return rel, err + } + } rel.Info.Description = "Dry run complete" return rel, nil } diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go index 705a8e11d..4ad3f080f 100644 --- a/pkg/action/install_test.go +++ b/pkg/action/install_test.go @@ -465,6 +465,46 @@ func TestInstallRelease_DryRunClient(t *testing.T) { } } +func TestInstallRelease_DryRunServerValidation(t *testing.T) { + // Test that server-side dry-run actually calls the Kubernetes API for validation + is := assert.New(t) + + // Use a fixture that returns dummy resources so our code path is exercised + config := actionConfigFixtureWithDummyResources(t, createDummyResourceList(false)) + + instAction := NewInstall(config) + instAction.Namespace = "spaced" + instAction.ReleaseName = "test-server-dry-run" + + // Set up the fake client to return an error on Create + expectedErr := errors.New("validation error: unknown field in spec") + config.KubeClient.(*kubefake.FailingKubeClient).CreateError = expectedErr + instAction.DryRunStrategy = DryRunServer + + vals := map[string]interface{}{} + _, err := instAction.Run(buildChart(withSampleTemplates()), vals) + + // The error from the API should be returned + is.Error(err) + is.Contains(err.Error(), "validation error") + + // Reset and test that client-side dry-run does NOT call the API + config2 := actionConfigFixtureWithDummyResources(t, createDummyResourceList(false)) + config2.KubeClient.(*kubefake.FailingKubeClient).CreateError = expectedErr + + instAction2 := NewInstall(config2) + instAction2.Namespace = "spaced" + instAction2.ReleaseName = "test-client-dry-run" + instAction2.DryRunStrategy = DryRunClient + + resi, err := instAction2.Run(buildChart(withSampleTemplates()), vals) + // Client-side dry-run should succeed since it doesn't call the API + is.NoError(err) + res, err := releaserToV1Release(resi) + is.NoError(err) + is.Equal(res.Info.Description, "Dry run complete") +} + func TestInstallRelease_DryRunHiddenSecret(t *testing.T) { is := assert.New(t) instAction := installAction(t) diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go index 81d51164f..ff8ff582a 100644 --- a/pkg/action/upgrade.go +++ b/pkg/action/upgrade.go @@ -398,6 +398,19 @@ func (u *Upgrade) performUpgrade(ctx context.Context, originalRelease, upgradedR if isDryRun(u.DryRunStrategy) { u.cfg.Logger().Debug("dry run for release", "name", upgradedRelease.Name) + // For server-side dry-run, validate resources against the API server + if u.DryRunStrategy == DryRunServer { + _, err := u.cfg.KubeClient.Update( + current, + target, + kube.ClientUpdateOptionForceReplace(u.ForceReplace), + kube.ClientUpdateOptionServerSideApply(serverSideApply, u.ForceConflicts), + kube.ClientUpdateOptionDryRun(true), + ) + if err != nil { + return upgradedRelease, err + } + } if len(u.Description) > 0 { upgradedRelease.Info.Description = u.Description } else { diff --git a/pkg/action/upgrade_test.go b/pkg/action/upgrade_test.go index bd020cbdb..501013ddb 100644 --- a/pkg/action/upgrade_test.go +++ b/pkg/action/upgrade_test.go @@ -637,6 +637,63 @@ func TestUpgradeRelease_DryRun(t *testing.T) { req.Error(err) } +func TestUpgradeRelease_DryRunServerValidation(t *testing.T) { + // Test that server-side dry-run actually calls the Kubernetes API for validation + is := assert.New(t) + req := require.New(t) + + // Use a fixture that returns dummy resources so our code path is exercised + config := actionConfigFixtureWithDummyResources(t, createDummyResourceList(true)) + + upAction := NewUpgrade(config) + upAction.Namespace = "spaced" + + // Create a previous release + rel := releaseStub() + rel.Name = "test-server-dry-run" + rel.Info.Status = common.StatusDeployed + req.NoError(upAction.cfg.Releases.Create(rel)) + + // Set up the fake client to return an error on Update + expectedErr := errors.New("validation error: unknown field in spec") + config.KubeClient.(*kubefake.FailingKubeClient).UpdateError = expectedErr + upAction.DryRunStrategy = DryRunServer + + vals := map[string]interface{}{} + ctx, done := context.WithCancel(t.Context()) + _, err := upAction.RunWithContext(ctx, rel.Name, buildChart(), vals) + done() + + // The error from the API should be returned + is.Error(err) + is.Contains(err.Error(), "validation error") + + // Reset and test that client-side dry-run does NOT call the API + config2 := actionConfigFixtureWithDummyResources(t, createDummyResourceList(true)) + config2.KubeClient.(*kubefake.FailingKubeClient).UpdateError = expectedErr + + upAction2 := NewUpgrade(config2) + upAction2.Namespace = "spaced" + + // Create a previous release + rel2 := releaseStub() + rel2.Name = "test-client-dry-run" + rel2.Info.Status = common.StatusDeployed + req.NoError(upAction2.cfg.Releases.Create(rel2)) + + upAction2.DryRunStrategy = DryRunClient + + ctx, done = context.WithCancel(t.Context()) + resi, err := upAction2.RunWithContext(ctx, rel2.Name, buildChart(), vals) + done() + + // Client-side dry-run should succeed since it doesn't call the API + is.NoError(err) + res, err := releaserToV1Release(resi) + is.NoError(err) + is.Equal(res.Info.Description, "Dry run complete") +} + func TestGetUpgradeServerSideValue(t *testing.T) { tests := []struct { name string From 42077b12a3a8691524356c52c23f7406996647c5 Mon Sep 17 00:00:00 2001 From: MrJack <36191829+biagiopietro@users.noreply.github.com> Date: Sun, 7 Dec 2025 19:14:14 +0100 Subject: [PATCH 2/8] Remove unneeded comments Signed-off-by: MrJack <36191829+biagiopietro@users.noreply.github.com> --- pkg/action/install_test.go | 6 ------ pkg/action/upgrade_test.go | 8 -------- 2 files changed, 14 deletions(-) diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go index 4ad3f080f..792b6cd2a 100644 --- a/pkg/action/install_test.go +++ b/pkg/action/install_test.go @@ -466,17 +466,14 @@ func TestInstallRelease_DryRunClient(t *testing.T) { } func TestInstallRelease_DryRunServerValidation(t *testing.T) { - // Test that server-side dry-run actually calls the Kubernetes API for validation is := assert.New(t) - // Use a fixture that returns dummy resources so our code path is exercised config := actionConfigFixtureWithDummyResources(t, createDummyResourceList(false)) instAction := NewInstall(config) instAction.Namespace = "spaced" instAction.ReleaseName = "test-server-dry-run" - // Set up the fake client to return an error on Create expectedErr := errors.New("validation error: unknown field in spec") config.KubeClient.(*kubefake.FailingKubeClient).CreateError = expectedErr instAction.DryRunStrategy = DryRunServer @@ -484,11 +481,9 @@ func TestInstallRelease_DryRunServerValidation(t *testing.T) { vals := map[string]interface{}{} _, err := instAction.Run(buildChart(withSampleTemplates()), vals) - // The error from the API should be returned is.Error(err) is.Contains(err.Error(), "validation error") - // Reset and test that client-side dry-run does NOT call the API config2 := actionConfigFixtureWithDummyResources(t, createDummyResourceList(false)) config2.KubeClient.(*kubefake.FailingKubeClient).CreateError = expectedErr @@ -498,7 +493,6 @@ func TestInstallRelease_DryRunServerValidation(t *testing.T) { instAction2.DryRunStrategy = DryRunClient resi, err := instAction2.Run(buildChart(withSampleTemplates()), vals) - // Client-side dry-run should succeed since it doesn't call the API is.NoError(err) res, err := releaserToV1Release(resi) is.NoError(err) diff --git a/pkg/action/upgrade_test.go b/pkg/action/upgrade_test.go index 501013ddb..051d92ebc 100644 --- a/pkg/action/upgrade_test.go +++ b/pkg/action/upgrade_test.go @@ -638,23 +638,19 @@ func TestUpgradeRelease_DryRun(t *testing.T) { } func TestUpgradeRelease_DryRunServerValidation(t *testing.T) { - // Test that server-side dry-run actually calls the Kubernetes API for validation is := assert.New(t) req := require.New(t) - // Use a fixture that returns dummy resources so our code path is exercised config := actionConfigFixtureWithDummyResources(t, createDummyResourceList(true)) upAction := NewUpgrade(config) upAction.Namespace = "spaced" - // Create a previous release rel := releaseStub() rel.Name = "test-server-dry-run" rel.Info.Status = common.StatusDeployed req.NoError(upAction.cfg.Releases.Create(rel)) - // Set up the fake client to return an error on Update expectedErr := errors.New("validation error: unknown field in spec") config.KubeClient.(*kubefake.FailingKubeClient).UpdateError = expectedErr upAction.DryRunStrategy = DryRunServer @@ -664,18 +660,15 @@ func TestUpgradeRelease_DryRunServerValidation(t *testing.T) { _, err := upAction.RunWithContext(ctx, rel.Name, buildChart(), vals) done() - // The error from the API should be returned is.Error(err) is.Contains(err.Error(), "validation error") - // Reset and test that client-side dry-run does NOT call the API config2 := actionConfigFixtureWithDummyResources(t, createDummyResourceList(true)) config2.KubeClient.(*kubefake.FailingKubeClient).UpdateError = expectedErr upAction2 := NewUpgrade(config2) upAction2.Namespace = "spaced" - // Create a previous release rel2 := releaseStub() rel2.Name = "test-client-dry-run" rel2.Info.Status = common.StatusDeployed @@ -687,7 +680,6 @@ func TestUpgradeRelease_DryRunServerValidation(t *testing.T) { resi, err := upAction2.RunWithContext(ctx, rel2.Name, buildChart(), vals) done() - // Client-side dry-run should succeed since it doesn't call the API is.NoError(err) res, err := releaserToV1Release(resi) is.NoError(err) From 9beac4fb83d98f29fea52806f6237abeeae482e8 Mon Sep 17 00:00:00 2001 From: MrJack <36191829+biagiopietro@users.noreply.github.com> Date: Thu, 25 Dec 2025 18:49:12 +0100 Subject: [PATCH 3/8] Applied feedback pt.1 Signed-off-by: MrJack <36191829+biagiopietro@users.noreply.github.com> --- pkg/action/install.go | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/pkg/action/install.go b/pkg/action/install.go index d53baa3c4..c671d0ffb 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -422,22 +422,30 @@ func (i *Install) RunWithContext(ctx context.Context, ch ci.Charter, vals map[st // Bail out here if it is a dry run if isDryRun(i.DryRunStrategy) { // For server-side dry-run, validate resources against the API server - if i.DryRunStrategy == DryRunServer && len(resources) > 0 { - if len(toBeAdopted) == 0 { - _, err = i.cfg.KubeClient.Create( - resources, - kube.ClientCreateOptionServerSideApply(i.ServerSideApply, false), - kube.ClientCreateOptionDryRun(true), - ) - } else { - _, err = i.cfg.KubeClient.Update( + if i.DryRunStrategy == DryRunServer { + var errs []error + if len(toBeAdopted) > 0 { + _, err := i.cfg.KubeClient.Update( toBeAdopted, resources, kube.ClientUpdateOptionServerSideApply(i.ServerSideApply, i.ForceConflicts), kube.ClientUpdateOptionDryRun(true), ) + if err != nil { + errs = append(errs, err) + } } - if err != nil { + if len(resources) > 0 { + _, err := i.cfg.KubeClient.Create( + resources, + kube.ClientCreateOptionServerSideApply(i.ServerSideApply, false), + kube.ClientCreateOptionDryRun(true), + ) + if err != nil { + errs = append(errs, err) + } + } + if err := errors.Join(errs...); err != nil { return rel, err } } From 9b8ff6d99db04fc85c930bfc48a3065a33067f61 Mon Sep 17 00:00:00 2001 From: MrJack <36191829+biagiopietro@users.noreply.github.com> Date: Fri, 23 Jan 2026 10:51:05 +0100 Subject: [PATCH 4/8] Applied feedback from review pt. 3 Signed-off-by: MrJack <36191829+biagiopietro@users.noreply.github.com> --- pkg/action/install.go | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/pkg/action/install.go b/pkg/action/install.go index c671d0ffb..8dc6d84a2 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -423,29 +423,22 @@ func (i *Install) RunWithContext(ctx context.Context, ch ci.Charter, vals map[st if isDryRun(i.DryRunStrategy) { // For server-side dry-run, validate resources against the API server if i.DryRunStrategy == DryRunServer { - var errs []error - if len(toBeAdopted) > 0 { - _, err := i.cfg.KubeClient.Update( + var err error + if len(toBeAdopted) == 0 && len(resources) > 0 { + _, err = i.cfg.KubeClient.Create( + resources, + kube.ClientCreateOptionServerSideApply(i.ServerSideApply, false), + kube.ClientCreateOptionDryRun(true), + ) + } else if len(resources) > 0 { + _, err = i.cfg.KubeClient.Update( toBeAdopted, resources, kube.ClientUpdateOptionServerSideApply(i.ServerSideApply, i.ForceConflicts), kube.ClientUpdateOptionDryRun(true), ) - if err != nil { - errs = append(errs, err) - } - } - if len(resources) > 0 { - _, err := i.cfg.KubeClient.Create( - resources, - kube.ClientCreateOptionServerSideApply(i.ServerSideApply, false), - kube.ClientCreateOptionDryRun(true), - ) - if err != nil { - errs = append(errs, err) - } } - if err := errors.Join(errs...); err != nil { + if err != nil { return rel, err } } From 4d464889ff0584774dd2ef0954d8fb5b74430ca3 Mon Sep 17 00:00:00 2001 From: MrJack <36191829+biagiopietro@users.noreply.github.com> Date: Mon, 23 Feb 2026 09:02:57 +0100 Subject: [PATCH 5/8] Make --dry-run=server validation to not differ from what a real install/upgrade would send to the API server Signed-off-by: MrJack <36191829+biagiopietro@users.noreply.github.com> --- pkg/action/install.go | 4 ++++ pkg/action/upgrade.go | 2 ++ 2 files changed, 6 insertions(+) diff --git a/pkg/action/install.go b/pkg/action/install.go index 8dc6d84a2..b0dafe0ed 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -431,11 +431,15 @@ func (i *Install) RunWithContext(ctx context.Context, ch ci.Charter, vals map[st kube.ClientCreateOptionDryRun(true), ) } else if len(resources) > 0 { + updateThreeWayMergeForUnstructured := i.TakeOwnership && !i.ServerSideApply _, err = i.cfg.KubeClient.Update( toBeAdopted, resources, + kube.ClientUpdateOptionForceReplace(i.ForceReplace), kube.ClientUpdateOptionServerSideApply(i.ServerSideApply, i.ForceConflicts), kube.ClientUpdateOptionDryRun(true), + kube.ClientUpdateOptionThreeWayMergeForUnstructured(updateThreeWayMergeForUnstructured), + kube.ClientUpdateOptionUpgradeClientSideFieldManager(true), ) } if err != nil { diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go index ff8ff582a..bd94ca880 100644 --- a/pkg/action/upgrade.go +++ b/pkg/action/upgrade.go @@ -400,12 +400,14 @@ func (u *Upgrade) performUpgrade(ctx context.Context, originalRelease, upgradedR u.cfg.Logger().Debug("dry run for release", "name", upgradedRelease.Name) // For server-side dry-run, validate resources against the API server if u.DryRunStrategy == DryRunServer { + upgradeClientSideFieldManager := isReleaseApplyMethodClientSideApply(originalRelease.ApplyMethod) && serverSideApply _, err := u.cfg.KubeClient.Update( current, target, kube.ClientUpdateOptionForceReplace(u.ForceReplace), kube.ClientUpdateOptionServerSideApply(serverSideApply, u.ForceConflicts), kube.ClientUpdateOptionDryRun(true), + kube.ClientUpdateOptionUpgradeClientSideFieldManager(upgradeClientSideFieldManager), ) if err != nil { return upgradedRelease, err From ae66ed7a215d3b969d88fc79e9e90c37064bfdcd Mon Sep 17 00:00:00 2001 From: MrJack <36191829+biagiopietro@users.noreply.github.com> Date: Mon, 22 Jun 2026 08:05:56 +0200 Subject: [PATCH 6/8] Update pkg/action/install_test.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: MrJack <36191829+biagiopietro@users.noreply.github.com> --- pkg/action/install_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go index 792b6cd2a..5befcab01 100644 --- a/pkg/action/install_test.go +++ b/pkg/action/install_test.go @@ -478,7 +478,7 @@ func TestInstallRelease_DryRunServerValidation(t *testing.T) { config.KubeClient.(*kubefake.FailingKubeClient).CreateError = expectedErr instAction.DryRunStrategy = DryRunServer - vals := map[string]interface{}{} + vals := map[string]any{} _, err := instAction.Run(buildChart(withSampleTemplates()), vals) is.Error(err) From a2624e633bbdf1512fb6b6e271bd9bda65da7bfb Mon Sep 17 00:00:00 2001 From: MrJack <36191829+biagiopietro@users.noreply.github.com> Date: Mon, 22 Jun 2026 08:06:28 +0200 Subject: [PATCH 7/8] Update pkg/action/upgrade_test.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: MrJack <36191829+biagiopietro@users.noreply.github.com> --- pkg/action/upgrade_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/action/upgrade_test.go b/pkg/action/upgrade_test.go index 051d92ebc..8d0af299f 100644 --- a/pkg/action/upgrade_test.go +++ b/pkg/action/upgrade_test.go @@ -655,7 +655,7 @@ func TestUpgradeRelease_DryRunServerValidation(t *testing.T) { config.KubeClient.(*kubefake.FailingKubeClient).UpdateError = expectedErr upAction.DryRunStrategy = DryRunServer - vals := map[string]interface{}{} + vals := map[string]any{} ctx, done := context.WithCancel(t.Context()) _, err := upAction.RunWithContext(ctx, rel.Name, buildChart(), vals) done() From 74dc48a3ed5a35c495c54fe317c7df301b4f8a96 Mon Sep 17 00:00:00 2001 From: MrJack <36191829+biagiopietro@users.noreply.github.com> Date: Mon, 22 Jun 2026 08:29:02 +0200 Subject: [PATCH 8/8] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: MrJack <36191829+biagiopietro@users.noreply.github.com> --- pkg/action/install.go | 11 +++++++---- pkg/action/upgrade.go | 11 ++++++++--- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/pkg/action/install.go b/pkg/action/install.go index b0dafe0ed..3d6daf759 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -421,22 +421,25 @@ func (i *Install) RunWithContext(ctx context.Context, ch ci.Charter, vals map[st // Bail out here if it is a dry run if isDryRun(i.DryRunStrategy) { - // For server-side dry-run, validate resources against the API server + // For server-side dry-run, validate resources against the API server. + // Force server-side apply in this path because kube dry-run semantics are + // only honored by the server-side apply create/update code paths. if i.DryRunStrategy == DryRunServer { + serverSideDryRun := true var err error if len(toBeAdopted) == 0 && len(resources) > 0 { _, err = i.cfg.KubeClient.Create( resources, - kube.ClientCreateOptionServerSideApply(i.ServerSideApply, false), + kube.ClientCreateOptionServerSideApply(serverSideDryRun, false), kube.ClientCreateOptionDryRun(true), ) } else if len(resources) > 0 { - updateThreeWayMergeForUnstructured := i.TakeOwnership && !i.ServerSideApply + updateThreeWayMergeForUnstructured := i.TakeOwnership && !serverSideDryRun _, err = i.cfg.KubeClient.Update( toBeAdopted, resources, kube.ClientUpdateOptionForceReplace(i.ForceReplace), - kube.ClientUpdateOptionServerSideApply(i.ServerSideApply, i.ForceConflicts), + kube.ClientUpdateOptionServerSideApply(serverSideDryRun, i.ForceConflicts), kube.ClientUpdateOptionDryRun(true), kube.ClientUpdateOptionThreeWayMergeForUnstructured(updateThreeWayMergeForUnstructured), kube.ClientUpdateOptionUpgradeClientSideFieldManager(true), diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go index bd94ca880..945ff3062 100644 --- a/pkg/action/upgrade.go +++ b/pkg/action/upgrade.go @@ -400,12 +400,17 @@ func (u *Upgrade) performUpgrade(ctx context.Context, originalRelease, upgradedR u.cfg.Logger().Debug("dry run for release", "name", upgradedRelease.Name) // For server-side dry-run, validate resources against the API server if u.DryRunStrategy == DryRunServer { - upgradeClientSideFieldManager := isReleaseApplyMethodClientSideApply(originalRelease.ApplyMethod) && serverSideApply + // Ensure this validation request is strictly non-mutating by always using + // server-side apply and disabling force-replace, regardless of the user's + // normal upgrade options. + dryRunServerSideApply := true + dryRunForceReplace := false + upgradeClientSideFieldManager := isReleaseApplyMethodClientSideApply(originalRelease.ApplyMethod) && dryRunServerSideApply _, err := u.cfg.KubeClient.Update( current, target, - kube.ClientUpdateOptionForceReplace(u.ForceReplace), - kube.ClientUpdateOptionServerSideApply(serverSideApply, u.ForceConflicts), + kube.ClientUpdateOptionForceReplace(dryRunForceReplace), + kube.ClientUpdateOptionServerSideApply(dryRunServerSideApply, u.ForceConflicts), kube.ClientUpdateOptionDryRun(true), kube.ClientUpdateOptionUpgradeClientSideFieldManager(upgradeClientSideFieldManager), )