Skip to content

Commit 15b12c5

Browse files
CharlieR-o-o-tSiarhei Rasiukevich
authored and
Siarhei Rasiukevich
committed
feat: namespace metadata sync on creation projectcapsule#1378
Signed-off-by: Siarhei Rasiukevich <[email protected]>
1 parent 8e0b5b9 commit 15b12c5

11 files changed

+498
-482
lines changed

charts/capsule/templates/mutatingwebhookconfiguration.yaml

+2-2
Original file line numberDiff line numberDiff line change
@@ -81,12 +81,12 @@ webhooks:
8181
sideEffects: None
8282
timeoutSeconds: {{ $.Values.webhooks.mutatingWebhooksTimeoutSeconds }}
8383
{{- end }}
84-
{{- with .Values.webhooks.hooks.namespaceOwnerReference }}
84+
{{- with .Values.webhooks.hooks.namespacesPatch }}
8585
- admissionReviewVersions:
8686
- v1
8787
- v1beta1
8888
clientConfig:
89-
{{- include "capsule.webhooks.service" (dict "path" "/namespace-owner-reference" "ctx" $) | nindent 4 }}
89+
{{- include "capsule.webhooks.service" (dict "path" "/namespace-patch" "ctx" $) | nindent 4 }}
9090
failurePolicy: {{ .failurePolicy }}
9191
matchPolicy: Equivalent
9292
name: owner.namespace.projectcapsule.dev

charts/capsule/templates/validatingwebhookconfiguration.yaml

+2-2
Original file line numberDiff line numberDiff line change
@@ -68,12 +68,12 @@ webhooks:
6868
sideEffects: None
6969
timeoutSeconds: {{ $.Values.webhooks.validatingWebhooksTimeoutSeconds }}
7070
{{- end }}
71-
{{ with .Values.webhooks.hooks.namespaces }}
71+
{{ with .Values.webhooks.hooks.namespacesValidation }}
7272
- admissionReviewVersions:
7373
- v1
7474
- v1beta1
7575
clientConfig:
76-
{{- include "capsule.webhooks.service" (dict "path" "/namespaces" "ctx" $) | nindent 4 }}
76+
{{- include "capsule.webhooks.service" (dict "path" "/namespace-validate" "ctx" $) | nindent 4 }}
7777
failurePolicy: {{ .failurePolicy }}
7878
matchPolicy: Equivalent
7979
name: namespaces.projectcapsule.dev

charts/capsule/values.yaml

+3-3
Original file line numberDiff line numberDiff line change
@@ -265,8 +265,6 @@ webhooks:
265265

266266
# Hook Configuration
267267
hooks:
268-
namespaceOwnerReference:
269-
failurePolicy: Fail
270268
cordoning:
271269
failurePolicy: Fail
272270
namespaceSelector:
@@ -279,7 +277,9 @@ webhooks:
279277
matchExpressions:
280278
- key: capsule.clastix.io/tenant
281279
operator: Exists
282-
namespaces:
280+
namespacesPatch:
281+
failurePolicy: Fail
282+
namespacesValidation:
283283
failurePolicy: Fail
284284
networkpolicies:
285285
failurePolicy: Fail

controllers/tenant/namespaces.go

+90-86
Original file line numberDiff line numberDiff line change
@@ -52,93 +52,8 @@ func (r *Manager) syncNamespaceMetadata(ctx context.Context, namespace string, t
5252
return
5353
}
5454

