Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions pkg/init/webhooks/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,15 @@ import (
"strings"

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"
"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 {
Expand All @@ -30,6 +34,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),
Expand Down Expand Up @@ -88,6 +93,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),
Expand Down Expand Up @@ -128,3 +134,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
}
4 changes: 2 additions & 2 deletions pkg/init/webhooks/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ const (

type Flags struct {
Install bool
InstallOptions []installOption
CertOptions []certOption
InstallOptions []InstallOption
CertOptions []CertOption
BindHost string
BindPort int
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/init/webhooks/flags_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
54 changes: 52 additions & 2 deletions pkg/init/webhooks/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down Expand Up @@ -71,7 +71,7 @@ func Install(
c client.Client,
scheme *runtime.Scheme,
apiTypes []client.Object,
options ...installOption,
options ...InstallOption,
) error {
opts := &installOptions{
localClient: c,
Expand All @@ -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 {
Expand All @@ -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
}
8 changes: 4 additions & 4 deletions pkg/init/webhooks/init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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"},
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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"),
},
Expand Down
32 changes: 30 additions & 2 deletions pkg/init/webhooks/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -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)
}

Expand All @@ -37,7 +40,7 @@ type certOptions struct {
additionalDNSNames []string
}

type certOption interface {
type CertOption interface {
ApplyToCertOptions(o *certOptions)
}

Expand Down Expand Up @@ -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)
}
60 changes: 60 additions & 0 deletions pkg/init/webhooks/remove.go
Original file line number Diff line number Diff line change
@@ -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
}