Skip to content
This repository was archived by the owner on Apr 17, 2025. It is now read-only.

Commit be92981

Browse files
authored
Merge pull request #39 from yiqigao217/mwh
Add MWH and update VWH on included-namespace label
2 parents ba15870 + f3b3e07 commit be92981

File tree

13 files changed

+304
-72
lines changed

13 files changed

+304
-72
lines changed

cmd/manager/main.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import (
4141
v1a2 "sigs.k8s.io/hierarchical-namespaces/api/v1alpha2"
4242
"sigs.k8s.io/hierarchical-namespaces/internal/config"
4343
"sigs.k8s.io/hierarchical-namespaces/internal/forest"
44+
"sigs.k8s.io/hierarchical-namespaces/internal/mutators"
4445
"sigs.k8s.io/hierarchical-namespaces/internal/reconcilers"
4546
"sigs.k8s.io/hierarchical-namespaces/internal/stats"
4647
"sigs.k8s.io/hierarchical-namespaces/internal/validators"
@@ -213,6 +214,10 @@ func startControllers(mgr ctrl.Manager, certsCreated chan struct{}) {
213214
validators.Create(mgr, f)
214215
}
215216

217+
// Create mutating admission controllers.
218+
setupLog.Info("Registering mutating webhook")
219+
mutators.Create(mgr)
220+
216221
// Create all reconciling controllers
217222
setupLog.Info("Creating controllers", "maxReconciles", maxReconciles)
218223
if err := reconcilers.Create(mgr, f, maxReconciles, false); err != nil {

config/webhook/manifests.yaml

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,32 @@
11

2+
---
3+
apiVersion: admissionregistration.k8s.io/v1
4+
kind: MutatingWebhookConfiguration
5+
metadata:
6+
creationTimestamp: null
7+
name: mutating-webhook-configuration
8+
webhooks:
9+
- admissionReviewVersions:
10+
- v1
11+
clientConfig:
12+
service:
13+
name: webhook-service
14+
namespace: system
15+
path: /mutate-namespace
16+
failurePolicy: Ignore
17+
name: namespacelabel.hnc.x-k8s.io
18+
rules:
19+
- apiGroups:
20+
- ""
21+
apiVersions:
22+
- v1
23+
operations:
24+
- CREATE
25+
- UPDATE
26+
resources:
27+
- namespaces
28+
sideEffects: None
29+
230
---
331
apiVersion: admissionregistration.k8s.io/v1
432
kind: ValidatingWebhookConfiguration
@@ -8,7 +36,6 @@ metadata:
836
webhooks:
937
- admissionReviewVersions:
1038
- v1
11-
- v1beta1
1239
clientConfig:
1340
service:
1441
name: webhook-service
@@ -29,7 +56,6 @@ webhooks:
2956
sideEffects: None
3057
- admissionReviewVersions:
3158
- v1
32-
- v1beta1
3359
clientConfig:
3460
service:
3561
name: webhook-service
@@ -50,7 +76,6 @@ webhooks:
5076
sideEffects: None
5177
- admissionReviewVersions:
5278
- v1
53-
- v1beta1
5479
clientConfig:
5580
service:
5681
name: webhook-service
@@ -72,7 +97,6 @@ webhooks:
7297
sideEffects: None
7398
- admissionReviewVersions:
7499
- v1
75-
- v1beta1
76100
clientConfig:
77101
service:
78102
name: webhook-service
@@ -94,7 +118,6 @@ webhooks:
94118
sideEffects: None
95119
- admissionReviewVersions:
96120
- v1
97-
- v1beta1
98121
clientConfig:
99122
service:
100123
name: webhook-service

internal/mutators/namespace.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package mutators
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"net/http"
7+
8+
"github.com/go-logr/logr"
9+
corev1 "k8s.io/api/core/v1"
10+
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
11+
12+
api "sigs.k8s.io/hierarchical-namespaces/api/v1alpha2"
13+
"sigs.k8s.io/hierarchical-namespaces/internal/config"
14+
)
15+
16+
// NamespaceMutatorServingPath is where the mutator will run. Must be kept in
17+
// sync with the kubebuilder markers below.
18+
const (
19+
NamespaceMutatorServingPath = "/mutate-namespace"
20+
)
21+
22+
// Note: the mutating webhook FAILS OPEN. This means that if the webhook goes
23+
// down, all further changes are allowed. (An empty line has to be kept below
24+
// the kubebuilder marker for the controller-gen to generate manifests.)
25+
//
26+
// +kubebuilder:webhook:admissionReviewVersions=v1,path=/mutate-namespace,mutating=true,failurePolicy=ignore,groups="",resources=namespaces,sideEffects=None,verbs=create;update,versions=v1,name=namespacelabel.hnc.x-k8s.io
27+
28+
type Namespace struct {
29+
Log logr.Logger
30+
decoder *admission.Decoder
31+
}
32+
33+
// Handle implements the mutating webhook.
34+
func (m *Namespace) Handle(ctx context.Context, req admission.Request) admission.Response {
35+
log := m.Log.WithValues("namespace", req.Name)
36+
ns := &corev1.Namespace{}
37+
err := m.decoder.Decode(req, ns)
38+
if err != nil {
39+
return admission.Errored(http.StatusBadRequest, err)
40+
}
41+
42+
m.handle(log, ns)
43+
marshaledNS, err := json.Marshal(ns)
44+
if err != nil {
45+
return admission.Errored(http.StatusInternalServerError, err)
46+
}
47+
return admission.PatchResponseFromRaw(req.Object.Raw, marshaledNS)
48+
}
49+
50+
// handle implements the non-boilerplate logic of this mutator, allowing it to
51+
// be more easily unit tested (ie without constructing a full admission.Request).
52+
// Currently, we only add `included-namespace` label to non-excluded namespaces
53+
// if the label is missing.
54+
func (m *Namespace) handle(log logr.Logger, ns *corev1.Namespace) {
55+
// Early exit if the namespace is excluded.
56+
if config.ExcludedNamespaces[ns.Name] {
57+
return
58+
}
59+
60+
// Add label if the namespace doesn't have it.
61+
if _, hasLabel := ns.Labels[api.LabelIncludedNamespace]; !hasLabel {
62+
if ns.Labels == nil {
63+
ns.Labels = map[string]string{}
64+
}
65+
log.Info("Not an excluded namespace; set included-namespace label.")
66+
ns.Labels[api.LabelIncludedNamespace] = "true"
67+
}
68+
}
69+
70+
// InjectDecoder injects the decoder.
71+
func (m *Namespace) InjectDecoder(d *admission.Decoder) error {
72+
m.decoder = d
73+
return nil
74+
}

internal/mutators/namespace_test.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package mutators
2+
3+
import (
4+
"testing"
5+
6+
. "github.com/onsi/gomega"
7+
corev1 "k8s.io/api/core/v1"
8+
"sigs.k8s.io/controller-runtime/pkg/log/zap"
9+
10+
api "sigs.k8s.io/hierarchical-namespaces/api/v1alpha2"
11+
"sigs.k8s.io/hierarchical-namespaces/internal/config"
12+
)
13+
14+
func TestMutateNamespaceIncludedLabel(t *testing.T) {
15+
m := &Namespace{}
16+
l := zap.New()
17+
config.ExcludedNamespaces = map[string]bool{"excluded": true}
18+
19+
tests := []struct {
20+
name string
21+
nsn string
22+
lval string
23+
expectlval string
24+
}{
25+
{name: "Set label on non-excluded namespace without label", nsn: "a", expectlval: "true"},
26+
{name: "No operation on non-excluded namespace with label (e.g. from a yaml file)", nsn: "a", lval: "true", expectlval: "true"},
27+
{name: "No operation on non-excluded namespace with label with wrong value(e.g. from a yaml file)", nsn: "a", lval: "wrong", expectlval: "wrong"},
28+
{name: "No operation on excluded namespace without label", nsn: "excluded"},
29+
{name: "No operation on excluded namespace with label (e.g. from a yaml file)", nsn: "excluded", lval: "true", expectlval: "true"},
30+
{name: "No operation on excluded namespace with label with wrong value (e.g. from a yaml file)", nsn: "excluded", lval: "wrong", expectlval: "wrong"},
31+
}
32+
for _, tc := range tests {
33+
t.Run(tc.name, func(t *testing.T) {
34+
g := NewWithT(t)
35+
36+
nsInst := &corev1.Namespace{}
37+
nsInst.Name = tc.nsn
38+
if tc.lval != "" {
39+
nsInst.SetLabels(map[string]string{api.LabelIncludedNamespace: tc.lval})
40+
}
41+
42+
// Test
43+
m.handle(l, nsInst)
44+
45+
// Report
46+
g.Expect(nsInst.Labels[api.LabelIncludedNamespace]).Should(Equal(tc.expectlval))
47+
})
48+
}
49+
}

internal/mutators/setup.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package mutators
2+
3+
import (
4+
ctrl "sigs.k8s.io/controller-runtime"
5+
"sigs.k8s.io/controller-runtime/pkg/webhook"
6+
)
7+
8+
// Create creates the mutator. This function is called from main.go.
9+
func Create(mgr ctrl.Manager) {
10+
// Create mutator for namespace `included-namespace` label.
11+
mgr.GetWebhookServer().Register(NamespaceMutatorServingPath, &webhook.Admission{Handler: &Namespace{
12+
Log: ctrl.Log.WithName("mutators").WithName("Namespace"),
13+
}})
14+
}

internal/reconcilers/hierarchy_config.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,7 @@ func (r *HierarchyConfigReconciler) syncWithForest(log logr.Logger, nsInst *core
298298
r.syncConditions(log, inst, ns, deletingCRD, hadCrit)
299299

300300
// Sync the tree labels. This should go last since it can depend on the conditions.
301-
nsCustomerLabelUpdated := r.syncLabel(log, nsInst, ns)
301+
nsCustomerLabelUpdated := r.syncTreeLabels(log, nsInst, ns)
302302

303303
return initial || nsCustomerLabelUpdated
304304
}
@@ -461,8 +461,8 @@ func (r *HierarchyConfigReconciler) syncAnchors(log logr.Logger, ns *forest.Name
461461
}
462462
}
463463

