Skip to content

Commit c7b9156

Browse files
authored
Merge pull request #824 from rikatz/add-new-webhook-markers
✨ Add url marker for webhook manifests
2 parents dba4afa + 3e161ca commit c7b9156

File tree

7 files changed

+430
-10
lines changed

7 files changed

+430
-10
lines changed

pkg/webhook/parser.go

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ type Config struct {
108108
// are substituted for hyphens. For example, a validating webhook path for type
109109
// batch.tutorial.kubebuilder.io/v1,Kind=CronJob would be
110110
// /validate-batch-tutorial-kubebuilder-io-v1-cronjob
111-
Path string
111+
Path string `marker:"path,optional"`
112112

113113
// WebhookVersions specifies the target API versions of the {Mutating,Validating}WebhookConfiguration objects
114114
// itself to generate. The only supported value is v1. Defaults to v1.
@@ -125,6 +125,14 @@ type Config struct {
125125
// an object, and mutating webhooks can specify a reinvocationPolicy to control
126126
// whether they are reinvoked as well.
127127
ReinvocationPolicy string `marker:"reinvocationPolicy,optional"`
128+
129+
// URL allows mutating webhooks configuration to specify an external URL when generating
130+
// the manifests, instead of using the internal service communication. Should be in format of
131+
// https://address:port/path
132+
// When this option is specified, the serviceConfig.Service is removed from webhook the manifest.
133+
// The URL configuration should be between quotes.
134+
// `url` cannot be specified when `path` is specified.
135+
URL string `marker:"url,optional"`
128136
}
129137

130138
// verbToAPIVariant converts a marker's verb to the proper value for the API.
@@ -157,12 +165,17 @@ func (c Config) ToMutatingWebhook() (admissionregv1.MutatingWebhook, error) {
157165
return admissionregv1.MutatingWebhook{}, err
158166
}
159167

168+
clientConfig, err := c.clientConfig()
169+
if err != nil {
170+
return admissionregv1.MutatingWebhook{}, err
171+
}
172+
160173
return admissionregv1.MutatingWebhook{
161174
Name: c.Name,
162175
Rules: c.rules(),
163176
FailurePolicy: c.failurePolicy(),
164177
MatchPolicy: matchPolicy,
165-
ClientConfig: c.clientConfig(),
178+
ClientConfig: clientConfig,
166179
SideEffects: c.sideEffects(),
167180
TimeoutSeconds: c.timeoutSeconds(),
168181
AdmissionReviewVersions: c.AdmissionReviewVersions,
@@ -181,12 +194,17 @@ func (c Config) ToValidatingWebhook() (admissionregv1.ValidatingWebhook, error)
181194
return admissionregv1.ValidatingWebhook{}, err
182195
}
183196

197+
clientConfig, err := c.clientConfig()
198+
if err != nil {
199+
return admissionregv1.ValidatingWebhook{}, err
200+
}
201+
184202
return admissionregv1.ValidatingWebhook{
185203
Name: c.Name,
186204
Rules: c.rules(),
187205
FailurePolicy: c.failurePolicy(),
188206
MatchPolicy: matchPolicy,
189-
ClientConfig: c.clientConfig(),
207+
ClientConfig: clientConfig,
190208
SideEffects: c.sideEffects(),
191209
TimeoutSeconds: c.timeoutSeconds(),
192210
AdmissionReviewVersions: c.AdmissionReviewVersions,
@@ -251,15 +269,27 @@ func (c Config) matchPolicy() (*admissionregv1.MatchPolicyType, error) {
251269
}
252270

253271
// clientConfig returns the client config for a webhook.
254-
func (c Config) clientConfig() admissionregv1.WebhookClientConfig {
272+
func (c Config) clientConfig() (admissionregv1.WebhookClientConfig, error) {
273+
if (c.Path != "" && c.URL != "") || (c.Path == "" && c.URL == "") {
274+
return admissionregv1.WebhookClientConfig{}, fmt.Errorf("`url` or `path` markers are required and mutually exclusive")
275+
}
276+
255277
path := c.Path
256-
return admissionregv1.WebhookClientConfig{
257-
Service: &admissionregv1.ServiceReference{
258-
Name: "webhook-service",
259-
Namespace: "system",
260-
Path: &path,
261-
},
278+
if path != "" {
279+
return admissionregv1.WebhookClientConfig{
280+
Service: &admissionregv1.ServiceReference{
281+
Name: "webhook-service",
282+
Namespace: "system",
283+
Path: &path,
284+
},
285+
}, nil
262286
}
287+
288+
url := c.URL
289+
return admissionregv1.WebhookClientConfig{
290+
URL: &url,
291+
}, nil
292+
263293
}
264294

265295
// sideEffects returns the sideEffects config for a webhook.

pkg/webhook/parser_integration_test.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,80 @@ var _ = Describe("Webhook Generation From Parsing to CustomResourceDefinition",
253253
assertSame(actualManifest, expectedManifest)
254254
}
255255
})
256+
257+
It("should properly generate the webhook definition with url instead of service", func() {
258+
By("switching into testdata to appease go modules")
259+
cwd, err := os.Getwd()
260+
Expect(err).NotTo(HaveOccurred())
261+
Expect(os.Chdir("./testdata/valid-url")).To(Succeed()) // go modules are directory-sensitive
262+
defer func() { Expect(os.Chdir(cwd)).To(Succeed()) }()
263+
264+
By("loading the roots")
265+
pkgs, err := loader.LoadRoots(".")
266+
Expect(err).NotTo(HaveOccurred())
267+
Expect(pkgs).To(HaveLen(1))
268+
269+
By("setting up the parser")
270+
reg := &markers.Registry{}
271+
Expect(reg.Register(webhook.ConfigDefinition)).To(Succeed())
272+
273+
By("requesting that the manifest be generated")
274+
outputDir, err := ioutil.TempDir("", "webhook-integration-test")
275+
Expect(err).NotTo(HaveOccurred())
276+
defer os.RemoveAll(outputDir)
277+
genCtx := &genall.GenerationContext{
278+
Collector: &markers.Collector{Registry: reg},
279+
Roots: pkgs,
280+
OutputRule: genall.OutputToDirectory(outputDir),
281+
}
282+
Expect(webhook.Generator{}.Generate(genCtx)).To(Succeed())
283+
for _, r := range genCtx.Roots {
284+
Expect(r.Errors).To(HaveLen(0))
285+
}
286+
287+
By("loading the generated v1 YAML")
288+
actualFile, err := ioutil.ReadFile(path.Join(outputDir, "manifests.yaml"))
289+
Expect(err).NotTo(HaveOccurred())
290+
actualMutating, actualValidating := unmarshalBothV1(actualFile)
291+
292+
By("loading the desired v1 YAML")
293+
expectedFile, err := ioutil.ReadFile("manifests.yaml")
294+
Expect(err).NotTo(HaveOccurred())
295+
expectedMutating, expectedValidating := unmarshalBothV1(expectedFile)
296+
297+
By("comparing the two")
298+
assertSame(actualMutating, expectedMutating)
299+
assertSame(actualValidating, expectedValidating)
300+
})
301+
302+
It("should fail to generate when both path and url are set", func() {
303+
By("switching into testdata to appease go modules")
304+
cwd, err := os.Getwd()
305+
Expect(err).NotTo(HaveOccurred())
306+
Expect(os.Chdir("./testdata/invalid-path-and-url")).To(Succeed()) // go modules are directory-sensitive
307+
defer func() { Expect(os.Chdir(cwd)).To(Succeed()) }()
308+
309+
By("loading the roots")
310+
pkgs, err := loader.LoadRoots(".")
311+
Expect(err).NotTo(HaveOccurred())
312+
Expect(pkgs).To(HaveLen(1))
313+
314+
By("setting up the parser")
315+
reg := &markers.Registry{}
316+
Expect(reg.Register(webhook.ConfigDefinition)).To(Succeed())
317+
318+
By("requesting that the manifest be generated")
319+
outputDir, err := ioutil.TempDir("", "webhook-integration-test")
320+
Expect(err).NotTo(HaveOccurred())
321+
defer os.RemoveAll(outputDir)
322+
genCtx := &genall.GenerationContext{
323+
Collector: &markers.Collector{Registry: reg},
324+
Roots: pkgs,
325+
OutputRule: genall.OutputToDirectory(outputDir),
326+
}
327+
err = webhook.Generator{}.Generate(genCtx)
328+
Expect(err).To(HaveOccurred())
329+
})
256330
})
257331

