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 9f405d7f..85159044 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" @@ -73,12 +79,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) }) @@ -231,6 +240,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()) @@ -802,3 +847,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.