Skip to content

Commit 7ee3ea2

Browse files
authored
Merge pull request #128 from mengqiy/admissionwebhook
Rebase admissionwebhook branch on top of master
2 parents 2d4d049 + 68e5598 commit 7ee3ea2

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+4782
-2008
lines changed

Gopkg.lock

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

example/controller.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
Copyright 2018 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package main
18+
19+
import (
20+
"context"
21+
22+
"github.com/go-logr/logr"
23+
24+
appsv1 "k8s.io/api/apps/v1"
25+
"k8s.io/apimachinery/pkg/api/errors"
26+
"sigs.k8s.io/controller-runtime/pkg/client"
27+
"sigs.k8s.io/controller-runtime/pkg/reconcile"
28+
)
29+
30+
// reconcileReplicaSet reconciles ReplicaSets
31+
type reconcileReplicaSet struct {
32+
// client can be used to retrieve objects from the APIServer.
33+
client client.Client
34+
log logr.Logger
35+
}
36+
37+
// Implement reconcile.Reconciler so the controller can reconcile objects
38+
var _ reconcile.Reconciler = &reconcileReplicaSet{}
39+
40+
func (r *reconcileReplicaSet) Reconcile(request reconcile.Request) (reconcile.Result, error) {
41+
// set up a convinient log object so we don't have to type request over and over again
42+
log := r.log.WithValues("request", request)
43+
44+
// Fetch the ReplicaSet from the cache
45+
rs := &appsv1.ReplicaSet{}
46+
err := r.client.Get(context.TODO(), request.NamespacedName, rs)
47+
if errors.IsNotFound(err) {
48+
log.Error(nil, "Could not find ReplicaSet")
49+
return reconcile.Result{}, nil
50+
}
51+
52+
if err != nil {
53+
log.Error(err, "Could not fetch ReplicaSet")
54+
return reconcile.Result{}, err
55+
}
56+
57+
// Print the ReplicaSet
58+
log.Info("Reconciling ReplicaSet", "container name", rs.Spec.Template.Spec.Containers[0].Name)
59+
60+
// Set the label if it is missing
61+
if rs.Labels == nil {
62+
rs.Labels = map[string]string{}
63+
}
64+
if rs.Labels["hello"] == "world" {
65+
return reconcile.Result{}, nil
66+
}
67+
68+
// Update the ReplicaSet
69+
rs.Labels["hello"] = "world"
70+
err = r.client.Update(context.TODO(), rs)
71+
if err != nil {
72+
log.Error(err, "Could not write ReplicaSet")
73+
return reconcile.Result{}, err
74+
}
75+
76+
return reconcile.Result{}, nil
77+
}

example/main.go

Lines changed: 57 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -17,29 +17,27 @@ limitations under the License.
1717
package main
1818

1919
import (
20-
"context"
2120
"flag"
2221
"os"
2322

24-
"github.com/go-logr/logr"
23+
admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1"
2524
appsv1 "k8s.io/api/apps/v1"
2625
corev1 "k8s.io/api/core/v1"
27-
"k8s.io/apimachinery/pkg/api/errors"
26+
apitypes "k8s.io/apimachinery/pkg/types"
2827
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
29-
"sigs.k8s.io/controller-runtime/pkg/client"
3028
"sigs.k8s.io/controller-runtime/pkg/client/config"
3129
"sigs.k8s.io/controller-runtime/pkg/controller"
3230
"sigs.k8s.io/controller-runtime/pkg/handler"
3331
"sigs.k8s.io/controller-runtime/pkg/manager"
34-
"sigs.k8s.io/controller-runtime/pkg/reconcile"
3532
logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
3633
"sigs.k8s.io/controller-runtime/pkg/runtime/signals"
3734
"sigs.k8s.io/controller-runtime/pkg/source"
35+
"sigs.k8s.io/controller-runtime/pkg/webhook"
36+
"sigs.k8s.io/controller-runtime/pkg/webhook/admission/builder"
37+
"sigs.k8s.io/controller-runtime/pkg/webhook/types"
3838
)
3939

40-
var (
41-
log = logf.Log.WithName("example-controller")
42-
)
40+
var log = logf.Log.WithName("example-controller")
4341

