Skip to content

Commit 67f6398

Browse files
authored
pkg/client: add history method to ActionClient (#397)
In order to give users of this library more information about the state of a release, we are adding the `History` method to the `ActionClient` interface. Without this, the only information is available via the `ActionClient` about a release is the latest revision (via the `Get` method). However this proves insufficient if callers need to know details of release history, for example if an upgrade failed and callers want to know about the most recently deployed release. Signed-off-by: Joe Lanford <[email protected]>
1 parent 4b2b362 commit 67f6398

File tree

3 files changed

+107
-0
lines changed

3 files changed

+107
-0
lines changed

pkg/client/actionclient.go

+30
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
"helm.sh/helm/v3/pkg/chart"
2929
helmkube "helm.sh/helm/v3/pkg/kube"
3030
"helm.sh/helm/v3/pkg/release"
31+
"helm.sh/helm/v3/pkg/releaseutil"
3132
"helm.sh/helm/v3/pkg/storage/driver"
3233
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
3334
apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
@@ -52,13 +53,15 @@ func (acgf ActionClientGetterFunc) ActionClientFor(ctx context.Context, obj clie
5253

5354
type ActionInterface interface {
5455
Get(name string, opts ...GetOption) (*release.Release, error)
56+
History(name string, opts ...HistoryOption) ([]*release.Release, error)
5557
Install(name, namespace string, chrt *chart.Chart, vals map[string]interface{}, opts ...InstallOption) (*release.Release, error)
5658
Upgrade(name, namespace string, chrt *chart.Chart, vals map[string]interface{}, opts ...UpgradeOption) (*release.Release, error)
5759
Uninstall(name string, opts ...UninstallOption) (*release.UninstallReleaseResponse, error)
5860
Reconcile(rel *release.Release) error
5961
}
6062

6163
type GetOption func(*action.Get) error
64+
type HistoryOption func(*action.History) error
6265
type InstallOption func(*action.Install) error
6366
type UpgradeOption func(*action.Upgrade) error
6467
type UninstallOption func(*action.Uninstall) error
@@ -73,6 +76,13 @@ func AppendGetOptions(opts ...GetOption) ActionClientGetterOption {
7376
}
7477
}
7578

79+
func AppendHistoryOptions(opts ...HistoryOption) ActionClientGetterOption {
80+
return func(getter *actionClientGetter) error {
81+
getter.defaultHistoryOpts = append(getter.defaultHistoryOpts, opts...)
82+
return nil
83+
}
84+
}
85+
7686
func AppendInstallOptions(opts ...InstallOption) ActionClientGetterOption {
7787
return func(getter *actionClientGetter) error {
7888
getter.defaultInstallOpts = append(getter.defaultInstallOpts, opts...)
@@ -139,6 +149,7 @@ type actionClientGetter struct {
139149
acg ActionConfigGetter
140150

141151
defaultGetOpts []GetOption
152+
defaultHistoryOpts []HistoryOption
142153
defaultInstallOpts []InstallOption
143154
defaultUpgradeOpts []UpgradeOption
144155
defaultUninstallOpts []UninstallOption
@@ -174,6 +185,7 @@ func (hcg *actionClientGetter) ActionClientFor(ctx context.Context, obj client.O
174185
// on purpose because we want user-provided defaults to be able to override the
175186
// post-renderer that we automatically configure for the client.
176187
defaultGetOpts: hcg.defaultGetOpts,
188+
defaultHistoryOpts: hcg.defaultHistoryOpts,
177189
defaultInstallOpts: append([]InstallOption{WithInstallPostRenderer(cpr)}, hcg.defaultInstallOpts...),
178190
defaultUpgradeOpts: append([]UpgradeOption{WithUpgradePostRenderer(cpr)}, hcg.defaultUpgradeOpts...),
179191
defaultUninstallOpts: hcg.defaultUninstallOpts,
@@ -188,6 +200,7 @@ type actionClient struct {
188200
conf *action.Configuration
189201

190202
defaultGetOpts []GetOption
203+
defaultHistoryOpts []HistoryOption
191204
defaultInstallOpts []InstallOption
192205
defaultUpgradeOpts []UpgradeOption
193206
defaultUninstallOpts []UninstallOption
@@ -209,6 +222,23 @@ func (c *actionClient) Get(name string, opts ...GetOption) (*release.Release, er
209222
return get.Run(name)
210223
}
211224

225+
// History returns the release history for a given release name. The releases are sorted
226+
// by revision number in descending order.
227+
func (c *actionClient) History(name string, opts ...HistoryOption) ([]*release.Release, error) {
228+
history := action.NewHistory(c.conf)
229+
for _, o := range concat(c.defaultHistoryOpts, opts...) {
230+
if err := o(history); err != nil {
231+
return nil, err
232+
}
233+
}
234+
rels, err := history.Run(name)
235+
if err != nil {
236+
return nil, err
237+
}
238+
releaseutil.Reverse(rels, releaseutil.SortByRevision)
239+
return rels, nil
240+
}
241+
212242
func (c *actionClient) Install(name, namespace string, chrt *chart.Chart, vals map[string]interface{}, opts ...InstallOption) (*release.Release, error) {
213243
install := action.NewInstall(c.conf)
214244
for _, o := range concat(c.defaultInstallOpts, opts...) {

pkg/client/actionclient_test.go

+60
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626

2727
. "github.com/onsi/ginkgo/v2"
2828
. "github.com/onsi/gomega"
29+
2930
"helm.sh/helm/v3/pkg/action"
3031
"helm.sh/helm/v3/pkg/chartutil"
3132
"helm.sh/helm/v3/pkg/kube"
@@ -121,6 +122,28 @@ var _ = Describe("ActionClient", func() {
121122
_, err = ac.Get(obj.GetName())
122123
Expect(err).To(MatchError(expectErr))
123124
})
125+
It("should get clients with custom history options", func() {
126+
expectMax := rand.Int()
127+
acg, err := NewActionClientGetter(actionConfigGetter, AppendHistoryOptions(
128+
func(history *action.History) error {
129+
history.Max = expectMax
130+
return nil
131+
},
132+
func(history *action.History) error {
133+
Expect(history.Max).To(Equal(expectMax))
134+
return expectErr
135+
},
136+
))
137+
Expect(err).ToNot(HaveOccurred())
138+
Expect(acg).NotTo(BeNil())
139+
140+
ac, err := acg.ActionClientFor(context.Background(), obj)
141+
Expect(err).ToNot(HaveOccurred())
142+
Expect(ac).NotTo(BeNil())
143+
144+
_, err = ac.History(obj.GetName())
145+
Expect(err).To(MatchError(expectErr))
146+
})
124147
It("should get clients with custom install options", func() {
125148
acg, err := NewActionClientGetter(actionConfigGetter, AppendInstallOptions(
126149
func(install *action.Install) error {
@@ -359,6 +382,13 @@ var _ = Describe("ActionClient", func() {
359382
panic(err)
360383
}
361384
})
385+
var _ = Describe("History", func() {
386+
It("should return a not found error", func() {
387+
rels, err := ac.History(obj.GetName())
388+
Expect(err).To(MatchError(driver.ErrReleaseNotFound))
389+
Expect(rels).To(BeNil())
390+
})
391+
})
362392
var _ = Describe("Install", func() {
363393
It("should succeed", func() {
364394
var (
@@ -482,6 +512,36 @@ var _ = Describe("ActionClient", func() {
482512
})
483513
})
484514
})
515+
var _ = Describe("History", func() {
516+
When("one revision exists", func() {
517+
It("should return a slice of releases of length 1", func() {
518+
rels, err := ac.History(obj.GetName())
519+
Expect(err).ToNot(HaveOccurred())
520+
Expect(rels).To(HaveLen(1))
521+
Expect(rels[0].Name).To(Equal(obj.GetName()))
522+
Expect(rels[0].Version).To(Equal(1))
523+
Expect(rels[0].Info.Status).To(Equal(release.StatusDeployed))
524+
})
525+
})
526+
When("multiple revisions exist", func() {
527+
BeforeEach(func() {
528+
// Upgrade the release to create a new revision.
529+
_, err := ac.Upgrade(obj.GetName(), obj.GetNamespace(), &chrt, vals)
530+
Expect(err).ToNot(HaveOccurred())
531+
})
532+
It("should return a slice of releases of length 2", func() {
533+
rels, err := ac.History(obj.GetName())
534+
Expect(err).ToNot(HaveOccurred())
535+
Expect(rels).To(HaveLen(2))
536+
Expect(rels[0].Name).To(Equal(obj.GetName()))
537+
Expect(rels[0].Version).To(Equal(2))
538+
Expect(rels[0].Info.Status).To(Equal(release.StatusDeployed))
539+
Expect(rels[1].Name).To(Equal(obj.GetName()))
540+
Expect(rels[1].Version).To(Equal(1))
541+
Expect(rels[1].Info.Status).To(Equal(release.StatusSuperseded))
542+
})
543+
})
544+
})
485545
var _ = Describe("Install", func() {
486546
It("should fail", func() {
487547
r, err := ac.Install(obj.GetName(), obj.GetNamespace(), &chrt, vals)

pkg/reconciler/internal/fake/actionclient.go

+17
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,14 @@ func (hcg *fakeActionClientGetter) ActionClientFor(_ context.Context, _ crclient
5050

5151
type ActionClient struct {
5252
Gets []GetCall
53+
Histories []HistoryCall
5354
Installs []InstallCall
5455
Upgrades []UpgradeCall
5556
Uninstalls []UninstallCall
5657
Reconciles []ReconcileCall
5758

5859
HandleGet func() (*release.Release, error)
60+
HandleHistory func() ([]*release.Release, error)
5961
HandleInstall func() (*release.Release, error)
6062
HandleUpgrade func() (*release.Release, error)
6163
HandleUninstall func() (*release.UninstallReleaseResponse, error)
@@ -66,6 +68,9 @@ func NewActionClient() ActionClient {
6668
relFunc := func(err error) func() (*release.Release, error) {
6769
return func() (*release.Release, error) { return nil, err }
6870
}
71+
historyFunc := func(err error) func() ([]*release.Release, error) {
72+
return func() ([]*release.Release, error) { return nil, err }
73+
}
6974
uninstFunc := func(err error) func() (*release.UninstallReleaseResponse, error) {
7075
return func() (*release.UninstallReleaseResponse, error) { return nil, err }
7176
}
@@ -74,12 +79,14 @@ func NewActionClient() ActionClient {
7479
}
7580
return ActionClient{
7681
Gets: make([]GetCall, 0),
82+
Histories: make([]HistoryCall, 0),
7783
Installs: make([]InstallCall, 0),
7884
Upgrades: make([]UpgradeCall, 0),
7985
Uninstalls: make([]UninstallCall, 0),
8086
Reconciles: make([]ReconcileCall, 0),
8187

8288
HandleGet: relFunc(errors.New("get not implemented")),
89+
HandleHistory: historyFunc(errors.New("history not implemented")),
8390
HandleInstall: relFunc(errors.New("install not implemented")),
8491
HandleUpgrade: relFunc(errors.New("upgrade not implemented")),
8592
HandleUninstall: uninstFunc(errors.New("uninstall not implemented")),
@@ -94,6 +101,11 @@ type GetCall struct {
94101
Opts []client.GetOption
95102
}
96103

104+
type HistoryCall struct {
105+
Name string
106+
Opts []client.HistoryOption
107+
}
108+
97109
type InstallCall struct {
98110
Name string
99111
Namespace string
@@ -124,6 +136,11 @@ func (c *ActionClient) Get(name string, opts ...client.GetOption) (*release.Rele
124136
return c.HandleGet()
125137
}
126138

139+
func (c *ActionClient) History(name string, opts ...client.HistoryOption) ([]*release.Release, error) {
140+
c.Histories = append(c.Histories, HistoryCall{name, opts})
141+
return c.HandleHistory()
142+
}
143+
127144
func (c *ActionClient) Install(name, namespace string, chrt *chart.Chart, vals map[string]interface{}, opts ...client.InstallOption) (*release.Release, error) {
128145
c.Installs = append(c.Installs, InstallCall{name, namespace, chrt, vals, opts})
129146
return c.HandleInstall()

0 commit comments

Comments
 (0)