55-
capsuleLabel, _ := utils.GetTypeLabel(&capsulev1beta2.Tenant{})
56-
5755
res, conflictErr = controllerutil.CreateOrUpdate(ctx, r.Client, ns, func() error {
58-
annotations := make(map[string]string)
59-
labels := map[string]string{
60-
"kubernetes.io/metadata.name": namespace,
61-
capsuleLabel: tnt.GetName(),
62-
}
63-
64-
if tnt.Spec.NamespaceOptions != nil && tnt.Spec.NamespaceOptions.AdditionalMetadata != nil {
65-
for k, v := range tnt.Spec.NamespaceOptions.AdditionalMetadata.Annotations {
66-
annotations[k] = v
67-
}
68-
}
69-
70-
if tnt.Spec.NamespaceOptions != nil && tnt.Spec.NamespaceOptions.AdditionalMetadata != nil {
71-
for k, v := range tnt.Spec.NamespaceOptions.AdditionalMetadata.Labels {
72-
labels[k] = v
73-
}
74-
}
75-
76-
if tnt.Spec.NodeSelector != nil {
77-
annotations = utils.BuildNodeSelector(tnt, annotations)
78-
}
79-
80-
if tnt.Spec.IngressOptions.AllowedClasses != nil {
81-
if len(tnt.Spec.IngressOptions.AllowedClasses.Exact) > 0 {
82-
annotations[AvailableIngressClassesAnnotation] = strings.Join(tnt.Spec.IngressOptions.AllowedClasses.Exact, ",")
83-
}
84-
85-
if len(tnt.Spec.IngressOptions.AllowedClasses.Regex) > 0 {
86-
annotations[AvailableIngressClassesRegexpAnnotation] = tnt.Spec.IngressOptions.AllowedClasses.Regex
87-
}
88-
}
89-
90-
if tnt.Spec.StorageClasses != nil {
91-
if len(tnt.Spec.StorageClasses.Exact) > 0 {
92-
annotations[AvailableStorageClassesAnnotation] = strings.Join(tnt.Spec.StorageClasses.Exact, ",")
93-
}
94-
95-
if len(tnt.Spec.StorageClasses.Regex) > 0 {
96-
annotations[AvailableStorageClassesRegexpAnnotation] = tnt.Spec.StorageClasses.Regex
97-
}
98-
}
99-
100-
if tnt.Spec.ContainerRegistries != nil {
101-
if len(tnt.Spec.ContainerRegistries.Exact) > 0 {
102-
annotations[AllowedRegistriesAnnotation] = strings.Join(tnt.Spec.ContainerRegistries.Exact, ",")
103-
}
104-
105-
if len(tnt.Spec.ContainerRegistries.Regex) > 0 {
106-
annotations[AllowedRegistriesRegexpAnnotation] = tnt.Spec.ContainerRegistries.Regex
107-
}
108-
}
109-
110-
if value, ok := tnt.Annotations[api.ForbiddenNamespaceLabelsAnnotation]; ok {
111-
annotations[api.ForbiddenNamespaceLabelsAnnotation] = value
112-
}
113-
114-
if value, ok := tnt.Annotations[api.ForbiddenNamespaceLabelsRegexpAnnotation]; ok {
115-
annotations[api.ForbiddenNamespaceLabelsRegexpAnnotation] = value
116-
}
117-
118-
if value, ok := tnt.Annotations[api.ForbiddenNamespaceAnnotationsAnnotation]; ok {
119-
annotations[api.ForbiddenNamespaceAnnotationsAnnotation] = value
120-
}
121-
122-
if value, ok := tnt.Annotations[api.ForbiddenNamespaceAnnotationsRegexpAnnotation]; ok {
123-
annotations[api.ForbiddenNamespaceAnnotationsRegexpAnnotation] = value
124-
}
125-
126-
if ns.Annotations == nil {
127-
ns.SetAnnotations(annotations)
128-
} else {
129-
for k, v := range annotations {
130-
ns.Annotations[k] = v
131-
}
132-
}
133-
134-
if ns.Labels == nil {
135-
ns.SetLabels(labels)
136-
} else {
137-
for k, v := range labels {
138-
ns.Labels[k] = v
139-
}
140-
}
141-
56+
SyncNamespaceMetadata(tnt, ns)
14257
return nil
14358
})
14459