4442
func main() {
4543
flag.Parse()
@@ -75,56 +73,65 @@ func main() {
7573
os.Exit(1)
7674
}
7775

78-
if err := mgr.Start(signals.SetupSignalHandler()); err != nil {
79-
entryLog.Error(err, "unable to run manager")
76+
// Setup webhooks
77+
mutatingWebhook, err := builder.NewWebhookBuilder().
78+
Name("mutating.k8s.io").
79+
Type(types.WebhookTypeMutating).
80+
Path("/mutating-pods").
81+
Operations(admissionregistrationv1beta1.Create, admissionregistrationv1beta1.Update).
82+
WithManager(mgr).
83+
ForType(&corev1.Pod{}).
84+
Build(&podAnnotator{client: mgr.GetClient(), decoder: mgr.GetAdmissionDecoder()})
85+
if err != nil {
86+
entryLog.Error(err, "unable to setup mutating webhook")
8087
os.Exit(1)
8188
}
82-
}
83-
84-
// reconcileReplicaSet reconciles ReplicaSets
85-
type reconcileReplicaSet struct {
86-
client client.Client
87-
log logr.Logger
88-
}
89-
90-
// Implement reconcile.Reconciler so the controller can reconcile objects
91-
var _ reconcile.Reconciler = &reconcileReplicaSet{}
92-
93-
func (r *reconcileReplicaSet) Reconcile(request reconcile.Request) (reconcile.Result, error) {
94-
// set up a convinient log object so we don't have to type request over and over again
95-
log := r.log.WithValues("request", request)
96-
97-
// Fetch the ReplicaSet from the cache
98-
rs := &appsv1.ReplicaSet{}
99-
err := r.client.Get(context.TODO(), request.NamespacedName, rs)
100-
if errors.IsNotFound(err) {
101-
log.Error(nil, "Could not find ReplicaSet")
102-
return reconcile.Result{}, nil
103-
}
10489

90+
validatingWebhook, err := builder.NewWebhookBuilder().
91+
Name("validating.k8s.io").
92+
Type(types.WebhookTypeValidating).
93+
Path("/validating-pods").
94+
Operations(admissionregistrationv1beta1.Create, admissionregistrationv1beta1.Update).
95+
WithManager(mgr).
96+
ForType(&corev1.Pod{}).
97+
Build(&podValidator{client: mgr.GetClient(), decoder: mgr.GetAdmissionDecoder()})
10598
if err != nil {
106-
log.Error(err, "Could not fetch ReplicaSet")
107-
return reconcile.Result{}, err
99+
entryLog.Error(err, "unable to setup validating webhook")
100+
os.Exit(1)
108101
}
109102

110-
// Print the ReplicaSet
111-
log.Info("Reconciling ReplicaSet", "container name", rs.Spec.Template.Spec.Containers[0].Name)
112-
113-
// Set the label if it is missing
114-
if rs.Labels == nil {
115-
rs.Labels = map[string]string{}
116-
}
117-
if rs.Labels["hello"] == "world" {
118-
return reconcile.Result{}, nil
103+
as, err := webhook.NewServer("foo-admission-server", mgr, webhook.ServerOptions{
104+
Port: 443,
105+
CertDir: "/tmp/cert",
106+
KVMap: map[string]interface{}{"foo": "bar"},
107+
BootstrapOptions: &webhook.BootstrapOptions{
108+
Secret: &apitypes.NamespacedName{
109+
Namespace: "default",
110+
Name: "foo-admission-server-secret",
111+
},
112+
113+
Service: &webhook.Service{
114+
Namespace: "default",
115+
Name: "foo-admission-server-service",
116+
// Selectors should select the pods that runs this webhook server.
117+
Selectors: map[string]string{
118+
"app": "foo-admission-server",
119+
},
120+
},
121+
},
122+
})
123+
if err != nil {
124+
entryLog.Error(err, "unable to create a new webhook server")
125+
os.Exit(1)
119126
}
120-
121-
// Update the ReplicaSet
122-
rs.Labels["hello"] = "world"
123-
err = r.client.Update(context.TODO(), rs)
127+
err = as.Register(mutatingWebhook, validatingWebhook)
124128
if err != nil {
125-
log.Error(err, "Could not write ReplicaSet")
126-
return reconcile.Result{}, err
129+
entryLog.Error(err, "unable to register webhooks in the admission server")
130+
os.Exit(1)
127131
}
128132

129-
return reconcile.Result{}, nil
133+
if err := mgr.Start(signals.SetupSignalHandler()); err != nil {
134+
entryLog.Error(err, "unable to run manager")
135+
os.Exit(1)
136+
}
130137
}

example/mutatingwebhook.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
Copyright 2018 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package main
18+
19+
import (
20+
"context"
21+
"fmt"
22+
"net/http"
23+
24+
corev1 "k8s.io/api/core/v1"
25+
"sigs.k8s.io/controller-runtime/pkg/client"
26+
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
27+
)
28+
29+
// podAnnotator annotates Pods
30+
type podAnnotator struct {
31+
client client.Client
32+
decoder admission.Decoder
33+
}
34+
35+
// Implement admission.Handler so the controller can handle admission request.
36+
var _ admission.Handler = &podAnnotator{}
37+
38+
// podAnnotator adds an annotation to every incoming pods.
39+
func (a *podAnnotator) Handle(ctx context.Context, req admission.Request) admission.Response {
40+
pod := &corev1.Pod{}
41+
42+
err := a.decoder.Decode(req, pod)
43+
if err != nil {
44+
return admission.ErrorResponse(http.StatusBadRequest, err)
45+
}
46+
copy := pod.DeepCopy()
47+
48+
err = mutatePodsFn(ctx, copy)
49+
if err != nil {
50+
return admission.ErrorResponse(http.StatusInternalServerError, err)
51+
}
52+
return admission.PatchResponse(pod, copy)
53+
}
54+
55+
// mutatePodsFn add an annotation to the given pod
56+
func mutatePodsFn(ctx context.Context, pod *corev1.Pod) error {
57+
v, ok := ctx.Value(admission.StringKey("foo")).(string)
58+
if !ok {
59+
return fmt.Errorf("the value associated with %v is expected to be a string", "foo")
60+
}
61+
anno := pod.GetAnnotations()
62+
anno["example-mutating-admission-webhook"] = v
63+
pod.SetAnnotations(anno)
64+
return nil
65+
}

example/validatingwebhook.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*
2+
Copyright 2018 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package main
18+
19+
import (
20+
"context"
21+
"fmt"
22+
"net/http"
23+
24+
corev1 "k8s.io/api/core/v1"
25+
"sigs.k8s.io/controller-runtime/pkg/client"
26+
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
27+
)
28+
29+
// podValidator validates Pods
30+
type podValidator struct {
31+
client client.Client
32+
decoder admission.Decoder
33+
}
34+
35+
// Implement admission.Handler so the controller can handle admission request.
36+
var _ admission.Handler = &podValidator{}
37+
38+
// podValidator admits a pod iff a specific annotation exists.
39+
func (v *podValidator) Handle(ctx context.Context, req admission.Request) admission.Response {
40+
pod := &corev1.Pod{}
41+
42+
err := v.decoder.Decode(req, pod)
43+
if err != nil {
44+
return admission.ErrorResponse(http.StatusBadRequest, err)
45+
}
46+
47+
allowed, reason, err := validatePodsFn(ctx, pod)
48+
if err != nil {
49+
return admission.ErrorResponse(http.StatusInternalServerError, err)
50+
}
51+
return admission.ValidationResponse(allowed, reason)
52+
}
53+
54+
func validatePodsFn(ctx context.Context, pod *corev1.Pod) (bool, string, error) {
55+
v, ok := ctx.Value(admission.StringKey("foo")).(string)
56+
if !ok {
57+
return false, "",
58+
fmt.Errorf("the value associated with key %q is expected to be a string", v)
59+
}
60+
annotations := pod.GetAnnotations()
61+
key := "example-mutating-admission-webhook"
62+
anno, found := annotations[key]
63+
switch {
64+
case !found:
65+
return found, fmt.Sprintf("failed to find annotation with key: %q", key), nil
66+
case found && anno == v:
67+
return found, "", nil
68+
case found && anno != v:
69+
return false,
70+
fmt.Sprintf("the value associate with key %q is expected to be %q, but got %q", "foo", v, anno), nil
71+
}
72+
return false, "", nil
73+
}

0 commit comments

Comments
 (0)