From 2a0b49597aef6a3df4c864a84891dec6a530eb3a Mon Sep 17 00:00:00 2001 From: Ludovic Cleroux Date: Tue, 11 Jul 2023 13:57:17 +0200 Subject: [PATCH 1/6] Allow user-provided post-renderers --- pkg/client/actionclient.go | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/pkg/client/actionclient.go b/pkg/client/actionclient.go index ce8429aa..7a67d0b3 100644 --- a/pkg/client/actionclient.go +++ b/pkg/client/actionclient.go @@ -25,12 +25,15 @@ import ( "gomodules.xyz/jsonpatch/v2" "helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/chart" + "helm.sh/helm/v3/pkg/kube" helmkube "helm.sh/helm/v3/pkg/kube" + "helm.sh/helm/v3/pkg/postrender" "helm.sh/helm/v3/pkg/release" "helm.sh/helm/v3/pkg/storage/driver" apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" apitypes "k8s.io/apimachinery/pkg/types" @@ -99,6 +102,7 @@ func AppendInstallFailureUninstallOptions(opts ...UninstallOption) ActionClientG return nil } } + func AppendUpgradeFailureRollbackOptions(opts ...RollbackOption) ActionClientGetterOption { return func(getter *actionClientGetter) error { getter.upgradeFailureRollbackOpts = append(getter.upgradeFailureRollbackOpts, opts...) @@ -106,6 +110,15 @@ func AppendUpgradeFailureRollbackOptions(opts ...RollbackOption) ActionClientGet } } +type PostRendererProvider func(rm meta.RESTMapper, kubeClient kube.Interface, obj client.Object) postrender.PostRenderer + +func AppendPostRenderers(postRendererFns ...PostRendererProvider) ActionClientGetterOption { + return func(getter *actionClientGetter) error { + getter.postRendererProviders = append(getter.postRendererProviders, postRendererFns...) + return nil + } +} + func NewActionClientGetter(acg ActionConfigGetter, opts ...ActionClientGetterOption) (ActionClientGetter, error) { actionClientGetter := &actionClientGetter{acg: acg} for _, opt := range opts { @@ -126,6 +139,8 @@ type actionClientGetter struct { installFailureUninstallOpts []UninstallOption upgradeFailureRollbackOpts []RollbackOption + + postRendererProviders []PostRendererProvider } var _ ActionClientGetter = &actionClientGetter{} @@ -139,7 +154,13 @@ func (hcg *actionClientGetter) ActionClientFor(obj client.Object) (ActionInterfa if err != nil { return nil, err } - postRenderer := DefaultPostRendererFunc(rm, actionConfig.KubeClient, obj) + var chainedPostRenderer = chainedPostRenderer{ + DefaultPostRendererFunc(rm, actionConfig.KubeClient, obj), + } + for _, provider := range hcg.postRendererProviders { + chainedPostRenderer = append(chainedPostRenderer, provider(rm, actionConfig.KubeClient, obj)) + } + return &actionClient{ conf: actionConfig, @@ -147,8 +168,8 @@ func (hcg *actionClientGetter) ActionClientFor(obj client.Object) (ActionInterfa // 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, - defaultInstallOpts: append([]InstallOption{WithInstallPostRenderer(postRenderer)}, hcg.defaultInstallOpts...), - defaultUpgradeOpts: append([]UpgradeOption{WithUpgradePostRenderer(postRenderer)}, hcg.defaultUpgradeOpts...), + defaultInstallOpts: append([]InstallOption{WithInstallPostRenderer(chainedPostRenderer)}, hcg.defaultInstallOpts...), + defaultUpgradeOpts: append([]UpgradeOption{WithUpgradePostRenderer(chainedPostRenderer)}, hcg.defaultUpgradeOpts...), defaultUninstallOpts: hcg.defaultUninstallOpts, installFailureUninstallOpts: hcg.installFailureUninstallOpts, From 8ac34c09ec03b26109876e59cc6c4d485968a1b7 Mon Sep 17 00:00:00 2001 From: Ludovic Cleroux Date: Fri, 28 Jul 2023 15:21:15 +0200 Subject: [PATCH 2/6] PR Comments --- pkg/client/actionclient.go | 16 ++--- pkg/client/actionclient_test.go | 102 ++++++++++++++++++++++++++++++++ pkg/client/postrenderer.go | 4 ++ 3 files changed, 111 insertions(+), 11 deletions(-) diff --git a/pkg/client/actionclient.go b/pkg/client/actionclient.go index 7a67d0b3..f1421b58 100644 --- a/pkg/client/actionclient.go +++ b/pkg/client/actionclient.go @@ -25,15 +25,12 @@ import ( "gomodules.xyz/jsonpatch/v2" "helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/kube" helmkube "helm.sh/helm/v3/pkg/kube" - "helm.sh/helm/v3/pkg/postrender" "helm.sh/helm/v3/pkg/release" "helm.sh/helm/v3/pkg/storage/driver" apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" apitypes "k8s.io/apimachinery/pkg/types" @@ -110,8 +107,6 @@ func AppendUpgradeFailureRollbackOptions(opts ...RollbackOption) ActionClientGet } } -type PostRendererProvider func(rm meta.RESTMapper, kubeClient kube.Interface, obj client.Object) postrender.PostRenderer - func AppendPostRenderers(postRendererFns ...PostRendererProvider) ActionClientGetterOption { return func(getter *actionClientGetter) error { getter.postRendererProviders = append(getter.postRendererProviders, postRendererFns...) @@ -154,12 +149,11 @@ func (hcg *actionClientGetter) ActionClientFor(obj client.Object) (ActionInterfa if err != nil { return nil, err } - var chainedPostRenderer = chainedPostRenderer{ - DefaultPostRendererFunc(rm, actionConfig.KubeClient, obj), - } + var cpr = chainedPostRenderer{} for _, provider := range hcg.postRendererProviders { - chainedPostRenderer = append(chainedPostRenderer, provider(rm, actionConfig.KubeClient, obj)) + cpr = append(cpr, provider(rm, actionConfig.KubeClient, obj)) } + cpr = append(cpr, DefaultPostRendererFunc(rm, actionConfig.KubeClient, obj)) return &actionClient{ conf: actionConfig, @@ -168,8 +162,8 @@ func (hcg *actionClientGetter) ActionClientFor(obj client.Object) (ActionInterfa // 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, - defaultInstallOpts: append([]InstallOption{WithInstallPostRenderer(chainedPostRenderer)}, hcg.defaultInstallOpts...), - defaultUpgradeOpts: append([]UpgradeOption{WithUpgradePostRenderer(chainedPostRenderer)}, hcg.defaultUpgradeOpts...), + defaultInstallOpts: append([]InstallOption{WithInstallPostRenderer(cpr)}, hcg.defaultInstallOpts...), + defaultUpgradeOpts: append([]UpgradeOption{WithUpgradePostRenderer(cpr)}, hcg.defaultUpgradeOpts...), defaultUninstallOpts: hcg.defaultUninstallOpts, installFailureUninstallOpts: hcg.installFailureUninstallOpts, diff --git a/pkg/client/actionclient_test.go b/pkg/client/actionclient_test.go index a33ee5d6..5bace1a2 100644 --- a/pkg/client/actionclient_test.go +++ b/pkg/client/actionclient_test.go @@ -17,8 +17,11 @@ limitations under the License. package client import ( + "bytes" "context" "errors" + "fmt" + "io" "strconv" "time" @@ -27,6 +30,8 @@ import ( . "github.com/onsi/gomega" "helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/chartutil" + "helm.sh/helm/v3/pkg/kube" + "helm.sh/helm/v3/pkg/postrender" "helm.sh/helm/v3/pkg/release" "helm.sh/helm/v3/pkg/releaseutil" "helm.sh/helm/v3/pkg/storage/driver" @@ -36,6 +41,7 @@ import ( "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" apitypes "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/rand" @@ -78,12 +84,15 @@ var _ = Describe("ActionClient", func() { var ( actionConfigGetter ActionConfigGetter + cli kube.Interface obj client.Object ) BeforeEach(func() { var err error actionConfigGetter, err = NewActionConfigGetter(cfg, rm, logr.Discard()) Expect(err).ShouldNot(HaveOccurred()) + cli = kube.New(newRESTClientGetter(cfg, rm, "")) + Expect(err).ShouldNot(HaveOccurred()) obj = testutil.BuildTestCR(gvk) }) @@ -236,6 +245,42 @@ var _ = Describe("ActionClient", func() { }) Expect(err).To(MatchError(ContainSubstring(expectErr.Error()))) + // Uninstall the chart to cleanup for other tests. + _, err = ac.Uninstall(obj.GetName()) + Expect(err).To(BeNil()) + }) + It("should get clients with postrenderers", func() { + + acg, err := NewActionClientGetter(actionConfigGetter, AppendPostRenderers(newMockPostRenderer("foo", "bar"))) + Expect(err).To(BeNil()) + Expect(acg).NotTo(BeNil()) + + ac, err := acg.ActionClientFor(obj) + Expect(err).To(BeNil()) + + _, err = ac.Install(obj.GetName(), obj.GetNamespace(), &chrt, chartutil.Values{}) + Expect(err).To(BeNil()) + + rel, err := ac.Get(obj.GetName()) + Expect(err).To(BeNil()) + + rl, err := cli.Build(bytes.NewBufferString(rel.Manifest), false) + Expect(err).To(BeNil()) + + Expect(rl).NotTo(BeEmpty()) + err = rl.Visit(func(info *resource.Info, err error) error { + Expect(err).To(BeNil()) + Expect(info.Object).NotTo(BeNil()) + objMeta, err := meta.Accessor(info.Object) + Expect(err).To(BeNil()) + Expect(objMeta.GetAnnotations()).To(HaveKey("foo")) + Expect(objMeta.GetAnnotations()["foo"]).To(Equal("bar")) + return nil + }) + Expect(err).To(BeNil()) + + fmt.Println(rel.Manifest) + // Uninstall the chart to cleanup for other tests. _, err = ac.Uninstall(obj.GetName()) Expect(err).To(BeNil()) @@ -807,3 +852,60 @@ func newTestDeployment(containers []v1.Container) *appsv1.Deployment { }, } } + +type mockPostRenderer struct { + k8sCli kube.Interface + key string + value string +} + +var _ postrender.PostRenderer = &mockPostRenderer{} + +func newMockPostRenderer(key, value string) PostRendererProvider { + return func(rm meta.RESTMapper, kubeClient kube.Interface, obj client.Object) postrender.PostRenderer { + return &mockPostRenderer{ + k8sCli: kubeClient, + key: key, + value: value, + } + } +} + +func (m *mockPostRenderer) Run(renderedManifests *bytes.Buffer) (modifiedManifests *bytes.Buffer, err error) { + b, err := io.ReadAll(renderedManifests) + if err != nil { + return nil, err + } + rl, err := m.k8sCli.Build(bytes.NewBuffer(b), false) + if err != nil { + return nil, err + } + out := bytes.Buffer{} + err = rl.Visit(func(r *resource.Info, err error) error { + objMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(r.Object) + if err != nil { + return err + } + u := &unstructured.Unstructured{Object: objMap} + + annotations := u.GetAnnotations() + if annotations == nil { + annotations = map[string]string{} + } + annotations[m.key] = m.value + u.SetAnnotations(annotations) + + outData, err := yaml.Marshal(u.Object) + if err != nil { + return err + } + if _, err := out.WriteString("---\n" + string(outData)); err != nil { + return err + } + return nil + }) + if err != nil { + return nil, err + } + return &out, nil +} diff --git a/pkg/client/postrenderer.go b/pkg/client/postrenderer.go index 81d57b28..85983b7d 100644 --- a/pkg/client/postrenderer.go +++ b/pkg/client/postrenderer.go @@ -20,6 +20,10 @@ import ( "github.com/operator-framework/helm-operator-plugins/pkg/manifestutil" ) +// PostRendererProvider is a function that returns a post-renderer for a given object. +// obj represents the custom resource that is being reconciled. +type PostRendererProvider func(rm meta.RESTMapper, kubeClient kube.Interface, obj client.Object) postrender.PostRenderer + // WithInstallPostRenderer sets the post-renderer to use for the install. // It overrides any post-renderer that may already be configured or set // as a default. From 201750ed174008c03cb89964f380671bab9fd441 Mon Sep 17 00:00:00 2001 From: Ludovic Cleroux Date: Thu, 3 Aug 2023 10:33:07 +0200 Subject: [PATCH 3/6] Fix mock postRenderer --- pkg/client/actionclient_test.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/pkg/client/actionclient_test.go b/pkg/client/actionclient_test.go index 5bace1a2..9d417cfd 100644 --- a/pkg/client/actionclient_test.go +++ b/pkg/client/actionclient_test.go @@ -881,7 +881,14 @@ func (m *mockPostRenderer) Run(renderedManifests *bytes.Buffer) (modifiedManifes return nil, err } out := bytes.Buffer{} - err = rl.Visit(func(r *resource.Info, err error) error { + if err := rl.Visit(m.visit(out)); err != nil { + return nil, err + } + return &out, nil +} + +func (m *mockPostRenderer) visit(out bytes.Buffer) func(r *resource.Info, err error) error { + return func(r *resource.Info, err error) error { objMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(r.Object) if err != nil { return err @@ -903,9 +910,5 @@ func (m *mockPostRenderer) Run(renderedManifests *bytes.Buffer) (modifiedManifes return err } return nil - }) - if err != nil { - return nil, err } - return &out, nil } From 1984ea2eb31f67b90406af52b3e074c03f3e4358 Mon Sep 17 00:00:00 2001 From: Ludovic Cleroux Date: Thu, 3 Aug 2023 10:33:43 +0200 Subject: [PATCH 4/6] Fix leftover --- pkg/client/actionclient_test.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/pkg/client/actionclient_test.go b/pkg/client/actionclient_test.go index 9d417cfd..584667fb 100644 --- a/pkg/client/actionclient_test.go +++ b/pkg/client/actionclient_test.go @@ -20,7 +20,6 @@ import ( "bytes" "context" "errors" - "fmt" "io" "strconv" "time" @@ -279,8 +278,6 @@ var _ = Describe("ActionClient", func() { }) Expect(err).To(BeNil()) - fmt.Println(rel.Manifest) - // Uninstall the chart to cleanup for other tests. _, err = ac.Uninstall(obj.GetName()) Expect(err).To(BeNil()) From 27b2ba40b063687582f3035a9878563fc057c407 Mon Sep 17 00:00:00 2001 From: Ludovic Cleroux Date: Thu, 3 Aug 2023 11:04:45 +0200 Subject: [PATCH 5/6] Fix leftover --- pkg/client/actionclient_test.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pkg/client/actionclient_test.go b/pkg/client/actionclient_test.go index 584667fb..ba64052c 100644 --- a/pkg/client/actionclient_test.go +++ b/pkg/client/actionclient_test.go @@ -885,7 +885,10 @@ func (m *mockPostRenderer) Run(renderedManifests *bytes.Buffer) (modifiedManifes } func (m *mockPostRenderer) visit(out bytes.Buffer) func(r *resource.Info, err error) error { - return func(r *resource.Info, err error) error { + return func(r *resource.Info, rErr error) error { + if rErr != nil { + return rErr + } objMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(r.Object) if err != nil { return err From 3666f74c9479481b385e7fc4e6a853b099d7945b Mon Sep 17 00:00:00 2001 From: Ludovic Cleroux Date: Thu, 3 Aug 2023 11:22:31 +0200 Subject: [PATCH 6/6] Fix failing test --- pkg/client/actionclient_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/client/actionclient_test.go b/pkg/client/actionclient_test.go index ba64052c..8e590087 100644 --- a/pkg/client/actionclient_test.go +++ b/pkg/client/actionclient_test.go @@ -878,13 +878,13 @@ func (m *mockPostRenderer) Run(renderedManifests *bytes.Buffer) (modifiedManifes return nil, err } out := bytes.Buffer{} - if err := rl.Visit(m.visit(out)); err != nil { + if err := rl.Visit(m.visit(&out)); err != nil { return nil, err } return &out, nil } -func (m *mockPostRenderer) visit(out bytes.Buffer) func(r *resource.Info, err error) error { +func (m *mockPostRenderer) visit(out *bytes.Buffer) func(r *resource.Info, err error) error { return func(r *resource.Info, rErr error) error { if rErr != nil { return rErr