diff --git a/cmd/controller.go b/cmd/controller.go index 90989309bd..05ea2815c7 100644 --- a/cmd/controller.go +++ b/cmd/controller.go @@ -23,7 +23,9 @@ type controller struct { metricsAddr, healthAddr string leaderElect bool enableWebhooks bool + enableAppcatWebhooks bool enableQuotas bool + enableEventForwarding bool certDir string } @@ -44,8 +46,10 @@ func init() { ControllerCMD.Flags().BoolVar(&c.leaderElect, "leader-elect", false, "Enable leader election for controller manager. "+ "Enabling this will ensure there is only one active controller manager.") ControllerCMD.Flags().BoolVar(&c.enableWebhooks, "webhooks", true, "Disable the validation webhooks.") + ControllerCMD.Flags().BoolVar(&c.enableAppcatWebhooks, "appcat-webhooks", true, "Disable the appcat validation webhooks") ControllerCMD.Flags().StringVar(&c.certDir, "certdir", "/etc/webhook/certs", "Set the webhook certificate directory") ControllerCMD.Flags().BoolVar(&c.enableQuotas, "quotas", false, "Enable the quota webhooks, is only active if webhooks is also true") + ControllerCMD.Flags().BoolVar(&c.enableEventForwarding, "event-forwarding", true, "Disable event-forwarding") viper.AutomaticEnv() if !viper.IsSet("PLANS_NAMESPACE") { viper.Set("PLANS_NAMESPACE", "syn-appcat") @@ -84,15 +88,17 @@ func (c *controller) executeController(cmd *cobra.Command, _ []string) error { return err } - events := &events.EventHandler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), - } + if c.enableEventForwarding { + events := &events.EventHandler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + } - err = events.SetupWithManager(mgr) + err = events.SetupWithManager(mgr) - if err != nil { - return err + if err != nil { + return err + } } if c.enableWebhooks { @@ -101,7 +107,7 @@ func (c *controller) executeController(cmd *cobra.Command, _ []string) error { return fmt.Errorf("PLANS_NAMEPSACE env variable needs to be set for quota support") } - err := setupWebhooks(mgr, c.enableQuotas) + err := setupWebhooks(mgr, c.enableQuotas, c.enableAppcatWebhooks) if err != nil { return err } @@ -117,45 +123,50 @@ func (c *controller) executeController(cmd *cobra.Command, _ []string) error { return mgr.Start(ctrl.SetupSignalHandler()) } -func setupWebhooks(mgr manager.Manager, withQuota bool) error { - err := webhooks.SetupPostgreSQLWebhookHandlerWithManager(mgr, withQuota) - if err != nil { - return err - } - err = webhooks.SetupRedisWebhookHandlerWithManager(mgr, withQuota) - if err != nil { - return err - } - err = webhooks.SetupMariaDBWebhookHandlerWithManager(mgr, withQuota) - if err != nil { - return err - } - err = webhooks.SetupMinioWebhookHandlerWithManager(mgr, withQuota) - if err != nil { - return err - } - err = webhooks.SetupNextcloudWebhookHandlerWithManager(mgr, withQuota) - if err != nil { - return err - } - err = webhooks.SetupKeycloakWebhookHandlerWithManager(mgr, withQuota) - if err != nil { - return err - } - err = webhooks.SetupNamespaceDeletionProtectionHandlerWithManager(mgr) - if err != nil { - return err +func setupWebhooks(mgr manager.Manager, withQuota bool, withAppcatWebhooks bool) error { + if withAppcatWebhooks { + err := webhooks.SetupPostgreSQLWebhookHandlerWithManager(mgr, withQuota) + if err != nil { + return err + } + err = webhooks.SetupRedisWebhookHandlerWithManager(mgr, withQuota) + if err != nil { + return err + } + err = webhooks.SetupMariaDBWebhookHandlerWithManager(mgr, withQuota) + if err != nil { + return err + } + err = webhooks.SetupMinioWebhookHandlerWithManager(mgr, withQuota) + if err != nil { + return err + } + err = webhooks.SetupNextcloudWebhookHandlerWithManager(mgr, withQuota) + if err != nil { + return err + } + err = webhooks.SetupKeycloakWebhookHandlerWithManager(mgr, withQuota) + if err != nil { + return err + } + err = webhooks.SetupXObjectbucketCDeletionProtectionHandlerWithManager(mgr) + if err != nil { + return err + } + + err = webhooks.SetupObjectbucketDeletionProtectionHandlerWithManager(mgr) + if err != nil { + return err + } } - err = webhooks.SetupXObjectbucketCDeletionProtectionHandlerWithManager(mgr) + err := webhooks.SetupReleaseDeletionProtectionHandlerWithManager(mgr) if err != nil { return err } - - err = webhooks.SetupObjectbucketDeletionProtectionHandlerWithManager(mgr) + err = webhooks.SetupNamespaceDeletionProtectionHandlerWithManager(mgr) if err != nil { return err } - return webhooks.SetupPVCDeletionProtectionHandlerWithManager(mgr) } diff --git a/config/controller/webhooks.yaml b/config/controller/webhooks.yaml index 049497c9c3..99514145c0 100644 --- a/config/controller/webhooks.yaml +++ b/config/controller/webhooks.yaml @@ -82,6 +82,25 @@ webhooks: resources: - persistentvolumeclaims sideEffects: None +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /validate-helm-crossplane-io-v1beta1-release + failurePolicy: Fail + name: releases.vshn.appcat.vshn.io + rules: + - apiGroups: + - helm.crossplane.io + apiVersions: + - v1beta1 + operations: + - DELETE + resources: + - releases + sideEffects: None - admissionReviewVersions: - v1 clientConfig: diff --git a/pkg/controller/webhooks/release.go b/pkg/controller/webhooks/release.go new file mode 100644 index 0000000000..e4b880fd5a --- /dev/null +++ b/pkg/controller/webhooks/release.go @@ -0,0 +1,72 @@ +package webhooks + +import ( + "context" + "fmt" + + "github.com/go-logr/logr" + helmv1beta1 "github.com/vshn/appcat/v4/apis/helm/release/v1beta1" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/webhook" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" +) + +//+kubebuilder:webhook:verbs=delete,path=/validate-helm-crossplane-io-v1beta1-release,mutating=false,failurePolicy=fail,groups="helm.crossplane.io",resources=releases,versions=v1beta1,name=releases.vshn.appcat.vshn.io,sideEffects=None,admissionReviewVersions=v1 + +var _ webhook.CustomValidator = &ReleaseDeletionProtectionHandler{} + +type ReleaseDeletionProtectionHandler struct { + client client.Client + log logr.Logger +} + +// SetupNamespaceDeletionProtectionHandlerWithManager registers the validation webhook with the manager. +func SetupReleaseDeletionProtectionHandlerWithManager(mgr ctrl.Manager) error { + + return ctrl.NewWebhookManagedBy(mgr). + For(&helmv1beta1.Release{}). + WithValidator(&ReleaseDeletionProtectionHandler{ + client: mgr.GetClient(), + log: mgr.GetLogger().WithName("webhook").WithName("release"), + }). + Complete() +} + +// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type +func (p *ReleaseDeletionProtectionHandler) ValidateCreate(_ context.Context, _ runtime.Object) (admission.Warnings, error) { + // NOOP for now + return nil, nil +} + +// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type +func (p *ReleaseDeletionProtectionHandler) ValidateUpdate(_ context.Context, _, _ runtime.Object) (admission.Warnings, error) { + // NOOP for now + return nil, nil +} + +// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type +func (p *ReleaseDeletionProtectionHandler) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { + + release, ok := obj.(client.Object) + if !ok { + return nil, fmt.Errorf("object is not valid") + } + + l := p.log.WithValues("object", release.GetName(), "release", release.GetNamespace(), "GVK", release.GetObjectKind().GroupVersionKind().String()) + + compInfo, err := checkManagedObject(ctx, release, p.client, l) + if err != nil { + return nil, err + } + + if compInfo.Exists { + l.Info("Blocking deletion of release", "parent", compInfo.Name) + return nil, fmt.Errorf(protectedMessage, "release", compInfo.Name) + } + + l.Info("Allowing deletion of release", "parent", compInfo.Name) + + return nil, nil +}