pull/11650/merge
Jason Coté 7 months ago committed by GitHub
commit 2e197f4d62
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -191,6 +191,7 @@ func addInstallFlags(cmd *cobra.Command, f *pflag.FlagSet, client *action.Instal
f.StringVar(&client.DryRunOption, "dry-run", "", "simulate an install. If --dry-run is set with no option being specified or as '--dry-run=client', it will not attempt cluster connections. Setting '--dry-run=server' allows attempting cluster connections.") f.StringVar(&client.DryRunOption, "dry-run", "", "simulate an install. If --dry-run is set with no option being specified or as '--dry-run=client', it will not attempt cluster connections. Setting '--dry-run=server' allows attempting cluster connections.")
f.Lookup("dry-run").NoOptDefVal = "client" f.Lookup("dry-run").NoOptDefVal = "client"
f.BoolVar(&client.Force, "force", false, "force resource updates through a replacement strategy") f.BoolVar(&client.Force, "force", false, "force resource updates through a replacement strategy")
f.BoolVar(&client.Force3WayMergePatch, "force-3-way-merge-patch", false, "forces use of a 3-way merge patch in cases where a 2-way merge is still the default")
f.BoolVar(&client.DisableHooks, "no-hooks", false, "prevent hooks from running during install") 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.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)") f.DurationVar(&client.Timeout, "timeout", 300*time.Second, "time to wait for any individual Kubernetes operation (like Jobs for hooks)")

@ -79,6 +79,7 @@ func newRollbackCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
f.BoolVar(&client.DryRun, "dry-run", false, "simulate a rollback") f.BoolVar(&client.DryRun, "dry-run", false, "simulate a rollback")
f.BoolVar(&client.Recreate, "recreate-pods", false, "performs pods restart for the resource if applicable") f.BoolVar(&client.Recreate, "recreate-pods", false, "performs pods restart for the resource if applicable")
f.BoolVar(&client.Force, "force", false, "force resource update through delete/recreate if needed") f.BoolVar(&client.Force, "force", false, "force resource update through delete/recreate if needed")
f.BoolVar(&client.Force3WayMergePatch, "force-3-way-merge-patch", false, "forces use of a 3-way merge patch in cases where a 2-way merge is still the default")
f.BoolVar(&client.DisableHooks, "no-hooks", false, "prevent hooks from running during rollback") 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.DurationVar(&client.Timeout, "timeout", 300*time.Second, "time to wait for any individual Kubernetes operation (like Jobs for hooks)")
f.BoolVar(&client.Wait, "wait", false, "if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment, StatefulSet, or ReplicaSet are in a ready state before marking the release as successful. It will wait for as long as --timeout") f.BoolVar(&client.Wait, "wait", false, "if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment, StatefulSet, or ReplicaSet are in a ready state before marking the release as successful. It will wait for as long as --timeout")

@ -131,6 +131,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
instClient.CreateNamespace = createNamespace instClient.CreateNamespace = createNamespace
instClient.ChartPathOptions = client.ChartPathOptions instClient.ChartPathOptions = client.ChartPathOptions
instClient.Force = client.Force instClient.Force = client.Force
instClient.Force3WayMergePatch = client.Force3WayMergePatch
instClient.DryRun = client.DryRun instClient.DryRun = client.DryRun
instClient.DryRunOption = client.DryRunOption instClient.DryRunOption = client.DryRunOption
instClient.DisableHooks = client.DisableHooks instClient.DisableHooks = client.DisableHooks
@ -271,6 +272,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
f.BoolVar(&client.Recreate, "recreate-pods", false, "performs pods restart for the resource if applicable") f.BoolVar(&client.Recreate, "recreate-pods", false, "performs pods restart for the resource if applicable")
f.MarkDeprecated("recreate-pods", "functionality will no longer be updated. Consult the documentation for other methods to recreate pods") f.MarkDeprecated("recreate-pods", "functionality will no longer be updated. Consult the documentation for other methods to recreate pods")
f.BoolVar(&client.Force, "force", false, "force resource updates through a replacement strategy") f.BoolVar(&client.Force, "force", false, "force resource updates through a replacement strategy")
f.BoolVar(&client.Force3WayMergePatch, "force-3-way-merge-patch", false, "forces use of a 3-way merge patch in cases where a 2-way merge is still the default")
f.BoolVar(&client.DisableHooks, "no-hooks", false, "disable pre/post upgrade hooks") 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.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") 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")

