diff --git a/pkg/client/actionclient.go b/pkg/client/actionclient.go index 5cba3dd5..130219d9 100644 --- a/pkg/client/actionclient.go +++ b/pkg/client/actionclient.go @@ -28,6 +28,7 @@ import ( "helm.sh/helm/v3/pkg/chart" helmkube "helm.sh/helm/v3/pkg/kube" "helm.sh/helm/v3/pkg/release" + "helm.sh/helm/v3/pkg/releaseutil" "helm.sh/helm/v3/pkg/storage/driver" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" @@ -52,6 +53,7 @@ func (acgf ActionClientGetterFunc) ActionClientFor(ctx context.Context, obj clie type ActionInterface interface { Get(name string, opts ...GetOption) (*release.Release, error) + History(name string, opts ...HistoryOption) ([]*release.Release, error) Install(name, namespace string, chrt *chart.Chart, vals map[string]interface{}, opts ...InstallOption) (*release.Release, error) Upgrade(name, namespace string, chrt *chart.Chart, vals map[string]interface{}, opts ...UpgradeOption) (*release.Release, error) Uninstall(name string, opts ...UninstallOption) (*release.UninstallReleaseResponse, error) @@ -59,6 +61,7 @@ type ActionInterface interface { } type GetOption func(*action.Get) error +type HistoryOption func(*action.History) error type InstallOption func(*action.Install) error type UpgradeOption func(*action.Upgrade) error type UninstallOption func(*action.Uninstall) error @@ -73,6 +76,13 @@ func AppendGetOptions(opts ...GetOption) ActionClientGetterOption { } } +func AppendHistoryOptions(opts ...HistoryOption) ActionClientGetterOption { + return func(getter *actionClientGetter) error { + getter.defaultHistoryOpts = append(getter.defaultHistoryOpts, opts...) + return nil + } +} + func AppendInstallOptions(opts ...InstallOption) ActionClientGetterOption { return func(getter *actionClientGetter) error { getter.defaultInstallOpts = append(getter.defaultInstallOpts, opts...) @@ -139,6 +149,7 @@ type actionClientGetter struct { acg ActionConfigGetter defaultGetOpts []GetOption + defaultHistoryOpts []HistoryOption defaultInstallOpts []InstallOption defaultUpgradeOpts []UpgradeOption defaultUninstallOpts []UninstallOption @@ -174,6 +185,7 @@ func (hcg *actionClientGetter) ActionClientFor(ctx context.Context, obj client.O // on purpose because we want user-provided defaults to be able to override the // post-renderer that we automatically configure for the client. defaultGetOpts: hcg.defaultGetOpts, + defaultHistoryOpts: hcg.defaultHistoryOpts, defaultInstallOpts: append([]InstallOption{WithInstallPostRenderer(cpr)}, hcg.defaultInstallOpts...), defaultUpgradeOpts: append([]UpgradeOption{WithUpgradePostRenderer(cpr)}, hcg.defaultUpgradeOpts...), defaultUninstallOpts: hcg.defaultUninstallOpts, @@ -188,6 +200,7 @@ type actionClient struct { conf *action.Configuration defaultGetOpts []GetOption + defaultHistoryOpts []HistoryOption defaultInstallOpts []InstallOption defaultUpgradeOpts []UpgradeOption defaultUninstallOpts []UninstallOption @@ -209,6 +222,23 @@ func (c *actionClient) Get(name string, opts ...GetOption) (*release.Release, er return get.Run(name) } +// History returns the release history for a given release name. The releases are sorted +// by revision number in descending order. +func (c *actionClient) History(name string, opts ...HistoryOption) ([]*release.Release, error) { + history := action.NewHistory(c.conf) + for _, o := range concat(c.defaultHistoryOpts, opts...) { + if err := o(history); err != nil { + return nil, err + } + } + rels, err := history.Run(name) + if err != nil { + return nil, err + } + releaseutil.Reverse(rels, releaseutil.SortByRevision) + return rels, nil +} + func (c *actionClient) Install(name, namespace string, chrt *chart.Chart, vals map[string]interface{}, opts ...InstallOption) (*release.Release, error) { install := action.NewInstall(c.conf) for _, o := range concat(c.defaultInstallOpts, opts...) { diff --git a/pkg/client/actionclient_test.go b/pkg/client/actionclient_test.go index c6411f86..727d3d1b 100644 --- a/pkg/client/actionclient_test.go +++ b/pkg/client/actionclient_test.go @@ -26,6 +26,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/chartutil" "helm.sh/helm/v3/pkg/kube" @@ -121,6 +122,28 @@ var _ = Describe("ActionClient", func() { _, err = ac.Get(obj.GetName()) Expect(err).To(MatchError(expectErr)) }) + It("should get clients with custom history options", func() { + expectMax := rand.Int() + acg, err := NewActionClientGetter(actionConfigGetter, AppendHistoryOptions( + func(history *action.History) error { + history.Max = expectMax + return nil + }, + func(history *action.History) error { + Expect(history.Max).To(Equal(expectMax)) + return expectErr + }, + )) + Expect(err).ToNot(HaveOccurred()) + Expect(acg).NotTo(BeNil()) + + ac, err := acg.ActionClientFor(context.Background(), obj) + Expect(err).ToNot(HaveOccurred()) + Expect(ac).NotTo(BeNil()) + + _, err = ac.History(obj.GetName()) + Expect(err).To(MatchError(expectErr)) + }) It("should get clients with custom install options", func() { acg, err := NewActionClientGetter(actionConfigGetter, AppendInstallOptions( func(install *action.Install) error { @@ -359,6 +382,13 @@ var _ = Describe("ActionClient", func() { panic(err) } }) + var _ = Describe("History", func() { + It("should return a not found error", func() { + rels, err := ac.History(obj.GetName()) + Expect(err).To(MatchError(driver.ErrReleaseNotFound)) + Expect(rels).To(BeNil()) + }) + }) var _ = Describe("Install", func() { It("should succeed", func() { var ( @@ -482,6 +512,36 @@ var _ = Describe("ActionClient", func() { }) }) }) + var _ = Describe("History", func() { + When("one revision exists", func() { + It("should return a slice of releases of length 1", func() { + rels, err := ac.History(obj.GetName()) + Expect(err).ToNot(HaveOccurred()) + Expect(rels).To(HaveLen(1)) + Expect(rels[0].Name).To(Equal(obj.GetName())) + Expect(rels[0].Version).To(Equal(1)) + Expect(rels[0].Info.Status).To(Equal(release.StatusDeployed)) + }) + }) + When("multiple revisions exist", func() { + BeforeEach(func() { + // Upgrade the release to create a new revision. + _, err := ac.Upgrade(obj.GetName(), obj.GetNamespace(), &chrt, vals) + Expect(err).ToNot(HaveOccurred()) + }) + It("should return a slice of releases of length 2", func() { + rels, err := ac.History(obj.GetName()) + Expect(err).ToNot(HaveOccurred()) + Expect(rels).To(HaveLen(2)) + Expect(rels[0].Name).To(Equal(obj.GetName())) + Expect(rels[0].Version).To(Equal(2)) + Expect(rels[0].Info.Status).To(Equal(release.StatusDeployed)) + Expect(rels[1].Name).To(Equal(obj.GetName())) + Expect(rels[1].Version).To(Equal(1)) + Expect(rels[1].Info.Status).To(Equal(release.StatusSuperseded)) + }) + }) + }) var _ = Describe("Install", func() { It("should fail", func() { r, err := ac.Install(obj.GetName(), obj.GetNamespace(), &chrt, vals) diff --git a/pkg/reconciler/internal/fake/actionclient.go b/pkg/reconciler/internal/fake/actionclient.go index e35cb3f4..162cdc1d 100644 --- a/pkg/reconciler/internal/fake/actionclient.go +++ b/pkg/reconciler/internal/fake/actionclient.go @@ -50,12 +50,14 @@ func (hcg *fakeActionClientGetter) ActionClientFor(_ context.Context, _ crclient type ActionClient struct { Gets []GetCall + Histories []HistoryCall Installs []InstallCall Upgrades []UpgradeCall Uninstalls []UninstallCall Reconciles []ReconcileCall HandleGet func() (*release.Release, error) + HandleHistory func() ([]*release.Release, error) HandleInstall func() (*release.Release, error) HandleUpgrade func() (*release.Release, error) HandleUninstall func() (*release.UninstallReleaseResponse, error) @@ -66,6 +68,9 @@ func NewActionClient() ActionClient { relFunc := func(err error) func() (*release.Release, error) { return func() (*release.Release, error) { return nil, err } } + historyFunc := func(err error) func() ([]*release.Release, error) { + return func() ([]*release.Release, error) { return nil, err } + } uninstFunc := func(err error) func() (*release.UninstallReleaseResponse, error) { return func() (*release.UninstallReleaseResponse, error) { return nil, err } } @@ -74,12 +79,14 @@ func NewActionClient() ActionClient { } return ActionClient{ Gets: make([]GetCall, 0), + Histories: make([]HistoryCall, 0), Installs: make([]InstallCall, 0), Upgrades: make([]UpgradeCall, 0), Uninstalls: make([]UninstallCall, 0), Reconciles: make([]ReconcileCall, 0), HandleGet: relFunc(errors.New("get not implemented")), + HandleHistory: historyFunc(errors.New("history not implemented")), HandleInstall: relFunc(errors.New("install not implemented")), HandleUpgrade: relFunc(errors.New("upgrade not implemented")), HandleUninstall: uninstFunc(errors.New("uninstall not implemented")), @@ -94,6 +101,11 @@ type GetCall struct { Opts []client.GetOption } +type HistoryCall struct { + Name string + Opts []client.HistoryOption +} + type InstallCall struct { Name string Namespace string @@ -124,6 +136,11 @@ func (c *ActionClient) Get(name string, opts ...client.GetOption) (*release.Rele return c.HandleGet() } +func (c *ActionClient) History(name string, opts ...client.HistoryOption) ([]*release.Release, error) { + c.Histories = append(c.Histories, HistoryCall{name, opts}) + return c.HandleHistory() +} + func (c *ActionClient) Install(name, namespace string, chrt *chart.Chart, vals map[string]interface{}, opts ...client.InstallOption) (*release.Release, error) { c.Installs = append(c.Installs, InstallCall{name, namespace, chrt, vals, opts}) return c.HandleInstall()