Skip to content

Commit 994b1d5

Browse files
authored
ActionClientGetter: allow disabling rollbacks after failures (#348)
When an install fails, we uninstall the failed release. When an upgrade fails, we rollback to the previous release. This commit makes it possible to disable that behavior, which results in the latest release being left in a Failed state for the caller to decide how to act.
1 parent 2c77136 commit 994b1d5

File tree

2 files changed

+68
-16
lines changed

2 files changed

+68
-16
lines changed

pkg/client/actionclient.go

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,18 @@ func AppendPostRenderers(postRendererFns ...PostRendererProvider) ActionClientGe
115115
}
116116
}
117117

118+
func WithFailureRollbacks(enableFailureRollbacks bool) ActionClientGetterOption {
119+
return func(getter *actionClientGetter) error {
120+
getter.enableFailureRollbacks = enableFailureRollbacks
121+
return nil
122+
}
123+
}
124+
118125
func NewActionClientGetter(acg ActionConfigGetter, opts ...ActionClientGetterOption) (ActionClientGetter, error) {
119-
actionClientGetter := &actionClientGetter{acg: acg}
126+
actionClientGetter := &actionClientGetter{
127+
acg: acg,
128+
enableFailureRollbacks: true,
129+
}
120130
for _, opt := range opts {
121131
if err := opt(actionClientGetter); err != nil {
122132
return nil, err
@@ -133,6 +143,7 @@ type actionClientGetter struct {
133143
defaultUpgradeOpts []UpgradeOption
134144
defaultUninstallOpts []UninstallOption
135145

146+
enableFailureRollbacks bool
136147
installFailureUninstallOpts []UninstallOption
137148
upgradeFailureRollbackOpts []RollbackOption
138149

@@ -167,6 +178,7 @@ func (hcg *actionClientGetter) ActionClientFor(ctx context.Context, obj client.O
167178
defaultUpgradeOpts: append([]UpgradeOption{WithUpgradePostRenderer(cpr)}, hcg.defaultUpgradeOpts...),
168179
defaultUninstallOpts: hcg.defaultUninstallOpts,
169180

181+
enableFailureRollbacks: hcg.enableFailureRollbacks,
170182
installFailureUninstallOpts: hcg.installFailureUninstallOpts,
171183
upgradeFailureRollbackOpts: hcg.upgradeFailureRollbackOpts,
172184
}, nil
@@ -180,6 +192,7 @@ type actionClient struct {
180192
defaultUpgradeOpts []UpgradeOption
181193
defaultUninstallOpts []UninstallOption
182194

195+
enableFailureRollbacks bool
183196
installFailureUninstallOpts []UninstallOption
184197
upgradeFailureRollbackOpts []RollbackOption
185198
}
@@ -209,7 +222,7 @@ func (c *actionClient) Install(name, namespace string, chrt *chart.Chart, vals m
209222
rel, err := install.Run(chrt, vals)
210223
if err != nil {
211224
c.conf.Log("Install failed")
212-
if rel != nil {
225+
if c.enableFailureRollbacks && rel != nil {
213226
// Uninstall the failed release installation so that we can retry
214227
// the installation again during the next reconciliation. In many
215228
// cases, the issue is unresolvable without a change to the CR, but
@@ -228,7 +241,7 @@ func (c *actionClient) Install(name, namespace string, chrt *chart.Chart, vals m
228241
return nil, fmt.Errorf("uninstall failed: %v: original install error: %w", uninstallErr, err)
229242
}
230243
}
231-
return nil, err
244+
return rel, err
232245
}
233246
return rel, nil
234247
}
@@ -243,7 +256,7 @@ func (c *actionClient) Upgrade(name, namespace string, chrt *chart.Chart, vals m
243256
upgrade.Namespace = namespace
244257
rel, err := upgrade.Run(name, chrt, vals)
245258
if err != nil {
246-
if rel != nil {
259+
if c.enableFailureRollbacks && rel != nil {
247260
rollbackOpts := append([]RollbackOption{func(rollback *action.Rollback) error {
248261
rollback.Force = true
249262
rollback.MaxHistory = upgrade.MaxHistory
@@ -260,7 +273,7 @@ func (c *actionClient) Upgrade(name, namespace string, chrt *chart.Chart, vals m
260273
return nil, fmt.Errorf("rollback failed: %v: original upgrade error: %w", rollbackErr, err)
261274
}
262275
}
263-
return nil, err
276+
return rel, err
264277
}
265278
return rel, nil
266279
}

pkg/client/actionclient_test.go

Lines changed: 50 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -322,17 +322,19 @@ var _ = Describe("ActionClient", func() {
322322

323323
var _ = Describe("ActionClient methods", func() {
324324
var (
325-
obj client.Object
326-
cl client.Client
327-
ac ActionInterface
328-
vals = chartutil.Values{"service": map[string]interface{}{"type": "NodePort"}}
325+
obj client.Object
326+
cl client.Client
327+
actionCfgGetter ActionConfigGetter
328+
ac ActionInterface
329+
vals = chartutil.Values{"service": map[string]interface{}{"type": "NodePort"}}
329330
)
330331
BeforeEach(func() {
331332
obj = testutil.BuildTestCR(gvk)
332333

333-
actionConfigGetter, err := NewActionConfigGetter(cfg, rm)
334+
var err error
335+
actionCfgGetter, err = NewActionConfigGetter(cfg, rm)
334336
Expect(err).ShouldNot(HaveOccurred())
335-
acg, err := NewActionClientGetter(actionConfigGetter)
337+
acg, err := NewActionClientGetter(actionCfgGetter)
336338
Expect(err).ToNot(HaveOccurred())
337339
ac, err = acg.ActionClientFor(context.Background(), obj)
338340
Expect(err).ToNot(HaveOccurred())
@@ -373,14 +375,32 @@ var _ = Describe("ActionClient", func() {
373375
})
374376
It("should uninstall a failed install", func() {
375377
By("failing to install the release", func() {
376-
chrt := testutil.MustLoadChart("../../pkg/internal/testdata/test-chart-1.2.0.tgz")
377-
chrt.Templates[2].Data = append(chrt.Templates[2].Data, []byte("\ngibberish")...)
378+
vals := chartutil.Values{"service": map[string]interface{}{"type": "FooBar"}}
378379
r, err := ac.Install(obj.GetName(), obj.GetNamespace(), &chrt, vals)
379380
Expect(err).To(HaveOccurred())
380-
Expect(r).To(BeNil())
381+
Expect(r).NotTo(BeNil())
381382
})
382383
verifyNoRelease(cl, obj.GetNamespace(), obj.GetName(), nil)
383384
})
385+
When("failure uninstall is disabled", func() {
386+
BeforeEach(func() {
387+
acg, err := NewActionClientGetter(actionCfgGetter, WithFailureRollbacks(false))
388+
Expect(err).ToNot(HaveOccurred())
389+
ac, err = acg.ActionClientFor(context.Background(), obj)
390+
Expect(err).ToNot(HaveOccurred())
391+
})
392+
It("should not uninstall a failed install", func() {
393+
vals := chartutil.Values{"service": map[string]interface{}{"type": "FooBar"}}
394+
returnedRelease, err := ac.Install(obj.GetName(), obj.GetNamespace(), &chrt, vals)
395+
Expect(err).To(HaveOccurred())
396+
Expect(returnedRelease).ToNot(BeNil())
397+
Expect(returnedRelease.Info.Status).To(Equal(release.StatusFailed))
398+
latestRelease, err := ac.Get(obj.GetName())
399+
Expect(err).ToNot(HaveOccurred())
400+
Expect(latestRelease).ToNot(BeNil())
401+
Expect(latestRelease.Version).To(Equal(returnedRelease.Version))
402+
})
403+
})
384404
When("using an option function that returns an error", func() {
385405
It("should fail", func() {
386406
opt := func(*action.Install) error { return errors.New("expect this error") }
@@ -484,17 +504,36 @@ var _ = Describe("ActionClient", func() {
484504
verifyRelease(cl, obj, rel)
485505
})
486506
It("should rollback a failed upgrade", func() {
487-
By("failing to install the release", func() {
507+
By("failing to upgrade the release", func() {
488508
vals := chartutil.Values{"service": map[string]interface{}{"type": "FooBar"}}
489509
r, err := ac.Upgrade(obj.GetName(), obj.GetNamespace(), &chrt, vals)
490510
Expect(err).To(HaveOccurred())
491-
Expect(r).To(BeNil())
511+
Expect(r).ToNot(BeNil())
492512
})
493513
tmp := *installedRelease
494514
rollbackRelease := &tmp
495515
rollbackRelease.Version = installedRelease.Version + 2
496516
verifyRelease(cl, obj, rollbackRelease)
497517
})
518+
When("failure rollback is disabled", func() {
519+
BeforeEach(func() {
520+
acg, err := NewActionClientGetter(actionCfgGetter, WithFailureRollbacks(false))
521+
Expect(err).ToNot(HaveOccurred())
522+
ac, err = acg.ActionClientFor(context.Background(), obj)
523+
Expect(err).ToNot(HaveOccurred())
524+
})
525+
It("should not rollback a failed upgrade", func() {
526+
vals := chartutil.Values{"service": map[string]interface{}{"type": "FooBar"}}
527+
returnedRelease, err := ac.Upgrade(obj.GetName(), obj.GetNamespace(), &chrt, vals)
528+
Expect(err).To(HaveOccurred())
529+
Expect(returnedRelease).ToNot(BeNil())
530+
Expect(returnedRelease.Info.Status).To(Equal(release.StatusFailed))
531+
latestRelease, err := ac.Get(obj.GetName())
532+
Expect(err).ToNot(HaveOccurred())
533+
Expect(latestRelease).ToNot(BeNil())
534+
Expect(latestRelease.Version).To(Equal(returnedRelease.Version))
535+
})
536+
})
498537
When("using an option function that returns an error", func() {
499538
It("should fail", func() {
500539
opt := func(*action.Upgrade) error { return errors.New("expect this error") }

0 commit comments

Comments
 (0)