diff --git a/PROJECT b/PROJECT index 492d9e3..1b0d463 100644 --- a/PROJECT +++ b/PROJECT @@ -29,6 +29,9 @@ resources: kind: DbRoleClaim path: github.com/infobloxopen/db-controller/api/v1 version: v1 + webhooks: + validation: true + webhookVersion: v1 - api: crdVersion: v1 namespaced: true diff --git a/cmd/main.go b/cmd/main.go index 51e133e..34a3944 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -238,6 +238,11 @@ func main() { os.Exit(1) } + if err = webhookpersistancev1.SetupDbRoleClaimWebhookWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create webhook", "webhook", "DbRoleClaim") + os.Exit(1) + } + // +kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { diff --git a/config/webhook/manifests.yaml b/config/webhook/manifests.yaml index f9fb31a..e77e4b6 100644 --- a/config/webhook/manifests.yaml +++ b/config/webhook/manifests.yaml @@ -94,3 +94,22 @@ webhooks: resources: - databaseclaims sideEffects: None +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /validate-persistance-atlas-infoblox-com-v1-dbroleclaim + failurePolicy: Fail + name: vdbroleclaim-v1.kb.io + rules: + - apiGroups: + - persistance.atlas.infoblox.com + apiVersions: + - v1 + operations: + - DELETE + resources: + - dbroleclaims + sideEffects: None diff --git a/internal/webhook/v1/databaseclaim_webhook.go b/internal/webhook/v1/databaseclaim_webhook.go index 8b40d4f..fc36ccb 100644 --- a/internal/webhook/v1/databaseclaim_webhook.go +++ b/internal/webhook/v1/databaseclaim_webhook.go @@ -29,10 +29,6 @@ import ( persistancev1 "github.com/infobloxopen/db-controller/api/v1" ) -// nolint:unused -// log is for logging in this package. -var databaseclaimlog = logf.Log.WithName("databaseclaim-resource") - const deletionOverrideLabel = "persistance.atlas.infoblox.com/allow-deletion" // SetupDatabaseClaimWebhookWithManager registers the webhook for DatabaseClaim in the manager. @@ -48,27 +44,33 @@ type DatabaseClaimCustomValidator struct{} var _ webhook.CustomValidator = &DatabaseClaimCustomValidator{} -// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type DatabaseClaim. func (v *DatabaseClaimCustomValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { return nil, nil } -// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type DatabaseClaim. func (v *DatabaseClaimCustomValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { return nil, nil } // ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type DatabaseClaim. func (v *DatabaseClaimCustomValidator) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { + log := logf.FromContext(ctx).WithName("databaseclaim-webhook") + + req, err := admission.RequestFromContext(ctx) + if err != nil { + log.Error(err, "Unable to retrieve AdmissionRequest from context") + } + log.Info("Deletion request details", "username", req.UserInfo.Username, "groups", req.UserInfo.Groups, "uid", req.UserInfo.UID) + claim, ok := obj.(*persistancev1.DatabaseClaim) if !ok { return nil, fmt.Errorf("expected a DatabaseClaim object but got %T", obj) } - databaseclaimlog.Info("Validation for DatabaseClaim upon deletion", "name", claim.Name) + log.Info("Validation for DatabaseClaim upon deletion", "name", claim.Name) if value, exists := claim.GetLabels()[deletionOverrideLabel]; exists && value == "true" { - databaseclaimlog.Info("Deletion override label found; allowing deletion", "name", claim.Name) + log.Info("Deletion override label found; allowing deletion", "name", claim.Name) return nil, nil } diff --git a/internal/webhook/v1/dbroleclaim_webhook.go b/internal/webhook/v1/dbroleclaim_webhook.go new file mode 100644 index 0000000..2a53691 --- /dev/null +++ b/internal/webhook/v1/dbroleclaim_webhook.go @@ -0,0 +1,76 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1 + +import ( + "context" + "fmt" + + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/webhook" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + persistancev1 "github.com/infobloxopen/db-controller/api/v1" +) + +// SetupDbRoleClaimWebhookWithManager registers the webhook for DbRoleClaim in the manager. +func SetupDbRoleClaimWebhookWithManager(mgr ctrl.Manager) error { + return ctrl.NewWebhookManagedBy(mgr).For(&persistancev1.DbRoleClaim{}). + WithValidator(&DbRoleClaimCustomValidator{}). + Complete() +} + +// +kubebuilder:webhook:path=/validate-persistance-atlas-infoblox-com-v1-dbroleclaim,mutating=false,failurePolicy=fail,sideEffects=None,groups=persistance.atlas.infoblox.com,resources=dbroleclaims,verbs=delete,versions=v1,name=vdbroleclaim-v1.kb.io,admissionReviewVersions=v1 + +type DbRoleClaimCustomValidator struct{} + +var _ webhook.CustomValidator = &DbRoleClaimCustomValidator{} + +func (v *DbRoleClaimCustomValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { + return nil, nil +} + +func (v *DbRoleClaimCustomValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { + return nil, nil +} + +func (v *DbRoleClaimCustomValidator) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { + log := logf.FromContext(ctx).WithName("dbroleclaim-webhook") + + req, err := admission.RequestFromContext(ctx) + if err != nil { + log.Error(err, "Unable to retrieve AdmissionRequest from context") + } + log.Info("Deletion request details", "username", req.UserInfo.Username, "groups", req.UserInfo.Groups, "uid", req.UserInfo.UID) + + roleClaim, ok := obj.(*persistancev1.DbRoleClaim) + if !ok { + return nil, fmt.Errorf("expected a DatabaseClaim object but got %T", obj) + } + + log.Info("Validation for DatabaseClaim upon deletion", "name", roleClaim.Name) + + if value, exists := roleClaim.GetLabels()[deletionOverrideLabel]; exists && value == "true" { + log.Info("Deletion override label found; allowing deletion", "name", roleClaim.Name) + return nil, nil + } + + return nil, fmt.Errorf("deletion is denied for DatabaseClaim '%s'; set annotation or label '%s=true' to override", + roleClaim.Name, deletionOverrideLabel) +} diff --git a/internal/webhook/v1/dbroleclaim_webhook_test.go b/internal/webhook/v1/dbroleclaim_webhook_test.go new file mode 100644 index 0000000..17be649 --- /dev/null +++ b/internal/webhook/v1/dbroleclaim_webhook_test.go @@ -0,0 +1,88 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1 + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + persistancev1 "github.com/infobloxopen/db-controller/api/v1" +) + +var _ = Describe("DbRoleClaim Webhook", func() { + var ( + obj *persistancev1.DbRoleClaim + oldObj *persistancev1.DbRoleClaim + validator DbRoleClaimCustomValidator + ) + + BeforeEach(func() { + obj = &persistancev1.DbRoleClaim{} + oldObj = &persistancev1.DbRoleClaim{} + validator = DbRoleClaimCustomValidator{} + Expect(validator).NotTo(BeNil(), "Expected validator to be initialized") + Expect(oldObj).NotTo(BeNil(), "Expected oldObj to be initialized") + Expect(obj).NotTo(BeNil(), "Expected obj to be initialized") + }) + + Context("When deleting a DatabaseClaim under Validating Webhook", func() { + It("Should allow deletion if the deletion override label is set to true", func() { + By("Setting the deletion override label") + obj.SetLabels(map[string]string{deletionOverrideLabel: "true"}) + + By("Calling ValidateDelete") + warnings, err := validator.ValidateDelete(ctx, obj) + + By("Expecting no errors and no warnings") + Expect(err).NotTo(HaveOccurred()) + Expect(warnings).To(BeNil()) + }) + + It("Should deny deletion if the deletion override label is not set", func() { + By("Not setting the deletion override label") + obj.SetLabels(map[string]string{}) + + By("Calling ValidateDelete") + warnings, err := validator.ValidateDelete(ctx, obj) + + By("Expecting an error to occur") + Expect(err).To(HaveOccurred()) + Expect(warnings).To(BeNil()) + + By("Validating the error message") + Expect(err.Error()).To(ContainSubstring("deletion is denied for DatabaseClaim")) + Expect(err.Error()).To(ContainSubstring(deletionOverrideLabel)) + }) + + It("Should deny deletion if the deletion override label is set to false", func() { + By("Setting the deletion override label to false") + obj.SetLabels(map[string]string{deletionOverrideLabel: "false"}) + + By("Calling ValidateDelete") + warnings, err := validator.ValidateDelete(ctx, obj) + + By("Expecting an error to occur") + Expect(err).To(HaveOccurred()) + Expect(warnings).To(BeNil()) + + By("Validating the error message") + Expect(err.Error()).To(ContainSubstring("deletion is denied for DatabaseClaim")) + Expect(err.Error()).To(ContainSubstring(deletionOverrideLabel)) + }) + }) + +}) diff --git a/internal/webhook/v1/webhook_suite_test.go b/internal/webhook/v1/webhook_suite_test.go index ebb26c3..8795f5c 100644 --- a/internal/webhook/v1/webhook_suite_test.go +++ b/internal/webhook/v1/webhook_suite_test.go @@ -114,6 +114,9 @@ var _ = BeforeSuite(func() { err = SetupDatabaseClaimWebhookWithManager(mgr) Expect(err).NotTo(HaveOccurred()) + err = SetupDbRoleClaimWebhookWithManager(mgr) + Expect(err).NotTo(HaveOccurred()) + // +kubebuilder:scaffold:webhook go func() {