Skip to content

Commit 078588a

Browse files
MaxFedotovMaksim Fedotov
andauthored
migrating service webhook to controller p1 (#130)
migrating service webhook to controller p2 migrating service webhook to controller p3. add tests Using an abstract reconciler to avoid copy/paste code update tests. remove service_labels webhook. fix bug in sync labels\endpoint func apply review notes disable EndpointSlicesLabelsReconciler for kubernetes versions <=1.16 Co-authored-by: Maksim Fedotov <[email protected]>
1 parent 2c54d91 commit 078588a

File tree

11 files changed

+417
-317
lines changed

11 files changed

+417
-317
lines changed

config/webhook/manifests.yaml

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -23,29 +23,6 @@ webhooks:
2323
- CREATE
2424
resources:
2525
- namespaces
26-
- clientConfig:
27-
caBundle: Cg==
28-
service:
29-
name: webhook-service
30-
namespace: system
31-
path: /mutate-v1-service-labels
32-
failurePolicy: Ignore
33-
name: service.labels.capsule.clastix.io
34-
rules:
35-
- apiGroups:
36-
- ""
37-
- discovery.k8s.io
38-
apiVersions:
39-
- v1
40-
- v1beta1
41-
operations:
42-
- CREATE
43-
- UPDATE
44-
resources:
45-
- services
46-
- endpoints
47-
- endpointslices
48-
4926
---
5027
apiVersion: admissionregistration.k8s.io/v1beta1
5128
kind: ValidatingWebhookConfiguration
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
/*
2+
Copyright 2020 Clastix Labs.
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 service_labels
18+
19+
import (
20+
"context"
21+
"fmt"
22+
23+
"github.com/go-logr/logr"
24+
corev1 "k8s.io/api/core/v1"
25+
"k8s.io/apimachinery/pkg/fields"
26+
"k8s.io/apimachinery/pkg/runtime"
27+
"k8s.io/apimachinery/pkg/types"
28+
ctrl "sigs.k8s.io/controller-runtime"
29+
"sigs.k8s.io/controller-runtime/pkg/builder"
30+
"sigs.k8s.io/controller-runtime/pkg/client"
31+
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
32+
"sigs.k8s.io/controller-runtime/pkg/event"
33+
"sigs.k8s.io/controller-runtime/pkg/predicate"
34+
"sigs.k8s.io/controller-runtime/pkg/reconcile"
35+
36+
"github.com/clastix/capsule/api/v1alpha1"
37+
)
38+
39+
type abstractServiceLabelsReconciler struct {
40+
obj client.Object
41+
client client.Client
42+
log logr.Logger
43+
scheme *runtime.Scheme
44+
}
45+
46+
func (r *abstractServiceLabelsReconciler) InjectClient(c client.Client) error {
47+
r.client = c
48+
return nil
49+
}
50+
51+
func (r *abstractServiceLabelsReconciler) Reconcile(ctx context.Context, request ctrl.Request) (ctrl.Result, error) {
52+
tenant, err := r.getTenant(ctx, request.NamespacedName, r.client)
53+
if err != nil {
54+
switch err.(type) {
55+
case *NonTenantObject, *NoServicesMetadata:
56+
return reconcile.Result{}, nil
57+
default:
58+
r.log.Error(err, fmt.Sprintf("Cannot sync %t labels", r.obj))
59+
return reconcile.Result{}, err
60+
}
61+
}
62+
63+
err = r.client.Get(ctx, request.NamespacedName, r.obj)
64+
if err != nil {
65+
return reconcile.Result{}, err
66+
}
67+
68+
_, err = controllerutil.CreateOrUpdate(ctx, r.client, r.obj, func() (err error) {
69+
r.obj.SetLabels(r.sync(r.obj.GetLabels(), tenant.Spec.ServicesMetadata.AdditionalLabels))
70+
r.obj.SetAnnotations(r.sync(r.obj.GetAnnotations(), tenant.Spec.ServicesMetadata.AdditionalAnnotations))
71+
return nil
72+
})
73+
74+
return reconcile.Result{}, err
75+
}
76+
77+
func (r *abstractServiceLabelsReconciler) getTenant(ctx context.Context, namespacedName types.NamespacedName, client client.Client) (*v1alpha1.Tenant, error) {
78+
ns := &corev1.Namespace{}
79+
tenant := &v1alpha1.Tenant{}
80+
81+
if err := client.Get(ctx, types.NamespacedName{Name: namespacedName.Namespace}, ns); err != nil {
82+
return nil, err
83+
}
84+
85+
capsuleLabel, _ := v1alpha1.GetTypeLabel(&v1alpha1.Tenant{})
86+
if _, ok := ns.GetLabels()[capsuleLabel]; !ok {
87+
return nil, NewNonTenantObject(namespacedName.Name)
88+
}
89+
90+
if err := client.Get(ctx, types.NamespacedName{Name: ns.Labels[capsuleLabel]}, tenant); err != nil {
91+
return nil, err
92+
}
93+
94+
if tenant.Spec.ServicesMetadata.AdditionalLabels == nil && tenant.Spec.ServicesMetadata.AdditionalAnnotations == nil {
95+
return nil, NewNoServicesMetadata(namespacedName.Name)
96+
}
97+
98+
return tenant, nil
99+
}
100+
101+
func (r *abstractServiceLabelsReconciler) sync(available map[string]string, tenantSpec map[string]string) map[string]string {
102+
if tenantSpec != nil {
103+
if available == nil {
104+
available = tenantSpec
105+
} else {
106+
for key, value := range tenantSpec {
107+
if available[key] != value {
108+
available[key] = value
109+
}
110+
}
111+
}
112+
}
113+
return available
114+
}
115+
116+
func (r *abstractServiceLabelsReconciler) forOptionPerInstanceName() builder.ForOption {
117+
return builder.WithPredicates(predicate.Funcs{
118+
CreateFunc: func(event event.CreateEvent) bool {
119+
return r.IsNamespaceInTenant(event.Object.GetNamespace())
120+
},
121+
DeleteFunc: func(deleteEvent event.DeleteEvent) bool {
122+
return r.IsNamespaceInTenant(deleteEvent.Object.GetNamespace())
123+
},
124+
UpdateFunc: func(updateEvent event.UpdateEvent) bool {
125+
return r.IsNamespaceInTenant(updateEvent.ObjectNew.GetNamespace())
126+
},
127+
GenericFunc: func(genericEvent event.GenericEvent) bool {
128+
return r.IsNamespaceInTenant(genericEvent.Object.GetNamespace())
129+
},
130+
})
131+
}
132+
133+
func (r *abstractServiceLabelsReconciler) IsNamespaceInTenant(namespace string) bool {
134+
tl := &v1alpha1.TenantList{}
135+
if err := r.client.List(context.Background(), tl, client.MatchingFieldsSelector{
136+
Selector: fields.OneTermEqualSelector(".status.namespaces", namespace),
137+
}); err != nil {
138+
return false
139+
}
140+
return len(tl.Items) > 0
141+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
Copyright 2020 Clastix Labs.
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 service_labels
18+
19+
import (
20+
"github.com/go-logr/logr"
21+
corev1 "k8s.io/api/core/v1"
22+
ctrl "sigs.k8s.io/controller-runtime"
23+
)
24+
25+
type EndpointsLabelsReconciler struct {
26+
abstractServiceLabelsReconciler
27+
28+
Log logr.Logger
29+
}
30+
31+
func (r *EndpointsLabelsReconciler) SetupWithManager(mgr ctrl.Manager) error {
32+
r.abstractServiceLabelsReconciler = abstractServiceLabelsReconciler{
33+
obj: &corev1.Endpoints{},
34+
scheme: mgr.GetScheme(),
35+
log: r.Log,
36+
}
37+
38+
return ctrl.NewControllerManagedBy(mgr).
39+
For(r.abstractServiceLabelsReconciler.obj, r.abstractServiceLabelsReconciler.forOptionPerInstanceName()).
40+
Complete(r)
41+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
Copyright 2020 Clastix Labs.
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 service_labels
18+
19+
import (
20+
"github.com/go-logr/logr"
21+
discoveryv1beta1 "k8s.io/api/discovery/v1beta1"
22+
ctrl "sigs.k8s.io/controller-runtime"
23+
)
24+
25+
type EndpointSlicesLabelsReconciler struct {
26+
abstractServiceLabelsReconciler
27+
28+
Log logr.Logger
29+
VersionMinor int
30+
VersionMajor int
31+
}
32+
33+
func (r *EndpointSlicesLabelsReconciler) SetupWithManager(mgr ctrl.Manager) error {
34+
r.scheme = mgr.GetScheme()
35+
r.abstractServiceLabelsReconciler = abstractServiceLabelsReconciler{
36+
scheme: mgr.GetScheme(),
37+
log: r.Log,
38+
}
39+
40+
if r.VersionMajor == 1 && r.VersionMinor <= 16 {
41+
r.Log.Info("Skipping controller setup, as EndpointSlices are not supported on current kubernetes version", "VersionMajor", r.VersionMajor, "VersionMinor", r.VersionMinor)
42+
return nil
43+
}
44+
45+
r.abstractServiceLabelsReconciler.obj = &discoveryv1beta1.EndpointSlice{}
46+
return ctrl.NewControllerManagedBy(mgr).
47+
For(r.obj, r.abstractServiceLabelsReconciler.forOptionPerInstanceName()).
48+
Complete(r)
49+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
Copyright 2020 Clastix Labs.
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 service_labels
18+
19+
import "fmt"
20+
21+
type NonTenantObject struct {
22+
objectName string
23+
}
24+
25+
func NewNonTenantObject(objectName string) error {
26+
return &NonTenantObject{objectName: objectName}
27+
}
28+
29+
func (n NonTenantObject) Error() string {
30+
return fmt.Sprintf("Skipping labels sync for %s as it doesn't belong to tenant", n.objectName)
31+
}
32+
33+
type NoServicesMetadata struct {
34+
objectName string
35+
}
36+
37+
func NewNoServicesMetadata(objectName string) error {
38+
return &NoServicesMetadata{objectName: objectName}
39+
}
40+
41+
func (n NoServicesMetadata) Error() string {
42+
return fmt.Sprintf("Skipping labels sync for %s because no AdditionalLabels or AdditionalAnnotations presents in Tenant spec", n.objectName)
43+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
Copyright 2020 Clastix Labs.
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 service_labels
18+
19+
import (
20+
"github.com/go-logr/logr"
21+
corev1 "k8s.io/api/core/v1"
22+
ctrl "sigs.k8s.io/controller-runtime"
23+
)
24+
25+
type ServicesLabelsReconciler struct {
26+
abstractServiceLabelsReconciler
27+
28+
Log logr.Logger
29+
}
30+
31+
func (r *ServicesLabelsReconciler) SetupWithManager(mgr ctrl.Manager) error {
32+
r.abstractServiceLabelsReconciler = abstractServiceLabelsReconciler{
33+
obj: &corev1.Service{},
34+
scheme: mgr.GetScheme(),
35+
log: r.Log,
36+
}
37+
return ctrl.NewControllerManagedBy(mgr).
38+
For(r.abstractServiceLabelsReconciler.obj, r.abstractServiceLabelsReconciler.forOptionPerInstanceName()).
39+
Complete(r)
40+
}

0 commit comments

Comments
 (0)