pull/12518/merge
Jakub Domanski 1 year ago committed by GitHub
commit 1c2817c66f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -85,6 +85,7 @@ func newRollbackCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
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")
f.BoolVar(&client.RecreateImmutableResources, "recreate-immutable-resources", false, "On status code 422, try to delete and create the resource again")
return cmd
}

@ -78,6 +78,11 @@ func TestRollbackCmd(t *testing.T) {
golden: "output/rollback-no-args.txt",
rels: rels,
wantError: true,
}, {
name: "rollback a release with recreate-immutable-resources option ",
cmd: "rollback funny-honey 1 --recreate-immutable-resources",
golden: "output/rollback.txt",
rels: rels,
}}
runTestCmd(t, tests)
}

@ -252,6 +252,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.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.RecreateImmutableResources, "recreate-immutable-resources", false, "On status code 422, try to delete and create the resource again")
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")

@ -175,6 +175,12 @@ func TestUpgradeCmd(t *testing.T) {
wantError: true,
rels: []*release.Release{relWithStatusMock("funny-bunny", 2, ch, release.StatusPendingInstall)},
},
{
name: "upgrade a release with --recreate-immutable-resources flag",
cmd: fmt.Sprintf("upgrade funny-bunny '%s' --recreate-immutable-resources", chartPath),
golden: "output/upgrade.txt",
rels: []*release.Release{relMock("funny-bunny", 2, ch)},
},
}
runTestCmd(t, tests)
}

