Skip to content

Commit e688971

Browse files
authored
Add various functional options to ActionConfigGetter and ActionClientGetter constructors (#196)
1 parent 1375ea2 commit e688971

File tree

5 files changed

+472
-27
lines changed

5 files changed

+472
-27
lines changed

pkg/client/actionclient.go

Lines changed: 97 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,71 @@ type GetOption func(*action.Get) error
6161
type InstallOption func(*action.Install) error
6262
type UpgradeOption func(*action.Upgrade) error
6363
type UninstallOption func(*action.Uninstall) error
64+
type RollbackOption func(*action.Rollback) error
6465

65-
func NewActionClientGetter(acg ActionConfigGetter) ActionClientGetter {
66-
return &actionClientGetter{acg}
66+
type ActionClientGetterOption func(*actionClientGetter) error
67+
68+
func AppendGetOptions(opts ...GetOption) ActionClientGetterOption {
69+
return func(getter *actionClientGetter) error {
70+
getter.defaultGetOpts = append(getter.defaultGetOpts, opts...)
71+
return nil
72+
}
73+
}
74+
75+
func AppendInstallOptions(opts ...InstallOption) ActionClientGetterOption {
76+
return func(getter *actionClientGetter) error {
77+
getter.defaultInstallOpts = append(getter.defaultInstallOpts, opts...)
78+
return nil
79+
}
80+
}
81+
82+
func AppendUpgradeOptions(opts ...UpgradeOption) ActionClientGetterOption {
83+
return func(getter *actionClientGetter) error {
84+
getter.defaultUpgradeOpts = append(getter.defaultUpgradeOpts, opts...)
85+
return nil
86+
}
87+
}
88+
89+
func AppendUninstallOptions(opts ...UninstallOption) ActionClientGetterOption {
90+
return func(getter *actionClientGetter) error {
91+
getter.defaultUninstallOpts = append(getter.defaultUninstallOpts, opts...)
92+
return nil
93+
}
94+
}
95+
96+
func AppendInstallFailureUninstallOptions(opts ...UninstallOption) ActionClientGetterOption {
97+
return func(getter *actionClientGetter) error {
98+
getter.installFailureUninstallOpts = append(getter.installFailureUninstallOpts, opts...)
99+
return nil
100+
}
101+
}
102+
func AppendUpgradeFailureRollbackOptions(opts ...RollbackOption) ActionClientGetterOption {
103+
return func(getter *actionClientGetter) error {
104+
getter.upgradeFailureRollbackOpts = append(getter.upgradeFailureRollbackOpts, opts...)
105+
return nil
106+
}
107+
}
108+
109+
func NewActionClientGetter(acg ActionConfigGetter, opts ...ActionClientGetterOption) (ActionClientGetter, error) {
110+
actionClientGetter := &actionClientGetter{acg: acg}
111+
for _, opt := range opts {
112+
if err := opt(actionClientGetter); err != nil {
113+
return nil, err
114+
}
115+
}
116+
return actionClientGetter, nil
67117
}
68118

69119
type actionClientGetter struct {
70120
acg ActionConfigGetter
121+
122+
defaultGetOpts []GetOption
123+
defaultInstallOpts []InstallOption
124+
defaultUpgradeOpts []UpgradeOption
125+
defaultUninstallOpts []UninstallOption
126+
127+
installFailureUninstallOpts []UninstallOption
128+
upgradeFailureRollbackOpts []RollbackOption
71129
}
72130

73131
var _ ActionClientGetter = &actionClientGetter{}
@@ -83,9 +141,18 @@ func (hcg *actionClientGetter) ActionClientFor(obj client.Object) (ActionInterfa
83141
}
84142
postRenderer := DefaultPostRendererFunc(rm, actionConfig.KubeClient, obj)
85143
return &actionClient{
86-
conf: actionConfig,
87-
defaultInstallOpts: []InstallOption{WithInstallPostRenderer(postRenderer)},
88-
defaultUpgradeOpts: []UpgradeOption{WithUpgradePostRenderer(postRenderer)},
144+
conf: actionConfig,
145+
146+
// For the install and upgrade options, we put the post renderer first in the list
147+
// on purpose because we want user-provided defaults to be able to override the
148+
// post-renderer that we automatically configure for the client.
149+
defaultGetOpts: hcg.defaultGetOpts,
150+
defaultInstallOpts: append([]InstallOption{WithInstallPostRenderer(postRenderer)}, hcg.defaultInstallOpts...),
151+
defaultUpgradeOpts: append([]UpgradeOption{WithUpgradePostRenderer(postRenderer)}, hcg.defaultUpgradeOpts...),
152+
defaultUninstallOpts: hcg.defaultUninstallOpts,
153+
154+
installFailureUninstallOpts: hcg.installFailureUninstallOpts,
155+
upgradeFailureRollbackOpts: hcg.upgradeFailureRollbackOpts,
89156
}, nil
90157
}
91158

@@ -96,6 +163,9 @@ type actionClient struct {
96163
defaultInstallOpts []InstallOption
97164
defaultUpgradeOpts []UpgradeOption
98165
defaultUninstallOpts []UninstallOption
166+
167+
installFailureUninstallOpts []UninstallOption
168+
upgradeFailureRollbackOpts []RollbackOption
99169
}
100170

101171
var _ ActionInterface = &actionClient{}
@@ -137,7 +207,7 @@ func (c *actionClient) Install(name, namespace string, chrt *chart.Chart, vals m
137207
//
138208
// Only return an error about a rollback failure if the failure was
139209
// caused by something other than the release not being found.
140-
_, uninstallErr := c.Uninstall(name)
210+
_, uninstallErr := c.uninstall(name, c.installFailureUninstallOpts...)
141211
if uninstallErr != nil && !errors.Is(uninstallErr, driver.ErrReleaseNotFound) {
142212
return nil, fmt.Errorf("uninstall failed: %v: original install error: %w", uninstallErr, err)
143213
}
@@ -158,16 +228,18 @@ func (c *actionClient) Upgrade(name, namespace string, chrt *chart.Chart, vals m
158228
rel, err := upgrade.Run(name, chrt, vals)
159229
if err != nil {
160230
if rel != nil {
161-
rollback := action.NewRollback(c.conf)
162-
rollback.Force = true
163-
rollback.MaxHistory = upgrade.MaxHistory
231+
rollbackOpts := append([]RollbackOption{func(rollback *action.Rollback) error {
232+
rollback.Force = true
233+
rollback.MaxHistory = upgrade.MaxHistory
234+
return nil
235+
}}, c.upgradeFailureRollbackOpts...)
164236

165237
// As of Helm 2.13, if Upgrade returns a non-nil release, that
166238
// means the release was also recorded in the release store.
167239
// Therefore, we should perform the rollback when we have a non-nil
168240
// release. Any rollback error here would be unexpected, so always
169241
// log both the update and rollback errors.
170-
rollbackErr := rollback.Run(name)
242+
rollbackErr := c.rollback(name, rollbackOpts...)
171243
if rollbackErr != nil {
172244
return nil, fmt.Errorf("rollback failed: %v: original upgrade error: %w", rollbackErr, err)
173245
}
@@ -177,9 +249,23 @@ func (c *actionClient) Upgrade(name, namespace string, chrt *chart.Chart, vals m
177249
return rel, nil
178250
}
179251

252+
func (c *actionClient) rollback(name string, opts ...RollbackOption) error {
253+
rollback := action.NewRollback(c.conf)
254+
for _, o := range opts {
255+
if err := o(rollback); err != nil {
256+
return err
257+
}
258+
}
259+
return rollback.Run(name)
260+
}
261+
180262
func (c *actionClient) Uninstall(name string, opts ...UninstallOption) (*release.UninstallReleaseResponse, error) {
263+
return c.uninstall(name, concat(c.defaultUninstallOpts, opts...)...)
264+
}
265+
266+
func (c *actionClient) uninstall(name string, opts ...UninstallOption) (*release.UninstallReleaseResponse, error) {
181267
uninstall := action.NewUninstall(c.conf)
182-
for _, o := range concat(c.defaultUninstallOpts, opts...) {
268+
for _, o := range opts {
183269
if err := o(uninstall); err != nil {
184270
return nil, err
185271
}

pkg/client/actionclient_test.go

Lines changed: 178 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"context"
2121
"errors"
2222
"strconv"
23+
"time"
2324

2425
"github.com/go-logr/logr"
2526
. "github.com/onsi/ginkgo/v2"
@@ -37,6 +38,7 @@ import (
3738
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
3839
"k8s.io/apimachinery/pkg/runtime/schema"
3940
apitypes "k8s.io/apimachinery/pkg/types"
41+
"k8s.io/apimachinery/pkg/util/rand"
4042
"k8s.io/cli-runtime/pkg/resource"
4143
"k8s.io/utils/pointer"
4244
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -61,7 +63,178 @@ var _ = Describe("ActionClient", func() {
6163
It("should return a valid ActionConfigGetter", func() {
6264
actionConfigGetter, err := NewActionConfigGetter(cfg, rm, logr.Discard())
6365
Expect(err).ShouldNot(HaveOccurred())
64-
Expect(NewActionClientGetter(actionConfigGetter)).NotTo(BeNil())
66+
acg, err := NewActionClientGetter(actionConfigGetter)
67+
Expect(err).To(BeNil())
68+
Expect(acg).NotTo(BeNil())
69+
})
70+
71+
When("options are specified", func() {
72+
expectErr := errors.New("expect this error")
73+
74+
var (
75+
actionConfigGetter ActionConfigGetter
76+
obj client.Object
77+
)
78+
BeforeEach(func() {
79+
var err error
80+
actionConfigGetter, err = NewActionConfigGetter(cfg, rm, logr.Discard())
81+
Expect(err).ShouldNot(HaveOccurred())
82+
obj = testutil.BuildTestCR(gvk)
83+
})
84+
85+
It("should get clients with custom get options", func() {
86+
expectVersion := rand.Int()
87+
acg, err := NewActionClientGetter(actionConfigGetter, AppendGetOptions(
88+
func(get *action.Get) error {
89+
get.Version = expectVersion
90+
return nil
91+
},
92+
func(get *action.Get) error {
93+
Expect(get.Version).To(Equal(expectVersion))
94+
return expectErr
95+
},
96+
))
97+
Expect(err).To(BeNil())
98+
Expect(acg).NotTo(BeNil())
99+
100+
ac, err := acg.ActionClientFor(obj)
101+
Expect(err).To(BeNil())
102+
Expect(ac).NotTo(BeNil())
103+
104+
_, err = ac.Get(obj.GetName())
105+
Expect(err).To(MatchError(expectErr))
106+
})
107+
It("should get clients with custom install options", func() {
108+
acg, err := NewActionClientGetter(actionConfigGetter, AppendInstallOptions(
109+
func(install *action.Install) error {
110+
install.Description = mockTestDesc
111+
return nil
112+
},
113+
func(install *action.Install) error {
114+
Expect(install.Description).To(Equal(mockTestDesc))
115+
return expectErr
116+
},
117+
))
118+
Expect(err).To(BeNil())
119+
Expect(acg).NotTo(BeNil())
120+
121+
ac, err := acg.ActionClientFor(obj)
122+
Expect(err).To(BeNil())
123+
Expect(ac).NotTo(BeNil())
124+
125+
_, err = ac.Install(obj.GetName(), obj.GetNamespace(), &chrt, chartutil.Values{})
126+
Expect(err).To(MatchError(expectErr))
127+
})
128+
It("should get clients with custom upgrade options", func() {
129+
acg, err := NewActionClientGetter(actionConfigGetter, AppendUpgradeOptions(
130+
func(upgrade *action.Upgrade) error {
131+
upgrade.Description = mockTestDesc
132+
return nil
133+
},
134+
func(upgrade *action.Upgrade) error {
135+
Expect(upgrade.Description).To(Equal(mockTestDesc))
136+
return expectErr
137+
},
138+
))
139+
Expect(err).To(BeNil())
140+
Expect(acg).NotTo(BeNil())
141+
142+
ac, err := acg.ActionClientFor(obj)
143+
Expect(err).To(BeNil())
144+
Expect(ac).NotTo(BeNil())
145+
146+
_, err = ac.Upgrade(obj.GetName(), obj.GetNamespace(), &chrt, chartutil.Values{})
147+
Expect(err).To(MatchError(expectErr))
148+
})
149+
It("should get clients with custom uninstall options", func() {
150+
acg, err := NewActionClientGetter(actionConfigGetter, AppendUninstallOptions(
151+
func(uninstall *action.Uninstall) error {
152+
uninstall.Description = mockTestDesc
153+
return nil
154+
},
155+
func(uninstall *action.Uninstall) error {
156+
Expect(uninstall.Description).To(Equal(mockTestDesc))
157+
return expectErr
158+
},
159+
))
160+
Expect(err).To(BeNil())
161+
Expect(acg).NotTo(BeNil())
162+
163+
ac, err := acg.ActionClientFor(obj)
164+
Expect(err).To(BeNil())
165+
Expect(ac).NotTo(BeNil())
166+
167+
_, err = ac.Uninstall(obj.GetName())
168+
Expect(err).To(MatchError(expectErr))
169+
})
170+
It("should get clients with custom install failure uninstall options", func() {
171+
acg, err := NewActionClientGetter(actionConfigGetter, AppendInstallFailureUninstallOptions(
172+
func(uninstall *action.Uninstall) error {
173+
uninstall.Description = mockTestDesc
174+
return nil
175+
},
176+
func(uninstall *action.Uninstall) error {
177+
Expect(uninstall.Description).To(Equal(mockTestDesc))
178+
return expectErr
179+
},
180+
))
181+
Expect(err).To(BeNil())
182+
Expect(acg).NotTo(BeNil())
183+
184+
ac, err := acg.ActionClientFor(obj)
185+
Expect(err).To(BeNil())
186+
Expect(ac).NotTo(BeNil())
187+
188+
_, err = ac.Install(obj.GetName(), obj.GetNamespace(), &chrt, chartutil.Values{}, func(install *action.Install) error {
189+
// Force the installatiom to fail by using an impossibly short wait.
190+
// When the installation fails, the failure uninstall logic is attempted.
191+
install.Wait = true
192+
install.Timeout = time.Nanosecond * 1
193+
return nil
194+
})
195+
Expect(err).To(MatchError(ContainSubstring(expectErr.Error())))
196+
197+
// Uninstall the chart to cleanup for other tests.
198+
_, err = ac.Uninstall(obj.GetName())
199+
Expect(err).To(BeNil())
200+
})
201+
It("should get clients with custom upgrade failure rollback options", func() {
202+
expectMaxHistory := rand.Int()
203+
acg, err := NewActionClientGetter(actionConfigGetter, AppendUpgradeFailureRollbackOptions(
204+
func(rollback *action.Rollback) error {
205+
rollback.MaxHistory = expectMaxHistory
206+
return nil
207+
},
208+
func(rollback *action.Rollback) error {
209+
Expect(rollback.MaxHistory).To(Equal(expectMaxHistory))
210+
return expectErr
211+
},
212+
))
213+
Expect(err).To(BeNil())
214+
Expect(acg).NotTo(BeNil())
215+
216+
ac, err := acg.ActionClientFor(obj)
217+
Expect(err).To(BeNil())
218+
Expect(ac).NotTo(BeNil())
219+
220+
// Install the chart so that we can try an upgrade.
221+
rel, err := ac.Install(obj.GetName(), obj.GetNamespace(), &chrt, chartutil.Values{})
222+
Expect(err).To(BeNil())
223+
Expect(rel).NotTo(BeNil())
224+
225+
_, err = ac.Upgrade(obj.GetName(), obj.GetNamespace(), &chrt, chartutil.Values{}, func(upgrade *action.Upgrade) error {
226+
// Force the upgrade to fail by using an impossibly short wait.
227+
// When the upgrade fails, the rollback logic is attempted.
228+
upgrade.Wait = true
229+
upgrade.Timeout = time.Nanosecond * 1
230+
return nil
231+
})
232+
Expect(err).To(MatchError(ContainSubstring(expectErr.Error())))
233+
234+
// Uninstall the chart to cleanup for other tests.
235+
_, err = ac.Uninstall(obj.GetName())
236+
Expect(err).To(BeNil())
237+
})
65238
})
66239
})
67240

@@ -88,7 +261,8 @@ var _ = Describe("ActionClient", func() {
88261
It("should return a valid ActionClient", func() {
89262
actionConfGetter, err := NewActionConfigGetter(cfg, rm, logr.Discard())
90263
Expect(err).ShouldNot(HaveOccurred())
91-
acg := NewActionClientGetter(actionConfGetter)
264+
acg, err := NewActionClientGetter(actionConfGetter)
265+
Expect(err).To(BeNil())
92266
ac, err := acg.ActionClientFor(obj)
93267
Expect(err).To(BeNil())
94268
Expect(ac).NotTo(BeNil())
@@ -107,7 +281,8 @@ var _ = Describe("ActionClient", func() {
107281

108282
actionConfigGetter, err := NewActionConfigGetter(cfg, rm, logr.Discard())
109283
Expect(err).ShouldNot(HaveOccurred())
110-
acg := NewActionClientGetter(actionConfigGetter)
284+
acg, err := NewActionClientGetter(actionConfigGetter)
285+
Expect(err).To(BeNil())
111286
ac, err = acg.ActionClientFor(obj)
112287
Expect(err).To(BeNil())
113288

0 commit comments

Comments
 (0)