Skip to content

Commit

Permalink
✨ Allow customizing generated webhook K8s Service
Browse files Browse the repository at this point in the history
Allow customizing the webhook K8s Service's name, namespace, and port.

## example usage

Look for the new `serviceName`, `serviceNamespace`, and `servicePort` fields.

```
❯ GOBIN=(pwd)/bin go install ./cmd/*

❯ ./bin/controller-gen webhook -w

Webhook

+kubebuilder:webhook:admissionReviewVersions=<[]string>,failurePolicy=<string>,groups=<[]string>[,matchPolicy=<string>],mutating=<bool>,name=<string>[,path=<string>][,reinvocationPolicy=<string>],resources=<[]string>[,serviceName=<string>][,serviceNamespace=<string>][,servicePort=<int>][,sideEffects=<string>][,timeoutSeconds=<int>][,url=<string>],verbs=<[]string>,versions=<[]string>[,webhookVersions=<[]string>]  package  specifies how a webhook should be served.

...
```

contributes to #865

Signed-off-by: David Xia <[email protected]>
  • Loading branch information
davidxia committed Jan 30, 2025
1 parent 0fa4366 commit fd6668c
Show file tree
Hide file tree
Showing 6 changed files with 295 additions and 4 deletions.
37 changes: 33 additions & 4 deletions pkg/webhook/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,10 @@ import (

// The default {Mutating,Validating}WebhookConfiguration version to generate.
const (
v1 = "v1"
defaultWebhookVersion = v1
v1 = "v1"
defaultWebhookVersion = v1
defaultServiceName = "webhook-service"
defaultServiceNamespace = "system"
)

var (
Expand Down Expand Up @@ -118,6 +120,12 @@ type Config struct {
// Name indicates the name of this webhook configuration. Should be a domain with at least three segments separated by dots
Name string

// ServiceName indicates the name of the K8s Service the webhook uses.
ServiceName string `marker:"serviceName,optional"`

// ServiceNamespace indicates the namespace of the K8s Service the webhook uses.
ServiceNamespace string `marker:"serviceNamespace,optional"`

// Path specifies that path that the API server should connect to this webhook on. Must be
// prefixed with a '/validate-' or '/mutate-' depending on the type, and followed by
// $GROUP-$VERSION-$KIND where all values are lower-cased and the periods in the group
Expand All @@ -126,6 +134,9 @@ type Config struct {
// /validate-batch-tutorial-kubebuilder-io-v1-cronjob
Path string `marker:"path,optional"`

// ServicePort indicates the port of the K8s Service the webhook uses
ServicePort *int32 `marker:"servicePort,optional"`

// WebhookVersions specifies the target API versions of the {Mutating,Validating}WebhookConfiguration objects
// itself to generate. The only supported value is v1. Defaults to v1.
WebhookVersions []string `marker:"webhookVersions,optional"`
Expand Down Expand Up @@ -318,11 +329,29 @@ func (c Config) clientConfig() (admissionregv1.WebhookClientConfig, error) {

path := c.Path
if path != "" {
var name, namespace string
var port *int32

if c.ServiceName != "" {
name = c.ServiceName
} else {
name = defaultServiceName
}
if c.ServiceNamespace != "" {
namespace = c.ServiceNamespace
} else {
namespace = defaultServiceNamespace
}
if c.ServicePort != nil {
port = c.ServicePort
}

return admissionregv1.WebhookClientConfig{
Service: &admissionregv1.ServiceReference{
Name: "webhook-service",
Namespace: "system",
Name: name,
Namespace: namespace,
Path: &path,
Port: port,
},
}, nil
}
Expand Down
46 changes: 46 additions & 0 deletions pkg/webhook/parser_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,52 @@ var _ = Describe("Webhook Generation From Parsing to CustomResourceDefinition",
assertSame(actualValidating, expectedValidating)
})

It("should properly generate the webhook definition when the Service is customized with the `kubebuilder:webhook` marker", func() {
By("switching into testdata to appease go modules")
cwd, err := os.Getwd()
Expect(err).NotTo(HaveOccurred())
Expect(os.Chdir("./testdata/valid-custom-service")).To(Succeed()) // go modules are directory-sensitive
defer func() { Expect(os.Chdir(cwd)).To(Succeed()) }()

By("loading the roots")
pkgs, err := loader.LoadRoots(".")
Expect(err).NotTo(HaveOccurred())
Expect(pkgs).To(HaveLen(1))

By("setting up the parser")
reg := &markers.Registry{}
Expect(reg.Register(webhook.ConfigDefinition)).To(Succeed())
Expect(reg.Register(webhook.WebhookConfigDefinition)).To(Succeed())

By("requesting that the manifest be generated")
outputDir, err := os.MkdirTemp("", "webhook-integration-test")
Expect(err).NotTo(HaveOccurred())
defer os.RemoveAll(outputDir)
genCtx := &genall.GenerationContext{
Collector: &markers.Collector{Registry: reg},
Roots: pkgs,
OutputRule: genall.OutputToDirectory(outputDir),
}
Expect(webhook.Generator{}.Generate(genCtx)).To(Succeed())
for _, r := range genCtx.Roots {
Expect(r.Errors).To(HaveLen(0))
}

By("loading the generated v1 YAML")
actualFile, err := os.ReadFile(path.Join(outputDir, "manifests.yaml"))
Expect(err).NotTo(HaveOccurred())
actualMutating, actualValidating := unmarshalBothV1(actualFile)

By("loading the desired v1 YAML")
expectedFile, err := os.ReadFile("manifests.yaml")
Expect(err).NotTo(HaveOccurred())
expectedMutating, expectedValidating := unmarshalBothV1(expectedFile)

By("comparing the two")
assertSame(actualMutating, expectedMutating)
assertSame(actualValidating, expectedValidating)
})

It("should generate the ordered webhook definitions", func() {
By("switching into testdata to appease go modules")
cwd, err := os.Getwd()
Expand Down
71 changes: 71 additions & 0 deletions pkg/webhook/testdata/valid-custom-service/cronjob_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
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.
*/

//go:generate ../../../../.run-controller-gen.sh webhook paths=. output:dir=.

// +groupName=testdata.kubebuilder.io
// +versionName=v1
package cronjob

import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.

// CronJobSpec defines the desired state of CronJob
type CronJobSpec struct {
// The schedule in Cron format, see https://en.wikipedia.org/wiki/Cron.
Schedule string `json:"schedule"`
}

// CronJobStatus defines the observed state of CronJob
type CronJobStatus struct {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
// Important: Run "make" to regenerate code after modifying this file

// Information when was the last time the job was successfully scheduled.
// +optional
LastScheduleTime *metav1.Time `json:"lastScheduleTime,omitempty"`
}

// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:resource:singular=mycronjob

// CronJob is the Schema for the cronjobs API
type CronJob struct {
/*
*/
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

Spec CronJobSpec `json:"spec,omitempty"`
Status CronJobStatus `json:"status,omitempty"`
}

// +kubebuilder:object:root=true

// CronJobList contains a list of CronJob
type CronJobList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []CronJob `json:"items"`
}

func init() {
SchemeBuilder.Register(&CronJob{}, &CronJobList{})
}
83 changes: 83 additions & 0 deletions pkg/webhook/testdata/valid-custom-service/manifests.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
---
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
name: mutating-webhook-configuration
webhooks:
- admissionReviewVersions:
- v1
- v1beta1
clientConfig:
service:
name: FOO
namespace: system
path: /mutate-testdata-kubebuilder-io-v1-cronjob
failurePolicy: Fail
matchPolicy: Equivalent
name: default.cronjob.testdata.kubebuilder.io
rules:
- apiGroups:
- testdata.kubebuiler.io
apiVersions:
- v1
operations:
- CREATE
- UPDATE
resources:
- cronjobs
sideEffects: None
timeoutSeconds: 10
reinvocationPolicy: IfNeeded
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: validating-webhook-configuration
webhooks:
- admissionReviewVersions:
- v1
- v1beta1
clientConfig:
service:
name: DEAD
namespace: BEEF
path: /validate-testdata-kubebuilder-io-v1-cronjob
port: 1234
failurePolicy: Fail
matchPolicy: Equivalent
name: validation.cronjob.testdata.kubebuilder.io
rules:
- apiGroups:
- testdata.kubebuiler.io
apiVersions:
- v1
operations:
- CREATE
- UPDATE
resources:
- cronjobs
sideEffects: None
timeoutSeconds: 10
- admissionReviewVersions:
- v1
- v1beta1
clientConfig:
service:
name: webhook-service
namespace: system
path: /validate-testdata-kubebuilder-io-v1-cronjob
failurePolicy: Fail
matchPolicy: Equivalent
name: validation.cronjob.testdata.kubebuilder.io
rules:
- apiGroups:
- testdata.kubebuiler.io
apiVersions:
- v1
operations:
- CREATE
- UPDATE
resources:
- cronjobs
sideEffects: NoneOnDryRun
timeoutSeconds: 10
50 changes: 50 additions & 0 deletions pkg/webhook/testdata/valid-custom-service/webhook.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
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 cronjob

import (
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/webhook"
)

func (c *CronJob) SetupWebhookWithManager(mgr ctrl.Manager) error {
return ctrl.NewWebhookManagedBy(mgr).
For(c).
Complete()
}

// +kubebuilder:webhook:webhookVersions=v1,verbs=create;update,path=/validate-testdata-kubebuilder-io-v1-cronjob,mutating=false,failurePolicy=fail,matchPolicy=Equivalent,groups=testdata.kubebuiler.io,resources=cronjobs,serviceName=DEAD,serviceNamespace=BEEF,servicePort=1234,versions=v1,name=validation.cronjob.testdata.kubebuilder.io,sideEffects=None,timeoutSeconds=10,admissionReviewVersions=v1;v1beta1
// +kubebuilder:webhook:verbs=create;update,path=/validate-testdata-kubebuilder-io-v1-cronjob,mutating=false,failurePolicy=fail,matchPolicy=Equivalent,groups=testdata.kubebuiler.io,resources=cronjobs,versions=v1,name=validation.cronjob.testdata.kubebuilder.io,sideEffects=NoneOnDryRun,timeoutSeconds=10,admissionReviewVersions=v1;v1beta1
// +kubebuilder:webhook:webhookVersions=v1,verbs=create;update,path=/mutate-testdata-kubebuilder-io-v1-cronjob,mutating=true,failurePolicy=fail,matchPolicy=Equivalent,groups=testdata.kubebuiler.io,resources=cronjobs,serviceName=FOO,versions=v1,name=default.cronjob.testdata.kubebuilder.io,sideEffects=None,timeoutSeconds=10,admissionReviewVersions=v1;v1beta1,reinvocationPolicy=IfNeeded

var _ webhook.Defaulter = &CronJob{}
var _ webhook.Validator = &CronJob{}

func (c *CronJob) Default() {
}

func (c *CronJob) ValidateCreate() error {
return nil
}

func (c *CronJob) ValidateUpdate(_ runtime.Object) error {
return nil
}

func (c *CronJob) ValidateDelete() error {
return nil
}
12 changes: 12 additions & 0 deletions pkg/webhook/zz_generated.markerhelp.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit fd6668c

Please sign in to comment.