@ -447,7 +447,7 @@ func (i *Install) performInstall(rel *release.Release, toBeAdopted kube.Resource
if len(toBeAdopted) == 0 && len(resources) > 0 {
_, err = i.cfg.KubeClient.Create(resources)
} else if len(resources) > 0 {
_, err = i.cfg.KubeClient.Update(toBeAdopted, resources, i.Force)
_, err = i.cfg.KubeClient.Update(toBeAdopted, resources, i.Force, false)
}
if err != nil {
return rel, err

@ -35,16 +35,17 @@ import (
type Rollback struct {
cfg *Configuration
Version int
Timeout time.Duration
Wait bool
WaitForJobs bool
DisableHooks bool
DryRun bool
Recreate bool // will (if true) recreate pods after a rollback.
Force bool // will (if true) force resource upgrade through uninstall/recreate if needed
CleanupOnFail bool
MaxHistory int // MaxHistory limits the maximum number of revisions saved per release
Version int
Timeout time.Duration
Wait bool
WaitForJobs bool
DisableHooks bool
DryRun bool
Recreate bool // will (if true) recreate pods after a rollback.
Force bool // will (if true) force resource upgrade through uninstall/recreate if needed
CleanupOnFail bool
MaxHistory int // MaxHistory limits the maximum number of revisions saved per release
RecreateImmutableResources bool // will (if true) try to delete and recreate immutable resources on 422 status code
}
// 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 {
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.RecreateImmutableResources)
if err != nil {
msg := fmt.Sprintf("Rollback %q failed: %s", targetRelease.Name, err)

@ -81,6 +81,8 @@ type Upgrade struct {
//
// This should be used with caution.
Force bool
// Recreate resources will try to delete and create resources ONLY during upgrade if it receives statusError with code 422
RecreateImmutableResources bool
// ResetValues will reset the values to the chart's built-ins rather than merging with existing.
ResetValues bool
// ReuseValues will re-use the user's last supplied values.
@ -415,7 +417,7 @@ func (u *Upgrade) releasingUpgrade(c chan<- resultMessage, upgradedRelease *rele
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.RecreateImmutableResources)
if err != nil {
u.cfg.recordRelease(originalRelease)
u.reportToPerformUpgrade(c, upgradedRelease, results.Created, err)
@ -521,6 +523,7 @@ func (u *Upgrade) failRelease(rel *release.Release, created kube.ResourceList, e
rollin.Recreate = u.Recreate
rollin.Force = u.Force
rollin.Timeout = u.Timeout
rollin.RecreateImmutableResources = u.RecreateImmutableResources
if rollErr := rollin.Run(rel.Name); rollErr != nil {
return rel, errors.Wrapf(rollErr, "an error occurred while rolling back the release. original upgrade error: %s", err)
}

@ -386,7 +386,7 @@ func (c *Client) BuildTable(reader io.Reader, validate bool) (ResourceList, erro
// occurs, a Result will still be returned with the error, containing all
// resource updates, creations, and deletions that were attempted. These can be
// 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 bool, recreateImmutableResources bool) (*Result, error) {
updateErrors := []string{}
res := &Result{}
@ -422,8 +422,31 @@ func (c *Client) Update(original, target ResourceList, force bool) (*Result, err
}
if err := updateResource(c, info, originalInfo.Object, force); err != nil {
c.Log("error updating the resource %q:\n\t %v", info.Name, err)
updateErrors = append(updateErrors, err.Error())
var errorMessage error
if force {
errorMessage = errors.Wrap(err, "failed to replace object")
} else {
errorMessage = errors.Wrapf(err, "cannot patch %q with kind %s", info.Name, info.Mapping.GroupVersionKind.Kind)
}
if statusError, isStatusError := err.(*apierrors.StatusError); recreateImmutableResources && isStatusError && statusError.ErrStatus.Code == 422 {
if err := deleteResource(info, metav1.DeletePropagationBackground); err != nil {
c.Log("Failed to delete %q, err: %s", info.ObjectName(), err)
} else {
if err := createResource(info); err != nil {
errorMessage = errors.Wrap(err, "failed to create resource")
} else {
errorMessage = nil
}
}
}
if errorMessage != nil {
c.Log("error updating the resource or replacing object %q:\n\t %v", info.Name, errorMessage)
updateErrors = append(updateErrors, err.Error())
}
}
// Because we check for errors later, append the info regardless
res.Updated = append(res.Updated, info)
@ -671,7 +694,8 @@ func updateResource(c *Client, target *resource.Info, currentObj runtime.Object,
var err error
obj, err = helper.Replace(target.Namespace, target.Name, true, target.Object)
if err != nil {
return errors.Wrap(err, "failed to replace object")
//errors.Wrap(err, "failed to replace object")
return err
}
c.Log("Replaced %q with kind %s for kind %s", target.Name, currentObj.GetObjectKind().GroupVersionKind().Kind, kind)
} else {
@ -693,7 +717,8 @@ func updateResource(c *Client, target *resource.Info, currentObj runtime.Object,
c.Log("Patch %s %q in namespace %s", kind, target.Name, target.Namespace)
obj, err = helper.Patch(target.Namespace, target.Name, patchType, patch, nil)
if err != nil {
return errors.Wrapf(err, "cannot patch %q with kind %s", target.Name, kind)
//return errors.Wrapf(err, "cannot patch %q with kind %s", target.Name, kind)
return err
}
}

@ -101,11 +101,14 @@ func newTestClient(t *testing.T) *Client {
}
func TestUpdate(t *testing.T) {
listA := newPodList("starfish", "otter", "squid")
listB := newPodList("starfish", "otter", "dolphin")
listC := newPodList("starfish", "otter", "dolphin")
listA := newPodList("starfish", "otter", "squid", "shark")
listB := newPodList("starfish", "otter", "dolphin", "shark")
listC := newPodList("starfish", "otter", "dolphin", "shark")
listB.Items[0].Spec.Containers[0].Ports = []v1.ContainerPort{{Name: "https", ContainerPort: 443}}
listC.Items[0].Spec.Containers[0].Ports = []v1.ContainerPort{{Name: "https", ContainerPort: 443}}
// Labels change is prohibited as this is immutable field
listB.Items[3].Labels = map[string]string{"changed": "changed"}
listC.Items[3].Labels = map[string]string{"changed": "changed"}
var actions []string
@ -146,11 +149,30 @@ func TestUpdate(t *testing.T) {
}
return newResponse(200, &listB.Items[0])
case p == "/namespaces/default/pods" && m == "POST":
return newResponse(200, &listB.Items[1])
data, err := io.ReadAll(req.Body)
if err != nil {
t.Fatalf("could not dump request: %s", err)
}
req.Body.Close()
var returnElement *v1.Pod
if strings.Contains(string(data), "dolphin") {
returnElement = &listB.Items[2]
} else if strings.Contains(string(data), "shark") {
returnElement = &listB.Items[3]
} else {
t.Fatalf("Creation of new pods with unknown name: %s", string(data))
}
return newResponse(200, returnElement)
case p == "/namespaces/default/pods/squid" && m == "DELETE":
return newResponse(200, &listB.Items[1])
case p == "/namespaces/default/pods/squid" && m == "GET":
return newResponse(200, &listB.Items[2])
case p == "/namespaces/default/pods/shark" && (m == "PUT" || m == "PATCH"):
return newResponse(422, &listA.Items[3])
case p == "/namespaces/default/pods/shark" && (m == "DELETE" || m == "POST" || m == "GET"):
return newResponse(200, &listA.Items[3])
default:
t.Fatalf("unexpected request: %s %s", req.Method, req.URL.Path)
return nil, nil
@ -166,7 +188,7 @@ func TestUpdate(t *testing.T) {
t.Fatal(err)
}
result, err := c.Update(first, second, false)
result, err := c.Update(first, second, false, true)
if err != nil {
t.Fatal(err)
}
@ -174,7 +196,7 @@ func TestUpdate(t *testing.T) {
if len(result.Created) != 1 {
t.Errorf("expected 1 resource created, got %d", len(result.Created))
}
if len(result.Updated) != 2 {
if len(result.Updated) != 3 {
t.Errorf("expected 2 resource updated, got %d", len(result.Updated))
}
if len(result.Deleted) != 1 {
@ -200,6 +222,11 @@ func TestUpdate(t *testing.T) {
"/namespaces/default/pods/otter:GET",
"/namespaces/default/pods/dolphin:GET",
"/namespaces/default/pods:POST",
"/namespaces/default/pods/shark:GET",
"/namespaces/default/pods/shark:GET",
"/namespaces/default/pods/shark:PATCH",
"/namespaces/default/pods/shark:DELETE",
"/namespaces/default/pods:POST",
"/namespaces/default/pods/squid:GET",
"/namespaces/default/pods/squid:DELETE",
}

@ -107,11 +107,11 @@ func (f *FailingKubeClient) WatchUntilReady(resources kube.ResourceList, d time.
}
// 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, ignoreMe bool, alsoIgnoreMe bool) (*kube.Result, error) {
if f.UpdateError != nil {
return &kube.Result{}, f.UpdateError
}
return f.PrintingKubeClient.Update(r, modified, ignoreMe)
return f.PrintingKubeClient.Update(r, modified, ignoreMe, alsoIgnoreMe)
}
// Build returns the configured error if set or prints

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

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

Loading…
Cancel
Save