mirror of
https://github.com/helm/helm.git
synced 2026-05-28 04:35:48 -04:00
Helm client/SDK support server-side apply
Signed-off-by: George Jenkins <gvjenkins@gmail.com>
This commit is contained in:
parent
892a5cbfd0
commit
e2dcbe28bf
20 changed files with 320 additions and 97 deletions
|
|
@ -520,3 +520,10 @@ func (cfg *Configuration) Init(getter genericclioptions.RESTClientGetter, namesp
|
|||
func (cfg *Configuration) SetHookOutputFunc(hookOutputFunc func(_, _, _ string) io.Writer) {
|
||||
cfg.HookOutputFunc = hookOutputFunc
|
||||
}
|
||||
|
||||
func determineReleaseSSApplyMethod(serverSideApply bool) release.ApplyMethod {
|
||||
if serverSideApply {
|
||||
return release.ApplyMethodServerSideApply
|
||||
}
|
||||
return release.ApplyMethodClientSideApply
|
||||
}
|
||||
|
|
|
|||
|
|
@ -946,3 +946,8 @@ func TestRenderResources_NoPostRenderer(t *testing.T) {
|
|||
assert.NotNil(t, buf)
|
||||
assert.Equal(t, "", notes)
|
||||
}
|
||||
|
||||
func TestDetermineReleaseSSAApplyMethod(t *testing.T) {
|
||||
assert.Equal(t, release.ApplyMethodClientSideApply, determineReleaseSSApplyMethod(false))
|
||||
assert.Equal(t, release.ApplyMethodServerSideApply, determineReleaseSSApplyMethod(true))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ type Metadata struct {
|
|||
Revision int `json:"revision" yaml:"revision"`
|
||||
Status string `json:"status" yaml:"status"`
|
||||
DeployedAt string `json:"deployedAt" yaml:"deployedAt"`
|
||||
ApplyMethod string `json:"applyMethod,omitempty" yaml:"applyMethod,omitempty"`
|
||||
}
|
||||
|
||||
// NewGetMetadata creates a new GetMetadata object with the given configuration.
|
||||
|
|
@ -79,6 +80,7 @@ func (g *GetMetadata) Run(name string) (*Metadata, error) {
|
|||
Revision: rel.Version,
|
||||
Status: rel.Info.Status.String(),
|
||||
DeployedAt: rel.Info.LastDeployed.Format(time.RFC3339),
|
||||
ApplyMethod: rel.ApplyMethod,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ import (
|
|||
)
|
||||
|
||||
// execHook executes all of the hooks for the given hook event.
|
||||
func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent, waitStrategy kube.WaitStrategy, timeout time.Duration) error {
|
||||
func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent, waitStrategy kube.WaitStrategy, timeout time.Duration, serverSideApply bool) error {
|
||||
executingHooks := []*release.Hook{}
|
||||
|
||||
for _, h := range rl.Hooks {
|
||||
|
|
@ -75,7 +75,7 @@ func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent,
|
|||
// Create hook resources
|
||||
if _, err := cfg.KubeClient.Create(
|
||||
resources,
|
||||
kube.ClientCreateOptionServerSideApply(false, false)); err != nil {
|
||||
kube.ClientCreateOptionServerSideApply(serverSideApply, false)); err != nil {
|
||||
h.LastRun.CompletedAt = helmtime.Now()
|
||||
h.LastRun.Phase = release.HookPhaseFailed
|
||||
return fmt.Errorf("warning: Hook %s %s failed: %w", hook, h.Path, err)
|
||||
|
|
|
|||
|
|
@ -385,7 +385,8 @@ data:
|
|||
Capabilities: chartutil.DefaultCapabilities,
|
||||
}
|
||||
|
||||
err := configuration.execHook(&tc.inputRelease, hookEvent, kube.StatusWatcherStrategy, 600)
|
||||
serverSideApply := true
|
||||
err := configuration.execHook(&tc.inputRelease, hookEvent, kube.StatusWatcherStrategy, 600, serverSideApply)
|
||||
|
||||
if !reflect.DeepEqual(kubeClient.deleteRecord, tc.expectedDeleteRecord) {
|
||||
t.Fatalf("Got unexpected delete record, expected: %#v, but got: %#v", kubeClient.deleteRecord, tc.expectedDeleteRecord)
|
||||
|
|
|
|||
|
|
@ -75,7 +75,13 @@ type Install struct {
|
|||
// ForceReplace will, if set to `true`, ignore certain warnings and perform the install anyway.
|
||||
//
|
||||
// This should be used with caution.
|
||||
ForceReplace bool
|
||||
ForceReplace bool
|
||||
// ForceConflicts causes server-side apply to force conflicts ("Overwrite value, become sole manager")
|
||||
// see: https://kubernetes.io/docs/reference/using-api/server-side-apply/#conflicts
|
||||
ForceConflicts bool
|
||||
// ServerSideApply when true (default) will enable changes to be applied via Kubernetes server-side apply
|
||||
// see: https://kubernetes.io/docs/reference/using-api/server-side-apply/
|
||||
ServerSideApply bool
|
||||
CreateNamespace bool
|
||||
DryRun bool
|
||||
DryRunOption string
|
||||
|
|
@ -145,7 +151,8 @@ type ChartPathOptions struct {
|
|||
// NewInstall creates a new Install object with the given configuration.
|
||||
func NewInstall(cfg *Configuration) *Install {
|
||||
in := &Install{
|
||||
cfg: cfg,
|
||||
cfg: cfg,
|
||||
ServerSideApply: true,
|
||||
}
|
||||
in.registryClient = cfg.RegistryClient
|
||||
|
||||
|
|
@ -175,7 +182,7 @@ func (i *Install) installCRDs(crds []chart.CRD) error {
|
|||
// Send them to Kube
|
||||
if _, err := i.cfg.KubeClient.Create(
|
||||
res,
|
||||
kube.ClientCreateOptionServerSideApply(false, false)); err != nil {
|
||||
kube.ClientCreateOptionServerSideApply(i.ServerSideApply, i.ForceConflicts)); err != nil {
|
||||
// If the error is CRD already exists, continue.
|
||||
if apierrors.IsAlreadyExists(err) {
|
||||
crdName := res[0].Name
|
||||
|
|
@ -403,7 +410,7 @@ func (i *Install) RunWithContext(ctx context.Context, chrt *chart.Chart, vals ma
|
|||
}
|
||||
if _, err := i.cfg.KubeClient.Create(
|
||||
resourceList,
|
||||
kube.ClientCreateOptionServerSideApply(false, false)); err != nil && !apierrors.IsAlreadyExists(err) {
|
||||
kube.ClientCreateOptionServerSideApply(i.ServerSideApply, false)); err != nil && !apierrors.IsAlreadyExists(err) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
|
@ -415,8 +422,7 @@ func (i *Install) RunWithContext(ctx context.Context, chrt *chart.Chart, vals ma
|
|||
}
|
||||
}
|
||||
|
||||
// Store the release in history before continuing (new in Helm 3). We always know
|
||||
// that this is a create operation.
|
||||
// Store the release in history before continuing. We always know that this is a create operation
|
||||
if err := i.cfg.Releases.Create(rel); err != nil {
|
||||
// We could try to recover gracefully here, but since nothing has been installed
|
||||
// yet, this is probably safer than trying to continue when we know storage is
|
||||
|
|
@ -463,7 +469,7 @@ func (i *Install) performInstall(rel *release.Release, toBeAdopted kube.Resource
|
|||
var err error
|
||||
// pre-install hooks
|
||||
if !i.DisableHooks {
|
||||
if err := i.cfg.execHook(rel, release.HookPreInstall, i.WaitStrategy, i.Timeout); err != nil {
|
||||
if err := i.cfg.execHook(rel, release.HookPreInstall, i.WaitStrategy, i.Timeout, i.ServerSideApply); err != nil {
|
||||
return rel, fmt.Errorf("failed pre-install: %s", err)
|
||||
}
|
||||
}
|
||||
|
|
@ -474,15 +480,15 @@ func (i *Install) performInstall(rel *release.Release, toBeAdopted kube.Resource
|
|||
if len(toBeAdopted) == 0 && len(resources) > 0 {
|
||||
_, err = i.cfg.KubeClient.Create(
|
||||
resources,
|
||||
kube.ClientCreateOptionServerSideApply(false, false))
|
||||
kube.ClientCreateOptionServerSideApply(i.ServerSideApply, false))
|
||||
} else if len(resources) > 0 {
|
||||
updateThreeWayMergeForUnstructured := i.TakeOwnership
|
||||
useUpdateThreeWayMergeForUnstructured := i.TakeOwnership && !i.ServerSideApply // Use three-way merge when taking ownership (and not using server-side apply)
|
||||
_, err = i.cfg.KubeClient.Update(
|
||||
toBeAdopted,
|
||||
resources,
|
||||
kube.ClientUpdateOptionServerSideApply(false, false),
|
||||
kube.ClientUpdateOptionThreeWayMergeForUnstructured(updateThreeWayMergeForUnstructured),
|
||||
kube.ClientUpdateOptionForceReplace(i.ForceReplace))
|
||||
kube.ClientUpdateOptionForceReplace(i.ForceReplace),
|
||||
kube.ClientUpdateOptionServerSideApply(i.ServerSideApply, i.ForceConflicts),
|
||||
kube.ClientUpdateOptionThreeWayMergeForUnstructured(useUpdateThreeWayMergeForUnstructured))
|
||||
}
|
||||
if err != nil {
|
||||
return rel, err
|
||||
|
|
@ -503,7 +509,7 @@ func (i *Install) performInstall(rel *release.Release, toBeAdopted kube.Resource
|
|||
}
|
||||
|
||||
if !i.DisableHooks {
|
||||
if err := i.cfg.execHook(rel, release.HookPostInstall, i.WaitStrategy, i.Timeout); err != nil {
|
||||
if err := i.cfg.execHook(rel, release.HookPostInstall, i.WaitStrategy, i.Timeout, i.ServerSideApply); err != nil {
|
||||
return rel, fmt.Errorf("failed post-install: %s", err)
|
||||
}
|
||||
}
|
||||
|
|
@ -580,7 +586,8 @@ func (i *Install) availableName() error {
|
|||
// createRelease creates a new release object
|
||||
func (i *Install) createRelease(chrt *chart.Chart, rawVals map[string]interface{}, labels map[string]string) *release.Release {
|
||||
ts := i.cfg.Now()
|
||||
return &release.Release{
|
||||
|
||||
r := &release.Release{
|
||||
Name: i.ReleaseName,
|
||||
Namespace: i.Namespace,
|
||||
Chart: chrt,
|
||||
|
|
@ -590,9 +597,12 @@ func (i *Install) createRelease(chrt *chart.Chart, rawVals map[string]interface{
|
|||
LastDeployed: ts,
|
||||
Status: release.StatusUnknown,
|
||||
},
|
||||
Version: 1,
|
||||
Labels: labels,
|
||||
Version: 1,
|
||||
Labels: labels,
|
||||
ApplyMethod: string(determineReleaseSSApplyMethod(i.ServerSideApply)),
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// recordRelease with an update operation in case reuse has been set.
|
||||
|
|
|
|||
|
|
@ -96,7 +96,8 @@ func (r *ReleaseTesting) Run(name string) (*release.Release, error) {
|
|||
rel.Hooks = executingHooks
|
||||
}
|
||||
|
||||
if err := r.cfg.execHook(rel, release.HookTest, kube.StatusWatcherStrategy, r.Timeout); err != nil {
|
||||
serverSideApply := rel.ApplyMethod == string(release.ApplyMethodServerSideApply)
|
||||
if err := r.cfg.execHook(rel, release.HookTest, kube.StatusWatcherStrategy, r.Timeout, serverSideApply); err != nil {
|
||||
rel.Hooks = append(skippedHooks, rel.Hooks...)
|
||||
r.cfg.Releases.Update(rel)
|
||||
return rel, err
|
||||
|
|
|
|||
|
|
@ -44,9 +44,17 @@ type Rollback struct {
|
|||
// ForceReplace will, if set to `true`, ignore certain warnings and perform the rollback anyway.
|
||||
//
|
||||
// This should be used with caution.
|
||||
ForceReplace bool
|
||||
CleanupOnFail bool
|
||||
MaxHistory int // MaxHistory limits the maximum number of revisions saved per release
|
||||
ForceReplace bool
|
||||
// ForceConflicts causes server-side apply to force conflicts ("Overwrite value, become sole manager")
|
||||
// see: https://kubernetes.io/docs/reference/using-api/server-side-apply/#conflicts
|
||||
ForceConflicts bool
|
||||
// ServerSideApply enables changes to be applied via Kubernetes server-side apply
|
||||
// Can be the string: "true", "false" or "auto"
|
||||
// When "auto", sever-side usage will be based upon the releases previous usage
|
||||
// see: https://kubernetes.io/docs/reference/using-api/server-side-apply/
|
||||
ServerSideApply string
|
||||
CleanupOnFail bool
|
||||
MaxHistory int // MaxHistory limits the maximum number of revisions saved per release
|
||||
}
|
||||
|
||||
// NewRollback creates a new Rollback object with the given configuration.
|
||||
|
|
@ -65,7 +73,7 @@ func (r *Rollback) Run(name string) error {
|
|||
r.cfg.Releases.MaxHistory = r.MaxHistory
|
||||
|
||||
slog.Debug("preparing rollback", "name", name)
|
||||
currentRelease, targetRelease, err := r.prepareRollback(name)
|
||||
currentRelease, targetRelease, serverSideApply, err := r.prepareRollback(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -78,7 +86,7 @@ func (r *Rollback) Run(name string) error {
|
|||
}
|
||||
|
||||
slog.Debug("performing rollback", "name", name)
|
||||
if _, err := r.performRollback(currentRelease, targetRelease); err != nil {
|
||||
if _, err := r.performRollback(currentRelease, targetRelease, serverSideApply); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
@ -93,18 +101,18 @@ func (r *Rollback) Run(name string) error {
|
|||
|
||||
// prepareRollback finds the previous release and prepares a new release object with
|
||||
// the previous release's configuration
|
||||
func (r *Rollback) prepareRollback(name string) (*release.Release, *release.Release, error) {
|
||||
func (r *Rollback) prepareRollback(name string) (*release.Release, *release.Release, bool, error) {
|
||||
if err := chartutil.ValidateReleaseName(name); err != nil {
|
||||
return nil, nil, fmt.Errorf("prepareRollback: Release name is invalid: %s", name)
|
||||
return nil, nil, false, fmt.Errorf("prepareRollback: Release name is invalid: %s", name)
|
||||
}
|
||||
|
||||
if r.Version < 0 {
|
||||
return nil, nil, errInvalidRevision
|
||||
return nil, nil, false, errInvalidRevision
|
||||
}
|
||||
|
||||
currentRelease, err := r.cfg.Releases.Last(name)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, nil, false, err
|
||||
}
|
||||
|
||||
previousVersion := r.Version
|
||||
|
|
@ -114,7 +122,7 @@ func (r *Rollback) prepareRollback(name string) (*release.Release, *release.Rele
|
|||
|
||||
historyReleases, err := r.cfg.Releases.History(name)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, nil, false, err
|
||||
}
|
||||
|
||||
// Check if the history version to be rolled back exists
|
||||
|
|
@ -127,14 +135,19 @@ func (r *Rollback) prepareRollback(name string) (*release.Release, *release.Rele
|
|||
}
|
||||
}
|
||||
if !previousVersionExist {
|
||||
return nil, nil, fmt.Errorf("release has no %d version", previousVersion)
|
||||
return nil, nil, false, fmt.Errorf("release has no %d version", previousVersion)
|
||||
}
|
||||
|
||||
slog.Debug("rolling back", "name", name, "currentVersion", currentRelease.Version, "targetVersion", previousVersion)
|
||||
|
||||
previousRelease, err := r.cfg.Releases.Get(name, previousVersion)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, nil, false, err
|
||||
}
|
||||
|
||||
serverSideApply, err := getUpgradeServerSideValue(r.ServerSideApply, previousRelease.ApplyMethod)
|
||||
if err != nil {
|
||||
return nil, nil, false, err
|
||||
}
|
||||
|
||||
// Store a new release object with previous release's configuration
|
||||
|
|
@ -152,16 +165,17 @@ func (r *Rollback) prepareRollback(name string) (*release.Release, *release.Rele
|
|||
// message here, and only override it later if we experience failure.
|
||||
Description: fmt.Sprintf("Rollback to %d", previousVersion),
|
||||
},
|
||||
Version: currentRelease.Version + 1,
|
||||
Labels: previousRelease.Labels,
|
||||
Manifest: previousRelease.Manifest,
|
||||
Hooks: previousRelease.Hooks,
|
||||
Version: currentRelease.Version + 1,
|
||||
Labels: previousRelease.Labels,
|
||||
Manifest: previousRelease.Manifest,
|
||||
Hooks: previousRelease.Hooks,
|
||||
ApplyMethod: string(determineReleaseSSApplyMethod(serverSideApply)),
|
||||
}
|
||||
|
||||
return currentRelease, targetRelease, nil
|
||||
return currentRelease, targetRelease, serverSideApply, nil
|
||||
}
|
||||
|
||||
func (r *Rollback) performRollback(currentRelease, targetRelease *release.Release) (*release.Release, error) {
|
||||
func (r *Rollback) performRollback(currentRelease, targetRelease *release.Release, serverSideApply bool) (*release.Release, error) {
|
||||
if r.DryRun {
|
||||
slog.Debug("dry run", "name", targetRelease.Name)
|
||||
return targetRelease, nil
|
||||
|
|
@ -177,15 +191,16 @@ func (r *Rollback) performRollback(currentRelease, targetRelease *release.Releas
|
|||
}
|
||||
|
||||
// pre-rollback hooks
|
||||
|
||||
if !r.DisableHooks {
|
||||
if err := r.cfg.execHook(targetRelease, release.HookPreRollback, r.WaitStrategy, r.Timeout); err != nil {
|
||||
if err := r.cfg.execHook(targetRelease, release.HookPreRollback, r.WaitStrategy, r.Timeout, serverSideApply); err != nil {
|
||||
return targetRelease, err
|
||||
}
|
||||
} else {
|
||||
slog.Debug("rollback hooks disabled", "name", targetRelease.Name)
|
||||
}
|
||||
|
||||
// It is safe to use "force" here because these are resources currently rendered by the chart.
|
||||
// It is safe to use "forceOwnership" here because these are resources currently rendered by the chart.
|
||||
err = target.Visit(setMetadataVisitor(targetRelease.Name, targetRelease.Namespace, true))
|
||||
if err != nil {
|
||||
return targetRelease, fmt.Errorf("unable to set metadata visitor from target release: %w", err)
|
||||
|
|
@ -193,8 +208,9 @@ func (r *Rollback) performRollback(currentRelease, targetRelease *release.Releas
|
|||
results, err := r.cfg.KubeClient.Update(
|
||||
current,
|
||||
target,
|
||||
kube.ClientUpdateOptionServerSideApply(false, false),
|
||||
kube.ClientUpdateOptionForceReplace(r.ForceReplace))
|
||||
kube.ClientUpdateOptionForceReplace(r.ForceReplace),
|
||||
kube.ClientUpdateOptionServerSideApply(serverSideApply, r.ForceConflicts),
|
||||
kube.ClientUpdateOptionThreeWayMergeForUnstructured(false))
|
||||
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("Rollback %q failed: %s", targetRelease.Name, err)
|
||||
|
|
@ -239,7 +255,7 @@ func (r *Rollback) performRollback(currentRelease, targetRelease *release.Releas
|
|||
|
||||
// post-rollback hooks
|
||||
if !r.DisableHooks {
|
||||
if err := r.cfg.execHook(targetRelease, release.HookPostRollback, r.WaitStrategy, r.Timeout); err != nil {
|
||||
if err := r.cfg.execHook(targetRelease, release.HookPostRollback, r.WaitStrategy, r.Timeout, serverSideApply); err != nil {
|
||||
return targetRelease, err
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -115,7 +115,8 @@ func (u *Uninstall) Run(name string) (*release.UninstallReleaseResponse, error)
|
|||
res := &release.UninstallReleaseResponse{Release: rel}
|
||||
|
||||
if !u.DisableHooks {
|
||||
if err := u.cfg.execHook(rel, release.HookPreDelete, u.WaitStrategy, u.Timeout); err != nil {
|
||||
serverSideApply := true
|
||||
if err := u.cfg.execHook(rel, release.HookPreDelete, u.WaitStrategy, u.Timeout, serverSideApply); err != nil {
|
||||
return res, err
|
||||
}
|
||||
} else {
|
||||
|
|
@ -144,7 +145,8 @@ func (u *Uninstall) Run(name string) (*release.UninstallReleaseResponse, error)
|
|||
}
|
||||
|
||||
if !u.DisableHooks {
|
||||
if err := u.cfg.execHook(rel, release.HookPostDelete, u.WaitStrategy, u.Timeout); err != nil {
|
||||
serverSideApply := true
|
||||
if err := u.cfg.execHook(rel, release.HookPostDelete, u.WaitStrategy, u.Timeout, serverSideApply); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
|
|
@ -244,11 +246,13 @@ func (u *Uninstall) deleteRelease(rel *release.Release) (kube.ResourceList, stri
|
|||
return nil, "", []error{fmt.Errorf("unable to build kubernetes objects for delete: %w", err)}
|
||||
}
|
||||
if len(resources) > 0 {
|
||||
if kubeClient, ok := u.cfg.KubeClient.(kube.InterfaceDeletionPropagation); ok {
|
||||
_, errs = kubeClient.DeleteWithPropagationPolicy(resources, parseCascadingFlag(u.DeletionPropagation))
|
||||
return resources, kept, errs
|
||||
if len(resources) > 0 {
|
||||
if kubeClient, ok := u.cfg.KubeClient.(kube.InterfaceDeletionPropagation); ok {
|
||||
_, errs = kubeClient.DeleteWithPropagationPolicy(resources, parseCascadingFlag(u.DeletionPropagation))
|
||||
return resources, kept, errs
|
||||
}
|
||||
_, errs = u.cfg.KubeClient.Delete(resources)
|
||||
}
|
||||
_, errs = u.cfg.KubeClient.Delete(resources)
|
||||
}
|
||||
return resources, kept, errs
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"helm.sh/helm/v4/pkg/kube"
|
||||
kubefake "helm.sh/helm/v4/pkg/kube/fake"
|
||||
|
|
@ -147,6 +148,6 @@ func TestUninstallRelease_Cascade(t *testing.T) {
|
|||
failer.BuildDummy = true
|
||||
unAction.cfg.KubeClient = failer
|
||||
_, err := unAction.Run(rel.Name)
|
||||
is.Error(err)
|
||||
require.Error(t, err)
|
||||
is.Contains(err.Error(), "failed to delete release: come-fail-away")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,6 +81,14 @@ type Upgrade struct {
|
|||
//
|
||||
// This should be used with caution.
|
||||
ForceReplace bool
|
||||
// ForceConflicts causes server-side apply to force conflicts ("Overwrite value, become sole manager")
|
||||
// see: https://kubernetes.io/docs/reference/using-api/server-side-apply/#conflicts
|
||||
ForceConflicts bool
|
||||
// ServerSideApply enables changes to be applied via Kubernetes server-side apply
|
||||
// Can be the string: "true", "false" or "auto"
|
||||
// When "auto", sever-side usage will be based upon the releases previous usage
|
||||
// see: https://kubernetes.io/docs/reference/using-api/server-side-apply/
|
||||
ServerSideApply string
|
||||
// ResetValues will reset the values to the chart's built-ins rather than merging with existing.
|
||||
ResetValues bool
|
||||
// ReuseValues will reuse the user's last supplied values.
|
||||
|
|
@ -127,7 +135,8 @@ type resultMessage struct {
|
|||
// NewUpgrade creates a new Upgrade object with the given configuration.
|
||||
func NewUpgrade(cfg *Configuration) *Upgrade {
|
||||
up := &Upgrade{
|
||||
cfg: cfg,
|
||||
cfg: cfg,
|
||||
ServerSideApply: "auto",
|
||||
}
|
||||
up.registryClient = cfg.RegistryClient
|
||||
|
||||
|
|
@ -162,7 +171,7 @@ func (u *Upgrade) RunWithContext(ctx context.Context, name string, chart *chart.
|
|||
}
|
||||
|
||||
slog.Debug("preparing upgrade", "name", name)
|
||||
currentRelease, upgradedRelease, err := u.prepareUpgrade(name, chart, vals)
|
||||
currentRelease, upgradedRelease, serverSideApply, err := u.prepareUpgrade(name, chart, vals)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -170,7 +179,7 @@ func (u *Upgrade) RunWithContext(ctx context.Context, name string, chart *chart.
|
|||
u.cfg.Releases.MaxHistory = u.MaxHistory
|
||||
|
||||
slog.Debug("performing update", "name", name)
|
||||
res, err := u.performUpgrade(ctx, currentRelease, upgradedRelease)
|
||||
res, err := u.performUpgrade(ctx, currentRelease, upgradedRelease, serverSideApply)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
|
@ -195,14 +204,14 @@ func (u *Upgrade) isDryRun() bool {
|
|||
}
|
||||
|
||||
// prepareUpgrade builds an upgraded release for an upgrade operation.
|
||||
func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart, vals map[string]interface{}) (*release.Release, *release.Release, error) {
|
||||
func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart, vals map[string]interface{}) (*release.Release, *release.Release, bool, error) {
|
||||
if chart == nil {
|
||||
return nil, nil, errMissingChart
|
||||
return nil, nil, false, errMissingChart
|
||||
}
|
||||
|
||||
// HideSecret must be used with dry run. Otherwise, return an error.
|
||||
if !u.isDryRun() && u.HideSecret {
|
||||
return nil, nil, errors.New("hiding Kubernetes secrets requires a dry-run mode")
|
||||
return nil, nil, false, errors.New("hiding Kubernetes secrets requires a dry-run mode")
|
||||
}
|
||||
|
||||
// finds the last non-deleted release with the given name
|
||||
|
|
@ -210,14 +219,14 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart, vals map[strin
|
|||
if err != nil {
|
||||
// to keep existing behavior of returning the "%q has no deployed releases" error when an existing release does not exist
|
||||
if errors.Is(err, driver.ErrReleaseNotFound) {
|
||||
return nil, nil, driver.NewErrNoDeployedReleases(name)
|
||||
return nil, nil, false, driver.NewErrNoDeployedReleases(name)
|
||||
}
|
||||
return nil, nil, err
|
||||
return nil, nil, false, err
|
||||
}
|
||||
|
||||
// Concurrent `helm upgrade`s will either fail here with `errPending` or when creating the release with "already exists". This should act as a pessimistic lock.
|
||||
if lastRelease.Info.Status.IsPending() {
|
||||
return nil, nil, errPending
|
||||
return nil, nil, false, errPending
|
||||
}
|
||||
|
||||
var currentRelease *release.Release
|
||||
|
|
@ -232,7 +241,7 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart, vals map[strin
|
|||
(lastRelease.Info.Status == release.StatusFailed || lastRelease.Info.Status == release.StatusSuperseded) {
|
||||
currentRelease = lastRelease
|
||||
} else {
|
||||
return nil, nil, err
|
||||
return nil, nil, false, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -240,11 +249,11 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart, vals map[strin
|
|||
// determine if values will be reused
|
||||
vals, err = u.reuseValues(chart, currentRelease, vals)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, nil, false, err
|
||||
}
|
||||
|
||||
if err := chartutil.ProcessDependencies(chart, vals); err != nil {
|
||||
return nil, nil, err
|
||||
return nil, nil, false, err
|
||||
}
|
||||
|
||||
// Increment revision count. This is passed to templates, and also stored on
|
||||
|
|
@ -260,11 +269,11 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart, vals map[strin
|
|||
|
||||
caps, err := u.cfg.getCapabilities()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, nil, false, err
|
||||
}
|
||||
valuesToRender, err := chartutil.ToRenderValuesWithSchemaValidation(chart, vals, options, caps, u.SkipSchemaValidation)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, nil, false, err
|
||||
}
|
||||
|
||||
// Determine whether or not to interact with remote
|
||||
|
|
@ -275,13 +284,20 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart, vals map[strin
|
|||
|
||||
hooks, manifestDoc, notesTxt, err := u.cfg.renderResources(chart, valuesToRender, "", "", u.SubNotes, false, false, u.PostRenderer, interactWithRemote, u.EnableDNS, u.HideSecret)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, nil, false, err
|
||||
}
|
||||
|
||||
if driver.ContainsSystemLabels(u.Labels) {
|
||||
return nil, nil, fmt.Errorf("user supplied labels contains system reserved label name. System labels: %+v", driver.GetSystemLabels())
|
||||
return nil, nil, false, fmt.Errorf("user supplied labels contains system reserved label name. System labels: %+v", driver.GetSystemLabels())
|
||||
}
|
||||
|
||||
serverSideApply, err := getUpgradeServerSideValue(u.ServerSideApply, lastRelease.ApplyMethod)
|
||||
if err != nil {
|
||||
return nil, nil, false, err
|
||||
}
|
||||
|
||||
slog.Debug("determined release apply method", slog.Bool("server_side_apply", serverSideApply), slog.String("previous_release_apply_method", lastRelease.ApplyMethod))
|
||||
|
||||
// Store an upgraded release.
|
||||
upgradedRelease := &release.Release{
|
||||
Name: name,
|
||||
|
|
@ -294,20 +310,21 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart, vals map[strin
|
|||
Status: release.StatusPendingUpgrade,
|
||||
Description: "Preparing upgrade", // This should be overwritten later.
|
||||
},
|
||||
Version: revision,
|
||||
Manifest: manifestDoc.String(),
|
||||
Hooks: hooks,
|
||||
Labels: mergeCustomLabels(lastRelease.Labels, u.Labels),
|
||||
Version: revision,
|
||||
Manifest: manifestDoc.String(),
|
||||
Hooks: hooks,
|
||||
Labels: mergeCustomLabels(lastRelease.Labels, u.Labels),
|
||||
ApplyMethod: string(determineReleaseSSApplyMethod(serverSideApply)),
|
||||
}
|
||||
|
||||
if len(notesTxt) > 0 {
|
||||
upgradedRelease.Info.Notes = notesTxt
|
||||
}
|
||||
err = validateManifest(u.cfg.KubeClient, manifestDoc.Bytes(), !u.DisableOpenAPIValidation)
|
||||
return currentRelease, upgradedRelease, err
|
||||
return currentRelease, upgradedRelease, serverSideApply, err
|
||||
}
|
||||
|
||||
func (u *Upgrade) performUpgrade(ctx context.Context, originalRelease, upgradedRelease *release.Release) (*release.Release, error) {
|
||||
func (u *Upgrade) performUpgrade(ctx context.Context, originalRelease, upgradedRelease *release.Release, serverSideApply bool) (*release.Release, error) {
|
||||
current, err := u.cfg.KubeClient.Build(bytes.NewBufferString(originalRelease.Manifest), false)
|
||||
if err != nil {
|
||||
// Checking for removed Kubernetes API error so can provide a more informative error message to the user
|
||||
|
|
@ -380,7 +397,7 @@ func (u *Upgrade) performUpgrade(ctx context.Context, originalRelease, upgradedR
|
|||
ctxChan := make(chan resultMessage)
|
||||
doneChan := make(chan interface{})
|
||||
defer close(doneChan)
|
||||
go u.releasingUpgrade(rChan, upgradedRelease, current, target, originalRelease)
|
||||
go u.releasingUpgrade(rChan, upgradedRelease, current, target, originalRelease, serverSideApply)
|
||||
go u.handleContext(ctx, doneChan, ctxChan, upgradedRelease)
|
||||
select {
|
||||
case result := <-rChan:
|
||||
|
|
@ -414,11 +431,11 @@ func (u *Upgrade) handleContext(ctx context.Context, done chan interface{}, c ch
|
|||
return
|
||||
}
|
||||
}
|
||||
func (u *Upgrade) releasingUpgrade(c chan<- resultMessage, upgradedRelease *release.Release, current kube.ResourceList, target kube.ResourceList, originalRelease *release.Release) {
|
||||
func (u *Upgrade) releasingUpgrade(c chan<- resultMessage, upgradedRelease *release.Release, current kube.ResourceList, target kube.ResourceList, originalRelease *release.Release, serverSideApply bool) {
|
||||
// pre-upgrade hooks
|
||||
|
||||
if !u.DisableHooks {
|
||||
if err := u.cfg.execHook(upgradedRelease, release.HookPreUpgrade, u.WaitStrategy, u.Timeout); err != nil {
|
||||
if err := u.cfg.execHook(upgradedRelease, release.HookPreUpgrade, u.WaitStrategy, u.Timeout, serverSideApply); err != nil {
|
||||
u.reportToPerformUpgrade(c, upgradedRelease, kube.ResourceList{}, fmt.Errorf("pre-upgrade hooks failed: %s", err))
|
||||
return
|
||||
}
|
||||
|
|
@ -429,8 +446,8 @@ func (u *Upgrade) releasingUpgrade(c chan<- resultMessage, upgradedRelease *rele
|
|||
results, err := u.cfg.KubeClient.Update(
|
||||
current,
|
||||
target,
|
||||
kube.ClientUpdateOptionServerSideApply(false, false),
|
||||
kube.ClientUpdateOptionForceReplace(u.ForceReplace))
|
||||
kube.ClientUpdateOptionForceReplace(u.ForceReplace),
|
||||
kube.ClientUpdateOptionServerSideApply(serverSideApply, u.ForceConflicts))
|
||||
if err != nil {
|
||||
u.cfg.recordRelease(originalRelease)
|
||||
u.reportToPerformUpgrade(c, upgradedRelease, results.Created, err)
|
||||
|
|
@ -459,7 +476,7 @@ func (u *Upgrade) releasingUpgrade(c chan<- resultMessage, upgradedRelease *rele
|
|||
|
||||
// post-upgrade hooks
|
||||
if !u.DisableHooks {
|
||||
if err := u.cfg.execHook(upgradedRelease, release.HookPostUpgrade, u.WaitStrategy, u.Timeout); err != nil {
|
||||
if err := u.cfg.execHook(upgradedRelease, release.HookPostUpgrade, u.WaitStrategy, u.Timeout, serverSideApply); err != nil {
|
||||
u.reportToPerformUpgrade(c, upgradedRelease, results.Created, fmt.Errorf("post-upgrade hooks failed: %s", err))
|
||||
return
|
||||
}
|
||||
|
|
@ -530,6 +547,8 @@ func (u *Upgrade) failRelease(rel *release.Release, created kube.ResourceList, e
|
|||
rollin.WaitForJobs = u.WaitForJobs
|
||||
rollin.DisableHooks = u.DisableHooks
|
||||
rollin.ForceReplace = u.ForceReplace
|
||||
rollin.ForceConflicts = u.ForceConflicts
|
||||
rollin.ServerSideApply = u.ServerSideApply
|
||||
rollin.Timeout = u.Timeout
|
||||
if rollErr := rollin.Run(rel.Name); rollErr != nil {
|
||||
return rel, fmt.Errorf("an error occurred while rolling back the release. original upgrade error: %w: %w", err, rollErr)
|
||||
|
|
@ -607,3 +626,16 @@ func mergeCustomLabels(current, desired map[string]string) map[string]string {
|
|||
}
|
||||
return labels
|
||||
}
|
||||
|
||||
func getUpgradeServerSideValue(serverSideOption string, releaseApplyMethod string) (bool, error) {
|
||||
switch serverSideOption {
|
||||
case "auto":
|
||||
return releaseApplyMethod == "ssa", nil
|
||||
case "false":
|
||||
return false, nil
|
||||
case "true":
|
||||
return true, nil
|
||||
default:
|
||||
return false, fmt.Errorf("invalid/unknown release server-side apply method: %s", serverSideOption)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -583,3 +583,109 @@ func TestUpgradeRelease_DryRun(t *testing.T) {
|
|||
done()
|
||||
req.Error(err)
|
||||
}
|
||||
|
||||
func TestGetUpgradeServerSideValue(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
actionServerSideOption string
|
||||
releaseApplyMethod string
|
||||
expectedServerSideApply bool
|
||||
}{
|
||||
{
|
||||
name: "action ssa auto / release csa",
|
||||
actionServerSideOption: "auto",
|
||||
releaseApplyMethod: "csa",
|
||||
expectedServerSideApply: false,
|
||||
},
|
||||
{
|
||||
name: "action ssa auto / release ssa",
|
||||
actionServerSideOption: "auto",
|
||||
releaseApplyMethod: "ssa",
|
||||
expectedServerSideApply: true,
|
||||
},
|
||||
{
|
||||
name: "action ssa auto / release empty",
|
||||
actionServerSideOption: "auto",
|
||||
releaseApplyMethod: "",
|
||||
expectedServerSideApply: false,
|
||||
},
|
||||
{
|
||||
name: "action ssa true / release csa",
|
||||
actionServerSideOption: "true",
|
||||
releaseApplyMethod: "csa",
|
||||
expectedServerSideApply: true,
|
||||
},
|
||||
{
|
||||
name: "action ssa true / release ssa",
|
||||
actionServerSideOption: "true",
|
||||
releaseApplyMethod: "ssa",
|
||||
expectedServerSideApply: true,
|
||||
},
|
||||
{
|
||||
name: "action ssa true / release 'unknown'",
|
||||
actionServerSideOption: "true",
|
||||
releaseApplyMethod: "foo",
|
||||
expectedServerSideApply: true,
|
||||
},
|
||||
{
|
||||
name: "action ssa true / release empty",
|
||||
actionServerSideOption: "true",
|
||||
releaseApplyMethod: "",
|
||||
expectedServerSideApply: true,
|
||||
},
|
||||
{
|
||||
name: "action ssa false / release csa",
|
||||
actionServerSideOption: "false",
|
||||
releaseApplyMethod: "ssa",
|
||||
expectedServerSideApply: false,
|
||||
},
|
||||
{
|
||||
name: "action ssa false / release ssa",
|
||||
actionServerSideOption: "false",
|
||||
releaseApplyMethod: "ssa",
|
||||
expectedServerSideApply: false,
|
||||
},
|
||||
{
|
||||
name: "action ssa false / release 'unknown'",
|
||||
actionServerSideOption: "false",
|
||||
releaseApplyMethod: "foo",
|
||||
expectedServerSideApply: false,
|
||||
},
|
||||
{
|
||||
name: "action ssa false / release empty",
|
||||
actionServerSideOption: "false",
|
||||
releaseApplyMethod: "ssa",
|
||||
expectedServerSideApply: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
serverSideApply, err := getUpgradeServerSideValue(tt.actionServerSideOption, tt.releaseApplyMethod)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, tt.expectedServerSideApply, serverSideApply)
|
||||
})
|
||||
}
|
||||
|
||||
testsError := []struct {
|
||||
name string
|
||||
actionServerSideOption string
|
||||
releaseApplyMethod string
|
||||
expectedErrorMsg string
|
||||
}{
|
||||
{
|
||||
name: "action invalid option",
|
||||
actionServerSideOption: "invalid",
|
||||
releaseApplyMethod: "ssa",
|
||||
expectedErrorMsg: "invalid/unknown release server-side apply method: invalid",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range testsError {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, err := getUpgradeServerSideValue(tt.actionServerSideOption, tt.releaseApplyMethod)
|
||||
assert.ErrorContains(t, err, tt.expectedErrorMsg)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ import (
|
|||
"helm.sh/helm/v4/pkg/action"
|
||||
"helm.sh/helm/v4/pkg/cli/output"
|
||||
"helm.sh/helm/v4/pkg/cmd/require"
|
||||
|
||||
release "helm.sh/helm/v4/pkg/release/v1"
|
||||
)
|
||||
|
||||
type metadataWriter struct {
|
||||
|
|
@ -75,6 +77,20 @@ func newGetMetadataCmd(cfg *action.Configuration, out io.Writer) *cobra.Command
|
|||
}
|
||||
|
||||
func (w metadataWriter) WriteTable(out io.Writer) error {
|
||||
|
||||
formatApplyMethod := func(applyMethod string) string {
|
||||
switch applyMethod {
|
||||
case "":
|
||||
return "client-side apply (defaulted)"
|
||||
case string(release.ApplyMethodClientSideApply):
|
||||
return "client-side apply"
|
||||
case string(release.ApplyMethodServerSideApply):
|
||||
return "server-side apply"
|
||||
default:
|
||||
return fmt.Sprintf("unknown (%q)", applyMethod)
|
||||
}
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintf(out, "NAME: %v\n", w.metadata.Name)
|
||||
_, _ = fmt.Fprintf(out, "CHART: %v\n", w.metadata.Chart)
|
||||
_, _ = fmt.Fprintf(out, "VERSION: %v\n", w.metadata.Version)
|
||||
|
|
@ -86,6 +102,7 @@ func (w metadataWriter) WriteTable(out io.Writer) error {
|
|||
_, _ = fmt.Fprintf(out, "REVISION: %v\n", w.metadata.Revision)
|
||||
_, _ = fmt.Fprintf(out, "STATUS: %v\n", w.metadata.Status)
|
||||
_, _ = fmt.Fprintf(out, "DEPLOYED_AT: %v\n", w.metadata.DeployedAt)
|
||||
_, _ = fmt.Fprintf(out, "APPLY_METHOD: %v\n", formatApplyMethod(w.metadata.ApplyMethod))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -196,6 +196,8 @@ func addInstallFlags(cmd *cobra.Command, f *pflag.FlagSet, client *action.Instal
|
|||
f.BoolVar(&client.ForceReplace, "force-replace", false, "force resource updates by replacement")
|
||||
f.BoolVar(&client.ForceReplace, "force", false, "deprecated")
|
||||
f.MarkDeprecated("force", "use --force-replace instead")
|
||||
f.BoolVar(&client.ForceConflicts, "force-conflicts", false, "if set server-side apply will force changes against conflicts")
|
||||
f.BoolVar(&client.ServerSideApply, "server-side", true, "object updates run in the server instead of the client")
|
||||
f.BoolVar(&client.DisableHooks, "no-hooks", false, "prevent hooks from running during install")
|
||||
f.BoolVar(&client.Replace, "replace", false, "reuse the given name, only if that name is a deleted release which remains in the history. This is unsafe in production")
|
||||
f.DurationVar(&client.Timeout, "timeout", 300*time.Second, "time to wait for any individual Kubernetes operation (like Jobs for hooks)")
|
||||
|
|
@ -217,6 +219,8 @@ func addInstallFlags(cmd *cobra.Command, f *pflag.FlagSet, client *action.Instal
|
|||
addValueOptionsFlags(f, valueOpts)
|
||||
addChartPathOptionsFlags(f, &client.ChartPathOptions)
|
||||
AddWaitFlag(cmd, &client.WaitStrategy)
|
||||
cmd.MarkFlagsMutuallyExclusive("force-replace", "force-conflicts")
|
||||
cmd.MarkFlagsMutuallyExclusive("force", "force-conflicts")
|
||||
|
||||
err := cmd.RegisterFlagCompletionFunc("version", func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
requiredArgs := 2
|
||||
|
|
|
|||
|
|
@ -80,12 +80,16 @@ func newRollbackCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
|
|||
f.BoolVar(&client.ForceReplace, "force-replace", false, "force resource updates by replacement")
|
||||
f.BoolVar(&client.ForceReplace, "force", false, "deprecated")
|
||||
f.MarkDeprecated("force", "use --force-replace instead")
|
||||
f.BoolVar(&client.ForceConflicts, "force-conflicts", false, "if set server-side apply will force changes against conflicts")
|
||||
f.StringVar(&client.ServerSideApply, "server-side", "auto", "must be \"true\", \"false\" or \"auto\". Object updates run in the server instead of the client (\"auto\" defaults the value from the previous chart release's method)")
|
||||
f.BoolVar(&client.DisableHooks, "no-hooks", false, "prevent hooks from running during rollback")
|
||||
f.DurationVar(&client.Timeout, "timeout", 300*time.Second, "time to wait for any individual Kubernetes operation (like Jobs for hooks)")
|
||||
f.BoolVar(&client.WaitForJobs, "wait-for-jobs", false, "if set and --wait enabled, will wait until all Jobs have been completed before marking the release as successful. It will wait for as long as --timeout")
|
||||
f.BoolVar(&client.CleanupOnFail, "cleanup-on-fail", false, "allow deletion of new resources created in this rollback when rollback fails")
|
||||
f.IntVar(&client.MaxHistory, "history-max", settings.MaxHistory, "limit the maximum number of revisions saved per release. Use 0 for no limit")
|
||||
AddWaitFlag(cmd, &client.WaitStrategy)
|
||||
cmd.MarkFlagsMutuallyExclusive("force-replace", "force-conflicts")
|
||||
cmd.MarkFlagsMutuallyExclusive("force", "force-conflicts")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
|
|
|||
1
pkg/cmd/testdata/output/get-metadata.txt
vendored
1
pkg/cmd/testdata/output/get-metadata.txt
vendored
|
|
@ -9,3 +9,4 @@ NAMESPACE: default
|
|||
REVISION: 1
|
||||
STATUS: deployed
|
||||
DEPLOYED_AT: 1977-09-02T22:04:05Z
|
||||
APPLY_METHOD: client-side apply (defaulted)
|
||||
|
|
|
|||
|
|
@ -273,6 +273,8 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
|
|||
f.BoolVar(&client.ForceReplace, "force-replace", false, "force resource updates by replacement")
|
||||
f.BoolVar(&client.ForceReplace, "force", false, "deprecated")
|
||||
f.MarkDeprecated("force", "use --force-replace instead")
|
||||
f.BoolVar(&client.ForceConflicts, "force-conflicts", false, "if set server-side apply will force changes against conflicts")
|
||||
f.StringVar(&client.ServerSideApply, "server-side", "auto", "must be \"true\", \"false\" or \"auto\". Object updates run in the server instead of the client (\"auto\" defaults the value from the previous chart release's method)")
|
||||
f.BoolVar(&client.DisableHooks, "no-hooks", false, "disable pre/post upgrade hooks")
|
||||
f.BoolVar(&client.DisableOpenAPIValidation, "disable-openapi-validation", false, "if set, the upgrade process will not validate rendered templates against the Kubernetes OpenAPI Schema")
|
||||
f.BoolVar(&client.SkipCRDs, "skip-crds", false, "if set, no CRDs will be installed when an upgrade is performed with install flag enabled. By default, CRDs are installed if not already present, when an upgrade is performed with install flag enabled")
|
||||
|
|
@ -297,6 +299,8 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
|
|||
bindOutputFlag(cmd, &outfmt)
|
||||
bindPostRenderFlag(cmd, &client.PostRenderer)
|
||||
AddWaitFlag(cmd, &client.WaitStrategy)
|
||||
cmd.MarkFlagsMutuallyExclusive("force-replace", "force-conflicts")
|
||||
cmd.MarkFlagsMutuallyExclusive("force", "force-conflicts")
|
||||
|
||||
err := cmd.RegisterFlagCompletionFunc("version", func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
if len(args) != 2 {
|
||||
|
|
|
|||
|
|
@ -108,6 +108,14 @@ func (f *FailingKubeClient) Delete(resources kube.ResourceList) (*kube.Result, [
|
|||
return f.PrintingKubeClient.Delete(resources)
|
||||
}
|
||||
|
||||
// DeleteWithPropagationPolicy returns the configured error if set or prints
|
||||
func (f *FailingKubeClient) DeleteWithPropagationPolicy(resources kube.ResourceList, policy metav1.DeletionPropagation) (*kube.Result, []error) {
|
||||
if f.DeleteWithPropagationError != nil {
|
||||
return nil, []error{f.DeleteWithPropagationError}
|
||||
}
|
||||
return f.PrintingKubeClient.DeleteWithPropagationPolicy(resources, policy)
|
||||
}
|
||||
|
||||
// WatchUntilReady returns the configured error if set or prints
|
||||
func (f *FailingKubeWaiter) WatchUntilReady(resources kube.ResourceList, d time.Duration) error {
|
||||
if f.watchUntilReadyError != nil {
|
||||
|
|
@ -146,14 +154,6 @@ func (f *FailingKubeClient) BuildTable(r io.Reader, _ bool) (kube.ResourceList,
|
|||
return f.PrintingKubeClient.BuildTable(r, false)
|
||||
}
|
||||
|
||||
// DeleteWithPropagationPolicy returns the configured error if set or prints
|
||||
func (f *FailingKubeClient) DeleteWithPropagationPolicy(resources kube.ResourceList, policy metav1.DeletionPropagation) (*kube.Result, []error) {
|
||||
if f.DeleteWithPropagationError != nil {
|
||||
return nil, []error{f.DeleteWithPropagationError}
|
||||
}
|
||||
return f.PrintingKubeClient.DeleteWithPropagationPolicy(resources, policy)
|
||||
}
|
||||
|
||||
func (f *FailingKubeClient) GetWaiter(ws kube.WaitStrategy) (kube.Waiter, error) {
|
||||
waiter, _ := f.PrintingKubeClient.GetWaiter(ws)
|
||||
printingKubeWaiter, _ := waiter.(*PrintingKubeWaiter)
|
||||
|
|
|
|||
|
|
@ -97,6 +97,17 @@ func (p *PrintingKubeClient) Delete(resources kube.ResourceList) (*kube.Result,
|
|||
return &kube.Result{Deleted: resources}, nil
|
||||
}
|
||||
|
||||
// DeleteWithPropagationPolicy implements KubeClient delete.
|
||||
//
|
||||
// It only prints out the content to be deleted.
|
||||
func (p *PrintingKubeClient) DeleteWithPropagationPolicy(resources kube.ResourceList, _ metav1.DeletionPropagation) (*kube.Result, []error) {
|
||||
_, err := io.Copy(p.Out, bufferize(resources))
|
||||
if err != nil {
|
||||
return nil, []error{err}
|
||||
}
|
||||
return &kube.Result{Deleted: resources}, nil
|
||||
}
|
||||
|
||||
// Update implements KubeClient Update.
|
||||
func (p *PrintingKubeClient) Update(_, modified kube.ResourceList, _ ...kube.ClientUpdateOption) (*kube.Result, error) {
|
||||
_, err := io.Copy(p.Out, bufferize(modified))
|
||||
|
|
@ -135,17 +146,6 @@ func (p *PrintingKubeClient) OutputContainerLogsForPodList(_ *v1.PodList, someNa
|
|||
return err
|
||||
}
|
||||
|
||||
// DeleteWithPropagationPolicy implements KubeClient delete.
|
||||
//
|
||||
// It only prints out the content to be deleted.
|
||||
func (p *PrintingKubeClient) DeleteWithPropagationPolicy(resources kube.ResourceList, _ metav1.DeletionPropagation) (*kube.Result, []error) {
|
||||
_, err := io.Copy(p.Out, bufferize(resources))
|
||||
if err != nil {
|
||||
return nil, []error{err}
|
||||
}
|
||||
return &kube.Result{Deleted: resources}, nil
|
||||
}
|
||||
|
||||
func (p *PrintingKubeClient) GetWaiter(_ kube.WaitStrategy) (kube.Waiter, error) {
|
||||
return &PrintingKubeWaiter{Out: p.Out, LogOutput: p.LogOutput}, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,11 @@ import (
|
|||
chart "helm.sh/helm/v4/pkg/chart/v2"
|
||||
)
|
||||
|
||||
type ApplyMethod string
|
||||
|
||||
const ApplyMethodClientSideApply ApplyMethod = "csa"
|
||||
const ApplyMethodServerSideApply ApplyMethod = "ssa"
|
||||
|
||||
// Release describes a deployment of a chart, together with the chart
|
||||
// and the variables used to deploy that chart.
|
||||
type Release struct {
|
||||
|
|
@ -42,6 +47,9 @@ type Release struct {
|
|||
// Labels of the release.
|
||||
// Disabled encoding into Json cause labels are stored in storage driver metadata field.
|
||||
Labels map[string]string `json:"-"`
|
||||
// ApplyMethod stores whether server-side or client-side apply was used for the release
|
||||
// Unset (empty string) should be treated as the default of client-side apply
|
||||
ApplyMethod string `json:"apply_method,omitempty"` // "ssa" | "csa"
|
||||
}
|
||||
|
||||
// SetStatus is a helper for setting the status on a release.
|
||||
|
|
|
|||
Loading…
Reference in a new issue