@@ -185,3 +100,92 @@ func (r *Manager) collectNamespaces(ctx context.Context, tenant *capsulev1beta2.
185100
return
186101
})
187102
}
103+
104+
// SyncNamespaceMetadata sync namespace metadata according to tenant spec.
105+
func SyncNamespaceMetadata(tnt *capsulev1beta2.Tenant, ns *corev1.Namespace) {
106+
capsuleLabel, _ := utils.GetTypeLabel(&capsulev1beta2.Tenant{})
107+
108+
annotations := make(map[string]string)
109+
labels := map[string]string{
110+
"kubernetes.io/metadata.name": ns.GetName(),
111+
capsuleLabel: tnt.GetName(),
112+
}
113+
114+
if tnt.Spec.NamespaceOptions != nil && tnt.Spec.NamespaceOptions.AdditionalMetadata != nil {
115+
for k, v := range tnt.Spec.NamespaceOptions.AdditionalMetadata.Annotations {
116+
annotations[k] = v
117+
}
118+
}
119+
120+
if tnt.Spec.NamespaceOptions != nil && tnt.Spec.NamespaceOptions.AdditionalMetadata != nil {
121+
for k, v := range tnt.Spec.NamespaceOptions.AdditionalMetadata.Labels {
122+
labels[k] = v
123+
}
124+
}
125+
126+
if tnt.Spec.NodeSelector != nil {
127+
annotations = utils.BuildNodeSelector(tnt, annotations)
128+
}
129+
130+
if tnt.Spec.IngressOptions.AllowedClasses != nil {
131+
if len(tnt.Spec.IngressOptions.AllowedClasses.Exact) > 0 {
132+
annotations[AvailableIngressClassesAnnotation] = strings.Join(tnt.Spec.IngressOptions.AllowedClasses.Exact, ",")
133+
}
134+
135+
if len(tnt.Spec.IngressOptions.AllowedClasses.Regex) > 0 {
136+
annotations[AvailableIngressClassesRegexpAnnotation] = tnt.Spec.IngressOptions.AllowedClasses.Regex
137+
}
138+
}
139+
140+
if tnt.Spec.StorageClasses != nil {
141+
if len(tnt.Spec.StorageClasses.Exact) > 0 {
142+
annotations[AvailableStorageClassesAnnotation] = strings.Join(tnt.Spec.StorageClasses.Exact, ",")
143+
}
144+
145+
if len(tnt.Spec.StorageClasses.Regex) > 0 {
146+
annotations[AvailableStorageClassesRegexpAnnotation] = tnt.Spec.StorageClasses.Regex
147+
}
148+
}
149+
150+
if tnt.Spec.ContainerRegistries != nil {
151+
if len(tnt.Spec.ContainerRegistries.Exact) > 0 {
152+
annotations[AllowedRegistriesAnnotation] = strings.Join(tnt.Spec.ContainerRegistries.Exact, ",")
153+
}
154+
155+
if len(tnt.Spec.ContainerRegistries.Regex) > 0 {
156+
annotations[AllowedRegistriesRegexpAnnotation] = tnt.Spec.ContainerRegistries.Regex
157+
}
158+
}
159+
160+
if value, ok := tnt.Annotations[api.ForbiddenNamespaceLabelsAnnotation]; ok {
161+
annotations[api.ForbiddenNamespaceLabelsAnnotation] = value
162+
}
163+
164+
if value, ok := tnt.Annotations[api.ForbiddenNamespaceLabelsRegexpAnnotation]; ok {
165+
annotations[api.ForbiddenNamespaceLabelsRegexpAnnotation] = value
166+
}
167+
168+
if value, ok := tnt.Annotations[api.ForbiddenNamespaceAnnotationsAnnotation]; ok {
169+
annotations[api.ForbiddenNamespaceAnnotationsAnnotation] = value
170+
}
171+
172+
if value, ok := tnt.Annotations[api.ForbiddenNamespaceAnnotationsRegexpAnnotation]; ok {
173+
annotations[api.ForbiddenNamespaceAnnotationsRegexpAnnotation] = value
174+
}
175+
176+
if ns.Annotations == nil {
177+
ns.SetAnnotations(annotations)
178+
} else {
179+
for k, v := range annotations {
180+
ns.Annotations[k] = v
181+
}
182+
}
183+
184+
if ns.Labels == nil {
185+
ns.SetLabels(labels)
186+
} else {
187+
for k, v := range labels {
188+
ns.Labels[k] = v
189+
}
190+
}
191+
}
+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
//go:build e2e
2+
3+
// Copyright 2020-2023 Project Capsule Authors.
4+
// SPDX-License-Identifier: Apache-2.0
5+
6+
package e2e
7+
8+
import (
9+
"context"
10+
. "github.com/onsi/ginkgo/v2"
11+
. "github.com/onsi/gomega"
12+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
13+
"k8s.io/apimachinery/pkg/types"
14+
15+
capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2"
16+
"github.com/projectcapsule/capsule/pkg/api"
17+
)
18+
19+
var _ = Describe("creating a Namespace for a Tenant with additional metadata", func() {
20+
tnt := &capsulev1beta2.Tenant{
21+
ObjectMeta: metav1.ObjectMeta{
22+
Name: "tenant-metadata",
23+
OwnerReferences: []metav1.OwnerReference{
24+
{
25+
APIVersion: "cap",
26+
Kind: "dummy",
27+
Name: "tenant-metadata",
28+
UID: "tenant-metadata",
29+
},
30+
},
31+
},
32+
Spec: capsulev1beta2.TenantSpec{
33+
Owners: capsulev1beta2.OwnerListSpec{
34+
{
35+
Name: "gatsby",
36+
Kind: "User",
37+
},
38+
},
39+
NamespaceOptions: &capsulev1beta2.NamespaceOptions{
40+
AdditionalMetadata: &api.AdditionalMetadataSpec{
41+
Labels: map[string]string{
42+
"k8s.io/custom-label": "foo",
43+
"clastix.io/custom-label": "bar",
44+
},
45+
Annotations: map[string]string{
46+
"k8s.io/custom-annotation": "bizz",
47+
"clastix.io/custom-annotation": "buzz",
48+
},
49+
},
50+
},
51+
},
52+
}
53+
54+
JustBeforeEach(func() {
55+
EventuallyCreation(func() error {
56+
return k8sClient.Create(context.TODO(), tnt)
57+
}).Should(Succeed())
58+
})
59+
JustAfterEach(func() {
60+
Expect(k8sClient.Delete(context.TODO(), tnt)).Should(Succeed())
61+
})
62+
63+
It("should contain Namespace metadata after tenant update", func() {
64+
ns := NewNamespace("")
65+
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
66+
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
67+
68+
By("checking labels", func() {
69+
Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: ns.GetName()}, ns)).Should(Succeed())
70+
Expect(ns.Labels).ShouldNot(HaveKeyWithValue("newlabel", "foobazbar"))
71+
72+
tnt.Spec.NamespaceOptions.AdditionalMetadata.Labels["newlabel"] = "foobazbar"
73+
Expect(k8sClient.Update(context.TODO(), tnt)).Should(Succeed())
74+
75+
Eventually(func() (ok bool) {
76+
Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: ns.GetName()}, ns)).Should(Succeed())
77+
ok, _ = Equal(ns.Labels["newlabel"]).Match("foobazbar")
78+
return
79+
}, defaultTimeoutInterval, defaultPollInterval).Should(BeTrue())
80+
})
81+
By("checking annotations", func() {
82+
Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: ns.GetName()}, ns)).Should(Succeed())
83+
Expect(ns.Labels).ShouldNot(HaveKeyWithValue("newannotation", "foobazbar"))
84+
85+
tnt.Spec.NamespaceOptions.AdditionalMetadata.Annotations["newannotation"] = "foobazbar"
86+
Expect(k8sClient.Update(context.TODO(), tnt)).Should(Succeed())
87+
88+
Eventually(func() (ok bool) {
89+
Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: ns.GetName()}, ns)).Should(Succeed())
90+
ok, _ = Equal(ns.Annotations["newannotation"]).Match("foobazbar")
91+
return
92+
}, defaultTimeoutInterval, defaultPollInterval).Should(BeTrue())
93+
})
94+
})
95+
})