@ -69,11 +69,12 @@ type Install struct {
ChartPathOptions ChartPathOptions
ClientOnly bool ClientOnly bool
Force bool Force bool
CreateNamespace bool Force3WayMergePatch bool
DryRun bool CreateNamespace bool
DryRunOption string DryRun bool
DryRunOption string
// HideSecret can be set to true when DryRun is enabled in order to hide // HideSecret can be set to true when DryRun is enabled in order to hide
// Kubernetes Secrets in the output. It cannot be used outside of DryRun. // Kubernetes Secrets in the output. It cannot be used outside of DryRun.
HideSecret bool HideSecret bool
@ -459,7 +460,7 @@ func (i *Install) performInstall(rel *release.Release, toBeAdopted kube.Resource
if len(toBeAdopted) == 0 && len(resources) > 0 { if len(toBeAdopted) == 0 && len(resources) > 0 {
_, err = i.cfg.KubeClient.Create(resources) _, err = i.cfg.KubeClient.Create(resources)
} else if len(resources) > 0 { } else if len(resources) > 0 {
_, err = i.cfg.KubeClient.Update(toBeAdopted, resources, i.Force) _, err = i.cfg.KubeClient.Update(toBeAdopted, resources, i.Force, i.Force3WayMergePatch)
} }
if err != nil { if err != nil {
return rel, err return rel, err

@ -35,16 +35,17 @@ import (
type Rollback struct { type Rollback struct {
cfg *Configuration cfg *Configuration
Version int Version int
Timeout time.Duration Timeout time.Duration
Wait bool Wait bool
WaitForJobs bool WaitForJobs bool
DisableHooks bool DisableHooks bool
DryRun bool DryRun bool
Recreate bool // will (if true) recreate pods after a rollback. Recreate bool // will (if true) recreate pods after a rollback.
Force bool // will (if true) force resource upgrade through uninstall/recreate if needed Force bool // will (if true) force resource upgrade through uninstall/recreate if needed
CleanupOnFail bool Force3WayMergePatch bool
MaxHistory int // MaxHistory limits the maximum number of revisions saved per release CleanupOnFail bool
MaxHistory int // MaxHistory limits the maximum number of revisions saved per release
} }
// NewRollback creates a new Rollback object with the given configuration. // NewRollback creates a new Rollback object with the given configuration.
@ -188,7 +189,7 @@ func (r *Rollback) performRollback(currentRelease, targetRelease *release.Releas
if err != nil { if err != nil {
return targetRelease, errors.Wrap(err, "unable to set metadata visitor from target release") return targetRelease, errors.Wrap(err, "unable to set metadata visitor from target release")
} }
results, err := r.cfg.KubeClient.Update(current, target, r.Force) results, err := r.cfg.KubeClient.Update(current, target, r.Force, r.Force3WayMergePatch)
if err != nil { if err != nil {
msg := fmt.Sprintf("Rollback %q failed: %s", targetRelease.Name, err) msg := fmt.Sprintf("Rollback %q failed: %s", targetRelease.Name, err)

@ -81,6 +81,8 @@ type Upgrade struct {
// //
// This should be used with caution. // This should be used with caution.
Force bool Force bool
// Forces use of a 3-way merge patch in cases where a 2-way merge is still the default
Force3WayMergePatch bool
// ResetValues will reset the values to the chart's built-ins rather than merging with existing. // ResetValues will reset the values to the chart's built-ins rather than merging with existing.
ResetValues bool ResetValues bool
// ReuseValues will reuse the user's last supplied values. // ReuseValues will reuse the user's last supplied values.
@ -426,7 +428,7 @@ func (u *Upgrade) releasingUpgrade(c chan<- resultMessage, upgradedRelease *rele
u.cfg.Log("upgrade hooks disabled for %s", upgradedRelease.Name) u.cfg.Log("upgrade hooks disabled for %s", upgradedRelease.Name)
} }
results, err := u.cfg.KubeClient.Update(current, target, u.Force) results, err := u.cfg.KubeClient.Update(current, target, u.Force, u.Force3WayMergePatch)
if err != nil { if err != nil {
u.cfg.recordRelease(originalRelease) u.cfg.recordRelease(originalRelease)
u.reportToPerformUpgrade(c, upgradedRelease, results.Created, err) u.reportToPerformUpgrade(c, upgradedRelease, results.Created, err)

@ -46,6 +46,7 @@ import (
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/jsonmergepatch"
"k8s.io/apimachinery/pkg/util/strategicpatch" "k8s.io/apimachinery/pkg/util/strategicpatch"
"k8s.io/apimachinery/pkg/watch" "k8s.io/apimachinery/pkg/watch"
"k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/cli-runtime/pkg/genericclioptions"
@ -386,7 +387,7 @@ func (c *Client) BuildTable(reader io.Reader, validate bool) (ResourceList, erro
// occurs, a Result will still be returned with the error, containing all // occurs, a Result will still be returned with the error, containing all
// resource updates, creations, and deletions that were attempted. These can be // resource updates, creations, and deletions that were attempted. These can be
// used for cleanup or other logging purposes. // used for cleanup or other logging purposes.
func (c *Client) Update(original, target ResourceList, force bool) (*Result, error) { func (c *Client) Update(original, target ResourceList, force, force3WayMergePatch bool) (*Result, error) {
updateErrors := []string{} updateErrors := []string{}
res := &Result{} res := &Result{}
@ -421,7 +422,7 @@ func (c *Client) Update(original, target ResourceList, force bool) (*Result, err
return errors.Errorf("no %s with the name %q found", kind, info.Name) return errors.Errorf("no %s with the name %q found", kind, info.Name)
} }
if err := updateResource(c, info, originalInfo.Object, force); err != nil { if err := updateResource(c, info, originalInfo.Object, force, force3WayMergePatch); err != nil {
c.Log("error updating the resource %q:\n\t %v", info.Name, err) c.Log("error updating the resource %q:\n\t %v", info.Name, err)
updateErrors = append(updateErrors, err.Error()) updateErrors = append(updateErrors, err.Error())
} }
@ -617,7 +618,7 @@ func deleteResource(info *resource.Info, policy metav1.DeletionPropagation) erro
}) })
} }
func createPatch(target *resource.Info, current runtime.Object) ([]byte, types.PatchType, error) { func createPatch(target *resource.Info, current runtime.Object, force3WayMergePatch bool) ([]byte, types.PatchType, error) {
oldData, err := json.Marshal(current) oldData, err := json.Marshal(current)
if err != nil { if err != nil {
return nil, types.StrategicMergePatchType, errors.Wrap(err, "serializing current configuration") return nil, types.StrategicMergePatchType, errors.Wrap(err, "serializing current configuration")
@ -645,7 +646,7 @@ func createPatch(target *resource.Info, current runtime.Object) ([]byte, types.P
// Unstructured objects, such as CRDs, may not have a not registered error // Unstructured objects, such as CRDs, may not have a not registered error
// returned from ConvertToVersion. Anything that's unstructured should // returned from ConvertToVersion. Anything that's unstructured should
// use the jsonpatch.CreateMergePatch. Strategic Merge Patch is not supported // use the jsonmergepatch.CreateThreeWayJSONMergePatch. Strategic Merge Patch is not supported
// on objects like CRDs. // on objects like CRDs.
_, isUnstructured := versionedObject.(runtime.Unstructured) _, isUnstructured := versionedObject.(runtime.Unstructured)
@ -654,6 +655,15 @@ func createPatch(target *resource.Info, current runtime.Object) ([]byte, types.P
if isUnstructured || isCRD { if isUnstructured || isCRD {
// fall back to generic JSON merge patch // fall back to generic JSON merge patch
// TODO Helm 4: the logic controlled by this flag should become the default.
// The flag can then be removed from all the commands where it is accepted: install, upgrade, & rollback.
if force3WayMergePatch {
patch, err := jsonmergepatch.CreateThreeWayJSONMergePatch(oldData, newData, currentData)
return patch, types.MergePatchType, err
}
// 2-way merge patch logic that is kept to maintain backwards-compatibility for Helm 3
patch, err := jsonpatch.CreateMergePatch(oldData, newData) patch, err := jsonpatch.CreateMergePatch(oldData, newData)
return patch, types.MergePatchType, err return patch, types.MergePatchType, err
} }
@ -667,7 +677,7 @@ func createPatch(target *resource.Info, current runtime.Object) ([]byte, types.P
return patch, types.StrategicMergePatchType, err return patch, types.StrategicMergePatchType, err
} }
func updateResource(c *Client, target *resource.Info, currentObj runtime.Object, force bool) error { func updateResource(c *Client, target *resource.Info, currentObj runtime.Object, force, force3WayMergePatch bool) error {
var ( var (
obj runtime.Object obj runtime.Object
helper = resource.NewHelper(target.Client, target.Mapping).WithFieldManager(getManagedFieldsManager()) helper = resource.NewHelper(target.Client, target.Mapping).WithFieldManager(getManagedFieldsManager())
@ -683,7 +693,7 @@ func updateResource(c *Client, target *resource.Info, currentObj runtime.Object,
} }
c.Log("Replaced %q with kind %s for kind %s", target.Name, currentObj.GetObjectKind().GroupVersionKind().Kind, kind) c.Log("Replaced %q with kind %s for kind %s", target.Name, currentObj.GetObjectKind().GroupVersionKind().Kind, kind)
} else { } else {
patch, patchType, err := createPatch(target, currentObj) patch, patchType, err := createPatch(target, currentObj, force3WayMergePatch)
if err != nil { if err != nil {
return errors.Wrap(err, "failed to create patch") return errors.Wrap(err, "failed to create patch")
} }

@ -279,7 +279,7 @@ func TestUpdate(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
result, err := c.Update(first, second, false) result, err := c.Update(first, second, false, false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

@ -105,11 +105,11 @@ func (f *FailingKubeClient) WatchUntilReady(resources kube.ResourceList, d time.
} }
// Update returns the configured error if set or prints // Update returns the configured error if set or prints
func (f *FailingKubeClient) Update(r, modified kube.ResourceList, ignoreMe bool) (*kube.Result, error) { func (f *FailingKubeClient) Update(r, modified kube.ResourceList, force, force3WayMergePatch bool) (*kube.Result, error) {
if f.UpdateError != nil { if f.UpdateError != nil {
return &kube.Result{}, f.UpdateError return &kube.Result{}, f.UpdateError
} }
return f.PrintingKubeClient.Update(r, modified, ignoreMe) return f.PrintingKubeClient.Update(r, modified, force, force3WayMergePatch)
} }
// Build returns the configured error if set or prints // Build returns the configured error if set or prints

@ -92,7 +92,7 @@ func (p *PrintingKubeClient) WatchUntilReady(resources kube.ResourceList, _ time
} }
// Update implements KubeClient Update. // Update implements KubeClient Update.
func (p *PrintingKubeClient) Update(_, modified kube.ResourceList, _ bool) (*kube.Result, error) { func (p *PrintingKubeClient) Update(_, modified kube.ResourceList, _, _ bool) (*kube.Result, error) {
_, err := io.Copy(p.Out, bufferize(modified)) _, err := io.Copy(p.Out, bufferize(modified))
if err != nil { if err != nil {
return nil, err return nil, err

@ -54,7 +54,7 @@ type Interface interface {
// Update updates one or more resources or creates the resource // Update updates one or more resources or creates the resource
// if it doesn't exist. // if it doesn't exist.
Update(original, target ResourceList, force bool) (*Result, error) Update(original, target ResourceList, force, force3WayMergePatch bool) (*Result, error)
// Build creates a resource list from a Reader. // Build creates a resource list from a Reader.
// //

Loading…
Cancel
Save