From 067e21f4b331b2a638a81b8a88ee77722f30f491 Mon Sep 17 00:00:00 2001 From: Johannes Aubart Date: Tue, 28 Oct 2025 16:39:02 +0100 Subject: [PATCH 1/2] extend webhook init logic --- pkg/init/webhooks/apply.go | 31 +++++++++++++++++ pkg/init/webhooks/flags.go | 4 +-- pkg/init/webhooks/flags_test.go | 4 +-- pkg/init/webhooks/init.go | 54 +++++++++++++++++++++++++++-- pkg/init/webhooks/init_test.go | 8 ++--- pkg/init/webhooks/options.go | 32 ++++++++++++++++-- pkg/init/webhooks/remove.go | 60 +++++++++++++++++++++++++++++++++ 7 files changed, 181 insertions(+), 12 deletions(-) create mode 100644 pkg/init/webhooks/remove.go diff --git a/pkg/init/webhooks/apply.go b/pkg/init/webhooks/apply.go index 78d4299..ed852f8 100644 --- a/pkg/init/webhooks/apply.go +++ b/pkg/init/webhooks/apply.go @@ -6,8 +6,11 @@ import ( "log" "strings" + "github.com/openmcp-project/controller-utils/pkg/collections/maps" admissionregistrationv1 "k8s.io/api/admissionregistration/v1" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/apiutil" @@ -30,6 +33,7 @@ func applyValidatingWebhook(ctx context.Context, opts *installOptions, obj clien resource := strings.ToLower(gvk.Kind + "s") result, err := controllerutil.CreateOrUpdate(ctx, opts.remoteClient, cfg, func() error { + cfg.Labels = maps.Merge(cfg.Labels, opts.managedLabels) webhook := admissionregistrationv1.ValidatingWebhook{ Name: strings.ToLower(fmt.Sprintf("v%s.%s", gvk.Kind, gvk.Group)), FailurePolicy: ptr.To(admissionregistrationv1.Fail), @@ -88,6 +92,7 @@ func applyMutatingWebhook(ctx context.Context, opts *installOptions, obj client. resource := strings.ToLower(gvk.Kind + "s") result, err := controllerutil.CreateOrUpdate(ctx, opts.remoteClient, cfg, func() error { + cfg.Labels = maps.Merge(cfg.Labels, opts.managedLabels) webhook := admissionregistrationv1.MutatingWebhook{ Name: strings.ToLower(fmt.Sprintf("m%s.%s", gvk.Kind, gvk.Group)), FailurePolicy: ptr.To(admissionregistrationv1.Fail), @@ -128,3 +133,29 @@ func applyMutatingWebhook(ctx context.Context, opts *installOptions, obj client. log.Println("Mutating webhook config", cfg.Name, result) return err } + +func applyWebhookService(ctx context.Context, opts *installOptions) error { + svc := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: opts.webhookService.Name, + Namespace: opts.webhookService.Namespace, + }, + } + + result, err := controllerutil.CreateOrUpdate(ctx, opts.localClient, svc, func() error { + svc.Labels = maps.Merge(svc.Labels, opts.managedLabels) + svc.Spec.Selector = opts.managedService.SelectorLabels + svc.Spec.Type = corev1.ServiceTypeClusterIP + svc.Spec.Ports = []corev1.ServicePort{ + { + Name: "https", + Protocol: corev1.ProtocolTCP, + Port: opts.webhookServicePort, + TargetPort: opts.managedService.TargetPort, + }, + } + return nil + }) + log.Println("Webhook service", types.NamespacedName{Namespace: svc.Namespace, Name: svc.Name}.String(), result) + return err +} diff --git a/pkg/init/webhooks/flags.go b/pkg/init/webhooks/flags.go index 27d9b07..5bfa10e 100644 --- a/pkg/init/webhooks/flags.go +++ b/pkg/init/webhooks/flags.go @@ -12,8 +12,8 @@ const ( type Flags struct { Install bool - InstallOptions []installOption - CertOptions []certOption + InstallOptions []InstallOption + CertOptions []CertOption BindHost string BindPort int } diff --git a/pkg/init/webhooks/flags_test.go b/pkg/init/webhooks/flags_test.go index f0bd3a6..4fc4bec 100644 --- a/pkg/init/webhooks/flags_test.go +++ b/pkg/init/webhooks/flags_test.go @@ -27,12 +27,12 @@ func Test_BindFlags(t *testing.T) { assert.NotNil(t, flags) assert.Equal(t, &Flags{ Install: true, - InstallOptions: []installOption{ + InstallOptions: []InstallOption{ WithoutCA, WithCustomBaseURL("https://webhooks.example.com"), WithWebhookServicePort(1234), }, - CertOptions: []certOption{ + CertOptions: []CertOption{ WithAdditionalDNSNames{"webhooks.example.com", "webhooks.example.org"}, }, BindHost: "someaddr", diff --git a/pkg/init/webhooks/init.go b/pkg/init/webhooks/init.go index fb27d71..efda162 100644 --- a/pkg/init/webhooks/init.go +++ b/pkg/init/webhooks/init.go @@ -18,7 +18,7 @@ var ( ) // GenerateCertificate -func GenerateCertificate(ctx context.Context, c client.Client, options ...certOption) error { +func GenerateCertificate(ctx context.Context, c client.Client, options ...CertOption) error { opts := &certOptions{ webhookService: getWebhookServiceFromEnv(), webhookSecret: getWebhookSecretFromEnv(), @@ -71,7 +71,7 @@ func Install( c client.Client, scheme *runtime.Scheme, apiTypes []client.Object, - options ...installOption, + options ...InstallOption, ) error { opts := &installOptions{ localClient: c, @@ -98,6 +98,12 @@ func Install( opts.caData = secret.Data[corev1.TLSCertKey] } + if opts.managedService != nil { + if err := applyWebhookService(ctx, opts); err != nil { + return err + } + } + for _, o := range apiTypes { _, isCustomValidator := o.(webhook.CustomValidator) if isCustomValidator { @@ -116,3 +122,47 @@ func Install( log.Println("Webhooks initialized") return nil } + +func Uninstall( + ctx context.Context, + c client.Client, + scheme *runtime.Scheme, + apiTypes []client.Object, + options ...InstallOption, +) error { + opts := &installOptions{ + localClient: c, + remoteClient: c, + scheme: scheme, + webhookService: getWebhookServiceFromEnv(), + webhookSecret: getWebhookSecretFromEnv(), + webhookServicePort: 443, + } + for _, io := range options { + io.ApplyToInstallOptions(opts) + } + + if opts.managedService != nil { + if err := removeWebhookService(ctx, opts); err != nil { + return err + } + } + + for _, o := range apiTypes { + _, isCustomValidator := o.(webhook.CustomValidator) + if isCustomValidator { + if err := removeValidatingWebhook(ctx, opts, o); err != nil { + return err + } + } + _, isCustomDefaulter := o.(webhook.CustomDefaulter) + if isCustomDefaulter { + if err := removeMutatingWebhook(ctx, opts, o); err != nil { + return err + } + } + } + + log.Println("Webhooks removed") + return nil +} diff --git a/pkg/init/webhooks/init_test.go b/pkg/init/webhooks/init_test.go index e1e8f5a..f7374e7 100644 --- a/pkg/init/webhooks/init_test.go +++ b/pkg/init/webhooks/init_test.go @@ -30,7 +30,7 @@ func Test_GenerateCertificate(t *testing.T) { desc string setup func(ctx context.Context, c client.Client) error validate func(ctx context.Context, c client.Client, t *testing.T, testErr error) error - options []certOption + options []CertOption }{ { desc: "should generate certificate", @@ -64,7 +64,7 @@ func Test_GenerateCertificate(t *testing.T) { }, { desc: "should generate certificate with custom object names", - options: []certOption{ + options: []CertOption{ WithWebhookSecret{Name: "myothersecret", Namespace: "myothernamespace"}, WithWebhookService{Name: "myotherservice", Namespace: "myothernamespace"}, WithAdditionalDNSNames{"some.other.name.example.com"}, @@ -165,7 +165,7 @@ func Test_Install(t *testing.T) { desc string setup func(ctx context.Context, c client.Client) error validate func(ctx context.Context, c client.Client, t *testing.T, testErr error) error - options []installOption + options []InstallOption }{ { desc: "should create webhook configurations for TestObj", @@ -224,7 +224,7 @@ func Test_Install(t *testing.T) { }, { desc: "should create webhook configurations for TestObj with custom values", - options: []installOption{ + options: []InstallOption{ WithoutCA, WithCustomBaseURL("https://webhooks.example.com"), }, diff --git a/pkg/init/webhooks/options.go b/pkg/init/webhooks/options.go index b1be813..2e7978b 100644 --- a/pkg/init/webhooks/options.go +++ b/pkg/init/webhooks/options.go @@ -3,6 +3,7 @@ package webhooks import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -21,9 +22,11 @@ type installOptions struct { webhookService types.NamespacedName webhookSecret types.NamespacedName webhookServicePort int32 + managedLabels map[string]string + managedService *WithManagedWebhookService } -type installOption interface { +type InstallOption interface { ApplyToInstallOptions(o *installOptions) } @@ -37,7 +40,7 @@ type certOptions struct { additionalDNSNames []string } -type certOption interface { +type CertOption interface { ApplyToCertOptions(o *certOptions) } @@ -134,3 +137,28 @@ type WithAdditionalDNSNames []string func (opt WithAdditionalDNSNames) ApplyToCertOptions(o *certOptions) { o.additionalDNSNames = opt } + +// +// Managed Webhook Service +// + +// WithManagedWebhookService allows to have the webhook service created and managed by this library. +type WithManagedWebhookService struct { + TargetPort intstr.IntOrString + SelectorLabels map[string]string +} + +func (opt WithManagedWebhookService) ApplyToInstallOptions(o *installOptions) { + o.managedService = &opt +} + +// +// Managed Labels +// + +// WithManagedLabels specifies labels which should be added to resources created by this library. +type WithManagedLabels map[string]string + +func (opt WithManagedLabels) ApplyToInstallOptions(o *installOptions) { + o.managedLabels = map[string]string(opt) +} diff --git a/pkg/init/webhooks/remove.go b/pkg/init/webhooks/remove.go new file mode 100644 index 0000000..94e06da --- /dev/null +++ b/pkg/init/webhooks/remove.go @@ -0,0 +1,60 @@ +package webhooks + +import ( + "context" + "log" + + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/apiutil" +) + +func removeValidatingWebhook(ctx context.Context, opts *installOptions, obj client.Object) error { + gvk, err := apiutil.GVKForObject(obj, opts.scheme) + if err != nil { + return err + } + + cfg := &admissionregistrationv1.ValidatingWebhookConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: generateValidateName(gvk), + }, + } + + err = opts.remoteClient.Delete(ctx, cfg) + log.Println("Removing validating webhook config", cfg.Name) + return client.IgnoreNotFound(err) +} + +func removeMutatingWebhook(ctx context.Context, opts *installOptions, obj client.Object) error { + gvk, err := apiutil.GVKForObject(obj, opts.scheme) + if err != nil { + return err + } + + cfg := &admissionregistrationv1.MutatingWebhookConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: generateMutateName(gvk), + }, + } + + err = opts.remoteClient.Delete(ctx, cfg) + log.Println("Removing mutating webhook config", cfg.Name) + return client.IgnoreNotFound(err) +} + +func removeWebhookService(ctx context.Context, opts *installOptions) error { + svc := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: opts.webhookService.Name, + Namespace: opts.webhookService.Namespace, + }, + } + + err := opts.localClient.Delete(ctx, svc) + log.Println("Removing webhook service", types.NamespacedName{Namespace: svc.Namespace, Name: svc.Name}.String()) + return err +} From dca0c5d16ac7bdee62310e0c2381d43d4a53bf47 Mon Sep 17 00:00:00 2001 From: Johannes Aubart Date: Tue, 28 Oct 2025 17:04:41 +0100 Subject: [PATCH 2/2] task generate --- pkg/init/webhooks/apply.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/init/webhooks/apply.go b/pkg/init/webhooks/apply.go index ed852f8..435b5d0 100644 --- a/pkg/init/webhooks/apply.go +++ b/pkg/init/webhooks/apply.go @@ -6,7 +6,6 @@ import ( "log" "strings" - "github.com/openmcp-project/controller-utils/pkg/collections/maps" admissionregistrationv1 "k8s.io/api/admissionregistration/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -15,6 +14,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/apiutil" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + "github.com/openmcp-project/controller-utils/pkg/collections/maps" ) func applyValidatingWebhook(ctx context.Context, opts *installOptions, obj client.Object) error {