258332
func unmarshalBothV1(in []byte) (mutating admissionregv1.MutatingWebhookConfiguration, validating admissionregv1.ValidatingWebhookConfiguration) {
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
7+
http://www.apache.org/licenses/LICENSE-2.0
8+
9+
Unless required by applicable law or agreed to in writing, software
10+
distributed under the License is distributed on an "AS IS" BASIS,
11+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
See the License for the specific language governing permissions and
13+
limitations under the License.
14+
*/
15+
16+
//go:generate ../../../../.run-controller-gen.sh webhook paths=. output:dir=.
17+
18+
// +groupName=testdata.kubebuilder.io
19+
// +versionName=v1
20+
package cronjob
21+
22+
import (
23+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
24+
)
25+
26+
// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
27+
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
28+
29+
// CronJobSpec defines the desired state of CronJob
30+
type CronJobSpec struct {
31+
// The schedule in Cron format, see https://en.wikipedia.org/wiki/Cron.
32+
Schedule string `json:"schedule"`
33+
}
34+
35+
// CronJobStatus defines the observed state of CronJob
36+
type CronJobStatus struct {
37+
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
38+
// Important: Run "make" to regenerate code after modifying this file
39+
40+
// Information when was the last time the job was successfully scheduled.
41+
// +optional
42+
LastScheduleTime *metav1.Time `json:"lastScheduleTime,omitempty"`
43+
}
44+
45+
// +kubebuilder:object:root=true
46+
// +kubebuilder:subresource:status
47+
// +kubebuilder:resource:singular=mycronjob
48+
49+
// CronJob is the Schema for the cronjobs API
50+
type CronJob struct {
51+
/*
52+
*/
53+
metav1.TypeMeta `json:",inline"`
54+
metav1.ObjectMeta `json:"metadata,omitempty"`
55+
56+
Spec CronJobSpec `json:"spec,omitempty"`
57+
Status CronJobStatus `json:"status,omitempty"`
58+
}
59+
60+
// +kubebuilder:object:root=true
61+
62+
// CronJobList contains a list of CronJob
63+
type CronJobList struct {
64+
metav1.TypeMeta `json:",inline"`
65+
metav1.ListMeta `json:"metadata,omitempty"`
66+
Items []CronJob `json:"items"`
67+
}
68+
69+
func init() {
70+
SchemeBuilder.Register(&CronJob{}, &CronJobList{})
71+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
7+
http://www.apache.org/licenses/LICENSE-2.0
8+
9+
Unless required by applicable law or agreed to in writing, software
10+
distributed under the License is distributed on an "AS IS" BASIS,
11+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
See the License for the specific language governing permissions and
13+
limitations under the License.
14+
*/
15+
16+
package cronjob
17+
18+
import (
19+
"k8s.io/apimachinery/pkg/runtime"
20+
ctrl "sigs.k8s.io/controller-runtime"
21+
"sigs.k8s.io/controller-runtime/pkg/webhook"
22+
)
23+
24+
func (c *CronJob) SetupWebhookWithManager(mgr ctrl.Manager) error {
25+
return ctrl.NewWebhookManagedBy(mgr).
26+
For(c).
27+
Complete()
28+
}
29+
30+
// +kubebuilder:webhook:url="https://anothersomewebhook:9443/validate-testdata-kubebuilder-io-v1-cronjob",path="/somepath",verbs=create;update,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
31+
32+
var _ webhook.Defaulter = &CronJob{}
33+
var _ webhook.Validator = &CronJob{}
34+
35+
func (c *CronJob) Default() {
36+
}
37+
38+
func (c *CronJob) ValidateCreate() error {
39+
return nil
40+
}
41+
42+
func (c *CronJob) ValidateUpdate(_ runtime.Object) error {
43+
return nil
44+
}
45+
46+
func (c *CronJob) ValidateDelete() error {
47+
return nil
48+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
7+
http://www.apache.org/licenses/LICENSE-2.0
8+
9+
Unless required by applicable law or agreed to in writing, software
10+
distributed under the License is distributed on an "AS IS" BASIS,
11+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
See the License for the specific language governing permissions and
13+
limitations under the License.
14+
*/
15+
16+
//go:generate ../../../../.run-controller-gen.sh webhook paths=. output:dir=.
17+
18+
// +groupName=testdata.kubebuilder.io
19+
// +versionName=v1
20+
package cronjob
21+
22+
import (
23+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
24+
)
25+
26+
// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
27+
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
28+
29+
// CronJobSpec defines the desired state of CronJob
30+
type CronJobSpec struct {
31+
// The schedule in Cron format, see https://en.wikipedia.org/wiki/Cron.
32+
Schedule string `json:"schedule"`
33+
}
34+
35+
// CronJobStatus defines the observed state of CronJob
36+
type CronJobStatus struct {
37+
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
38+
// Important: Run "make" to regenerate code after modifying this file
39+
40+
// Information when was the last time the job was successfully scheduled.
41+
// +optional
42+
LastScheduleTime *metav1.Time `json:"lastScheduleTime,omitempty"`
43+
}
44+
45+
// +kubebuilder:object:root=true
46+
// +kubebuilder:subresource:status
47+
// +kubebuilder:resource:singular=mycronjob
48+
49+
// CronJob is the Schema for the cronjobs API
50+
type CronJob struct {
51+
/*
52+
*/
53+
metav1.TypeMeta `json:",inline"`
54+
metav1.ObjectMeta `json:"metadata,omitempty"`
55+
56+
Spec CronJobSpec `json:"spec,omitempty"`
57+
Status CronJobStatus `json:"status,omitempty"`
58+
}
59+
60+
// +kubebuilder:object:root=true
61+
62+
// CronJobList contains a list of CronJob
63+
type CronJobList struct {
64+
metav1.TypeMeta `json:",inline"`
65+
metav1.ListMeta `json:"metadata,omitempty"`
66+
Items []CronJob `json:"items"`
67+
}
68+
69+
func init() {
70+
SchemeBuilder.Register(&CronJob{}, &CronJobList{})
71+
}

0 commit comments

Comments
 (0)