e2e/namespace_additional_metadata_test.go e2e/namespace_metadata_webhook_test.go

+16-14
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ var _ = Describe("creating a Namespace for a Tenant with additional metadata", f
3737
Kind: "User",
3838
},
3939
},
40+
NodeSelector: map[string]string{
41+
"node": "foobar",
42+
},
4043
NamespaceOptions: &capsulev1beta2.NamespaceOptions{
4144
AdditionalMetadata: &api.AdditionalMetadataSpec{
4245
Labels: map[string]string{
@@ -67,26 +70,25 @@ var _ = Describe("creating a Namespace for a Tenant with additional metadata", f
6770
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
6871

6972
By("checking additional labels", func() {
70-
Eventually(func() (ok bool) {
7173
Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: ns.GetName()}, ns)).Should(Succeed())
74+
7275
for k, v := range tnt.Spec.NamespaceOptions.AdditionalMetadata.Labels {
73-
if ok, _ = HaveKeyWithValue(k, v).Match(ns.Labels); !ok {
74-
return
75-
}
76+
Expect(ns.Labels).To(HaveKeyWithValue(k, v))
7677
}
7778
return
78-
}, defaultTimeoutInterval, defaultPollInterval).Should(BeTrue())
7979
})
8080
By("checking additional annotations", func() {
81-
Eventually(func() (ok bool) {
82-
Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: ns.GetName()}, ns)).Should(Succeed())
83-
for k, v := range tnt.Spec.NamespaceOptions.AdditionalMetadata.Annotations {
84-
if ok, _ = HaveKeyWithValue(k, v).Match(ns.Annotations); !ok {
85-
return
86-
}
87-
}
88-
return
89-
}, defaultTimeoutInterval, defaultPollInterval).Should(BeTrue())
81+
Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: ns.GetName()}, ns)).Should(Succeed())
82+
83+
for k, v := range tnt.Spec.NamespaceOptions.AdditionalMetadata.Annotations {
84+
Expect(ns.Annotations).To(HaveKeyWithValue(k, v))
85+
}
86+
return
87+
})
88+
By("checking namespace node-selector annotation", func() {
89+
Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: ns.GetName()}, ns)).Should(Succeed())
90+
Expect(ns.Annotations).Should(HaveKeyWithValue("scheduler.alpha.kubernetes.io/node-selector", "node=foobar"))
91+
return
9092
})
9193
})
9294
})

0 commit comments

Comments
 (0)