From 6526dc513ecab157dd7c893b12eedce9fe0f9c5e Mon Sep 17 00:00:00 2001 From: grzegorz-ciezkowski Date: Thu, 9 Jan 2025 15:51:23 +0100 Subject: [PATCH] feat(pluginpresets) Deny plugin creation only deploy metohd is via PluginPreset (#829) --- e2e/plugin/e2e_test.go | 25 +++++++++++++------ e2e/plugin/fixtures/fixtures.go | 24 ++++++++++++++++++ e2e/shared/cluster.go | 3 +++ pkg/admission/plugin_webhook.go | 11 ++++++++ .../plugin/pluginpreset_controller.go | 8 ++++++ pkg/test/resources.go | 15 +++++++++++ 6 files changed, 79 insertions(+), 7 deletions(-) diff --git a/e2e/plugin/e2e_test.go b/e2e/plugin/e2e_test.go index 7c9a6724b..175e0670c 100644 --- a/e2e/plugin/e2e_test.go +++ b/e2e/plugin/e2e_test.go @@ -72,6 +72,7 @@ var _ = Describe("Plugin E2E", Ordered, func() { Eventually(func(g Gomega) { err := adminClient.Get(ctx, client.ObjectKey{Name: remoteClusterName, Namespace: env.TestNamespace}, &greenhousev1alpha1.Cluster{}) g.Expect(err).ToNot(HaveOccurred()) + }).Should(Succeed(), "cluster resource should be created") By("verifying the cluster status is ready") @@ -90,16 +91,26 @@ var _ = Describe("Plugin E2E", Ordered, func() { Expect(err).NotTo(HaveOccurred()) Expect(len(pluginDefinitionList.Items)).To(BeEquivalentTo(1)) - By("Creating the plugin") + By("Try to creating the plugin") // Creating plugin testPlugin := fixtures.PreparePlugin("test-nginx-plugin", env.TestNamespace, test.WithPluginDefinition(testPluginDefinition.Name), - test.WithCluster(remoteClusterName), test.WithReleaseNamespace(env.TestNamespace), test.WithPluginOptionValue("replicaCount", &apiextensionsv1.JSON{Raw: []byte("1")}, nil)) err = adminClient.Create(ctx, testPlugin) + Expect(err).To(HaveOccurred()) + + By("Creating the plugin preset") + testPluginPreset := fixtures.PreparePluginPreset("test-nginx-preset", env.TestNamespace, testPlugin.Spec) + err = adminClient.Create(ctx, testPluginPreset) Expect(err).ToNot(HaveOccurred()) + By("Checking the plugin preset is ready") + Eventually(func(g Gomega) { + err = adminClient.Get(ctx, client.ObjectKeyFromObject(testPluginPreset), testPluginPreset) + g.Expect(err).ToNot(HaveOccurred()) + }).Should(Succeed()) + By("Checking the plugin status is ready") pluginList := &greenhousev1alpha1.PluginList{} Eventually(func(g Gomega) { @@ -131,11 +142,11 @@ var _ = Describe("Plugin E2E", Ordered, func() { By("Updating replicas") Eventually(func(g Gomega) { - namespacedName := types.NamespacedName{Name: testPlugin.Name, Namespace: env.TestNamespace} - err = adminClient.Get(ctx, namespacedName, testPlugin) + namespacedName := types.NamespacedName{Name: testPluginPreset.Name, Namespace: testPluginPreset.Namespace} + err = adminClient.Get(ctx, namespacedName, testPluginPreset) g.Expect(err).NotTo(HaveOccurred()) - test.SetOptionValueForPlugin(testPlugin, "replicaCount", "2") - err = adminClient.Update(ctx, testPlugin) + test.SetOptionValueForPluginPreset(testPluginPreset, "replicaCount", "2") + err = adminClient.Update(ctx, testPluginPreset) g.Expect(err).NotTo(HaveOccurred()) }).Should(Succeed()) @@ -151,7 +162,7 @@ var _ = Describe("Plugin E2E", Ordered, func() { }).Should(Succeed()) By("Deleting plugin") - test.EventuallyDeleted(ctx, adminClient, testPlugin) + test.EventuallyDeleted(ctx, adminClient, testPluginPreset) By("Check, is deployment deleted") Eventually(func(g Gomega) bool { diff --git a/e2e/plugin/fixtures/fixtures.go b/e2e/plugin/fixtures/fixtures.go index 60a830934..16d6f3916 100644 --- a/e2e/plugin/fixtures/fixtures.go +++ b/e2e/plugin/fixtures/fixtures.go @@ -101,3 +101,27 @@ func PreparePlugin(name, namespace string, opts ...func(*greenhousev1alpha1.Plug } return plugin } + +func PreparePluginPreset(name, namespace string, pluginSpec greenhousev1alpha1.PluginSpec) *greenhousev1alpha1.PluginPreset { + pluginPreset := &greenhousev1alpha1.PluginPreset{ + TypeMeta: metav1.TypeMeta{ + Kind: "PluginPreset", + APIVersion: greenhousev1alpha1.GroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + GenerateName: name + "-gen", + }, + Spec: greenhousev1alpha1.PluginPresetSpec{ + Plugin: pluginSpec, + ClusterSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "test", + }, + }, + }, + } + + return pluginPreset +} diff --git a/e2e/shared/cluster.go b/e2e/shared/cluster.go index 5f86f7aed..c81b69c8d 100644 --- a/e2e/shared/cluster.go +++ b/e2e/shared/cluster.go @@ -31,6 +31,9 @@ func OnboardRemoteCluster(ctx context.Context, k8sClient client.Client, kubeConf ObjectMeta: metav1.ObjectMeta{ Name: name, Namespace: namespace, + Labels: map[string]string{ + "app": "test", + }, }, Type: greenhouseapis.SecretTypeKubeConfig, Data: map[string][]byte{ diff --git a/pkg/admission/plugin_webhook.go b/pkg/admission/plugin_webhook.go index c96e65d3e..b1e5b3b05 100644 --- a/pkg/admission/plugin_webhook.go +++ b/pkg/admission/plugin_webhook.go @@ -6,6 +6,7 @@ package admission import ( "context" "encoding/json" + "errors" "fmt" "strings" @@ -88,6 +89,10 @@ func ValidateCreatePlugin(ctx context.Context, c client.Client, obj runtime.Obje return nil, nil } + if !allowCreatePlugin(plugin) { + return nil, errors.New("plugin creation is not allowed") + } + pluginDefinition := new(greenhousev1alpha1.PluginDefinition) err := c.Get(ctx, client.ObjectKey{Namespace: "", Name: plugin.Spec.PluginDefinition}, pluginDefinition) if err != nil { @@ -144,6 +149,12 @@ func ValidateDeletePlugin(_ context.Context, _ client.Client, _ runtime.Object) return nil, nil } +func allowCreatePlugin(plugin *greenhousev1alpha1.Plugin) bool { + _, ok := plugin.Annotations["greenhouse.sap/allow-create"] + delete(plugin.Annotations, "greenhouse.sap/allow-create") + return ok +} + // validateOwnerRefernce returns a Warning if the Plugin is managed by a PluginPreset // The user is warned that the Plugin will be reconciled to the desired state specified in the PluginPreset. func validateOwnerReference(plugin *greenhousev1alpha1.Plugin) admission.Warnings { diff --git a/pkg/controllers/plugin/pluginpreset_controller.go b/pkg/controllers/plugin/pluginpreset_controller.go index a5dd0ec11..cbc700821 100644 --- a/pkg/controllers/plugin/pluginpreset_controller.go +++ b/pkg/controllers/plugin/pluginpreset_controller.go @@ -169,7 +169,15 @@ func (r *PluginPresetReconciler) reconcilePluginPreset(ctx context.Context, pres return err } + log.FromContext(ctx).Info("======================== OOOOOOOOOO", "cluster", cluster) _, err = clientutil.CreateOrPatch(ctx, r.Client, plugin, func() error { + // Add annotation to allow plugin creation + if plugin.Annotations == nil { + plugin.Annotations = make(map[string]string) + } + + plugin.Annotations["greenhouse.sap/allow-create"] = "true" + // Label the plugin with the managed resource label to identify it as managed by the PluginPreset. plugin.SetLabels(map[string]string{greenhouseapis.LabelKeyPluginPreset: preset.Name}) // Set the owner reference to the PluginPreset. This is used to trigger reconciliation, if the managed Plugin is modified. diff --git a/pkg/test/resources.go b/pkg/test/resources.go index f5bdd686a..94341973b 100644 --- a/pkg/test/resources.go +++ b/pkg/test/resources.go @@ -208,6 +208,21 @@ func SetOptionValueForPlugin(plugin *greenhousev1alpha1.Plugin, key, value strin }) } +// SetOptionValueForPluginPreset sets the value of a PluginOtionValue +func SetOptionValueForPluginPreset(pluginPreset *greenhousev1alpha1.PluginPreset, key, value string) { + for i, keyValue := range pluginPreset.Spec.Plugin.OptionValues { + if keyValue.Name == key { + pluginPreset.Spec.Plugin.OptionValues[i].Value.Raw = []byte(value) + return + } + } + + pluginPreset.Spec.Plugin.OptionValues = append(pluginPreset.Spec.Plugin.OptionValues, greenhousev1alpha1.PluginOptionValue{ + Name: key, + Value: &apiextensionsv1.JSON{Raw: []byte(value)}, + }) +} + // NewPlugin returns a greenhousev1alpha1.Plugin object. Opts can be used to set the desired state of the Plugin. func NewPlugin(ctx context.Context, name, namespace string, opts ...func(*greenhousev1alpha1.Plugin)) *greenhousev1alpha1.Plugin { GinkgoHelper()