464-
// Sync namespace tree labels and other labels. Return true if the labels are updated.
465-
func (r *HierarchyConfigReconciler) syncLabel(log logr.Logger, nsInst *corev1.Namespace, ns *forest.Namespace) bool {
464+
// Sync namespace tree labels. Return true if the labels are updated.
465+
func (r *HierarchyConfigReconciler) syncTreeLabels(log logr.Logger, nsInst *corev1.Namespace, ns *forest.Namespace) bool {
466466
if ns.IsExternal() {
467467
metadata.SetLabel(nsInst, nsInst.Name+api.LabelTreeDepthSuffix, "0")
468468
return false
@@ -504,7 +504,7 @@ func (r *HierarchyConfigReconciler) syncLabel(log logr.Logger, nsInst *corev1.Na
504504
// Update the labels in the forest so that we can quickly access the labels and
505505
// compare if they match the given selector
506506
if ns.SetLabels(nsInst.Labels) {
507-
log.Info("Namespace labels have been updated.")
507+
log.Info("Namespace tree labels have been updated.")
508508
return true
509509
}
510510
return false

internal/validators/anchor.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ const (
2323
// Note: the validating webhook FAILS CLOSE. This means that if the webhook goes down, all further
2424
// changes are forbidden.
2525
//
26-
// +kubebuilder:webhook:admissionReviewVersions=v1;v1beta1,path=/validate-hnc-x-k8s-io-v1alpha2-subnamespaceanchors,mutating=false,failurePolicy=fail,groups="hnc.x-k8s.io",resources=subnamespaceanchors,sideEffects=None,verbs=create;delete,versions=v1alpha2,name=subnamespaceanchors.hnc.x-k8s.io
26+
// +kubebuilder:webhook:admissionReviewVersions=v1,path=/validate-hnc-x-k8s-io-v1alpha2-subnamespaceanchors,mutating=false,failurePolicy=fail,groups="hnc.x-k8s.io",resources=subnamespaceanchors,sideEffects=None,verbs=create;delete,versions=v1alpha2,name=subnamespaceanchors.hnc.x-k8s.io
2727

2828
type Anchor struct {
2929
Log logr.Logger

internal/validators/hierarchy.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ const (
3434
// changes to the hierarchy are forbidden. However, new objects will still be propagated according
3535
// to the existing hierarchy (unless the reconciler is down too).
3636
//
37-
// +kubebuilder:webhook:admissionReviewVersions=v1;v1beta1,path=/validate-hnc-x-k8s-io-v1alpha2-hierarchyconfigurations,mutating=false,failurePolicy=fail,groups="hnc.x-k8s.io",resources=hierarchyconfigurations,sideEffects=None,verbs=create;update,versions=v1alpha2,name=hierarchyconfigurations.hnc.x-k8s.io
37+
// +kubebuilder:webhook:admissionReviewVersions=v1,path=/validate-hnc-x-k8s-io-v1alpha2-hierarchyconfigurations,mutating=false,failurePolicy=fail,groups="hnc.x-k8s.io",resources=hierarchyconfigurations,sideEffects=None,verbs=create;update,versions=v1alpha2,name=hierarchyconfigurations.hnc.x-k8s.io
3838

3939
type Hierarchy struct {
4040
Log logr.Logger

internal/validators/hncconfig.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ const (
2828
// Note: the validating webhook FAILS CLOSE. This means that if the webhook goes down, all further
2929
// changes are denied.
3030
//
31-
// +kubebuilder:webhook:admissionReviewVersions=v1;v1beta1,path=/validate-hnc-x-k8s-io-v1alpha2-hncconfigurations,mutating=false,failurePolicy=fail,groups="hnc.x-k8s.io",resources=hncconfigurations,sideEffects=None,verbs=create;update;delete,versions=v1alpha2,name=hncconfigurations.hnc.x-k8s.io
31+
// +kubebuilder:webhook:admissionReviewVersions=v1,path=/validate-hnc-x-k8s-io-v1alpha2-hncconfigurations,mutating=false,failurePolicy=fail,groups="hnc.x-k8s.io",resources=hncconfigurations,sideEffects=None,verbs=create;update;delete,versions=v1alpha2,name=hncconfigurations.hnc.x-k8s.io
3232

3333
type HNCConfig struct {
3434
Log logr.Logger

0 commit comments

Comments
 (0)