Skip to content

Commit

Permalink
migrating service webhook to controller p1 (#130)
Browse files Browse the repository at this point in the history
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]>
  • Loading branch information
MaxFedotov and Maksim Fedotov authored Nov 10, 2020
1 parent 2c54d91 commit 078588a
Show file tree
Hide file tree
Showing 11 changed files with 417 additions and 317 deletions.
23 changes: 0 additions & 23 deletions config/webhook/manifests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,29 +23,6 @@ webhooks:
- CREATE
resources:
- namespaces
- clientConfig:
caBundle: Cg==
service:
name: webhook-service
namespace: system
path: /mutate-v1-service-labels
failurePolicy: Ignore
name: service.labels.capsule.clastix.io
rules:
- apiGroups:
- ""
- discovery.k8s.io
apiVersions:
- v1
- v1beta1
operations:
- CREATE
- UPDATE
resources:
- services
- endpoints
- endpointslices

---
apiVersion: admissionregistration.k8s.io/v1beta1
kind: ValidatingWebhookConfiguration
Expand Down
141 changes: 141 additions & 0 deletions controllers/service_labels/abstract.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/*
Copyright 2020 Clastix Labs.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package service_labels

import (
"context"
"fmt"

"github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/event"
"sigs.k8s.io/controller-runtime/pkg/predicate"
"sigs.k8s.io/controller-runtime/pkg/reconcile"

"github.com/clastix/capsule/api/v1alpha1"
)

type abstractServiceLabelsReconciler struct {
obj client.Object
client client.Client
log logr.Logger
scheme *runtime.Scheme
}

func (r *abstractServiceLabelsReconciler) InjectClient(c client.Client) error {
r.client = c
return nil
}

func (r *abstractServiceLabelsReconciler) Reconcile(ctx context.Context, request ctrl.Request) (ctrl.Result, error) {
tenant, err := r.getTenant(ctx, request.NamespacedName, r.client)
if err != nil {
switch err.(type) {
case *NonTenantObject, *NoServicesMetadata:
return reconcile.Result{}, nil
default:
r.log.Error(err, fmt.Sprintf("Cannot sync %t labels", r.obj))
return reconcile.Result{}, err
}
}

err = r.client.Get(ctx, request.NamespacedName, r.obj)
if err != nil {
return reconcile.Result{}, err
}

_, err = controllerutil.CreateOrUpdate(ctx, r.client, r.obj, func() (err error) {
r.obj.SetLabels(r.sync(r.obj.GetLabels(), tenant.Spec.ServicesMetadata.AdditionalLabels))
r.obj.SetAnnotations(r.sync(r.obj.GetAnnotations(), tenant.Spec.ServicesMetadata.AdditionalAnnotations))
return nil
})

return reconcile.Result{}, err
}

func (r *abstractServiceLabelsReconciler) getTenant(ctx context.Context, namespacedName types.NamespacedName, client client.Client) (*v1alpha1.Tenant, error) {
ns := &corev1.Namespace{}
tenant := &v1alpha1.Tenant{}

if err := client.Get(ctx, types.NamespacedName{Name: namespacedName.Namespace}, ns); err != nil {
return nil, err
}

capsuleLabel, _ := v1alpha1.GetTypeLabel(&v1alpha1.Tenant{})
if _, ok := ns.GetLabels()[capsuleLabel]; !ok {
return nil, NewNonTenantObject(namespacedName.Name)
}

if err := client.Get(ctx, types.NamespacedName{Name: ns.Labels[capsuleLabel]}, tenant); err != nil {
return nil, err
}

if tenant.Spec.ServicesMetadata.AdditionalLabels == nil && tenant.Spec.ServicesMetadata.AdditionalAnnotations == nil {
return nil, NewNoServicesMetadata(namespacedName.Name)
}

return tenant, nil
}

func (r *abstractServiceLabelsReconciler) sync(available map[string]string, tenantSpec map[string]string) map[string]string {
if tenantSpec != nil {
if available == nil {
available = tenantSpec
} else {
for key, value := range tenantSpec {
if available[key] != value {
available[key] = value
}
}
}
}
return available
}

func (r *abstractServiceLabelsReconciler) forOptionPerInstanceName() builder.ForOption {
return builder.WithPredicates(predicate.Funcs{
CreateFunc: func(event event.CreateEvent) bool {
return r.IsNamespaceInTenant(event.Object.GetNamespace())
},
DeleteFunc: func(deleteEvent event.DeleteEvent) bool {
return r.IsNamespaceInTenant(deleteEvent.Object.GetNamespace())
},
UpdateFunc: func(updateEvent event.UpdateEvent) bool {
return r.IsNamespaceInTenant(updateEvent.ObjectNew.GetNamespace())
},
GenericFunc: func(genericEvent event.GenericEvent) bool {
return r.IsNamespaceInTenant(genericEvent.Object.GetNamespace())
},
})
}

func (r *abstractServiceLabelsReconciler) IsNamespaceInTenant(namespace string) bool {
tl := &v1alpha1.TenantList{}
if err := r.client.List(context.Background(), tl, client.MatchingFieldsSelector{
Selector: fields.OneTermEqualSelector(".status.namespaces", namespace),
}); err != nil {
return false
}
return len(tl.Items) > 0
}
41 changes: 41 additions & 0 deletions controllers/service_labels/endpoint.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
Copyright 2020 Clastix Labs.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package service_labels

import (
"github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
ctrl "sigs.k8s.io/controller-runtime"
)

type EndpointsLabelsReconciler struct {
abstractServiceLabelsReconciler

Log logr.Logger
}

func (r *EndpointsLabelsReconciler) SetupWithManager(mgr ctrl.Manager) error {
r.abstractServiceLabelsReconciler = abstractServiceLabelsReconciler{
obj: &corev1.Endpoints{},
scheme: mgr.GetScheme(),
log: r.Log,
}

return ctrl.NewControllerManagedBy(mgr).
For(r.abstractServiceLabelsReconciler.obj, r.abstractServiceLabelsReconciler.forOptionPerInstanceName()).
Complete(r)
}
49 changes: 49 additions & 0 deletions controllers/service_labels/endpoint_slices.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
Copyright 2020 Clastix Labs.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package service_labels

import (
"github.com/go-logr/logr"
discoveryv1beta1 "k8s.io/api/discovery/v1beta1"
ctrl "sigs.k8s.io/controller-runtime"
)

type EndpointSlicesLabelsReconciler struct {
abstractServiceLabelsReconciler

Log logr.Logger
VersionMinor int
VersionMajor int
}

func (r *EndpointSlicesLabelsReconciler) SetupWithManager(mgr ctrl.Manager) error {
r.scheme = mgr.GetScheme()
r.abstractServiceLabelsReconciler = abstractServiceLabelsReconciler{
scheme: mgr.GetScheme(),
log: r.Log,
}

if r.VersionMajor == 1 && r.VersionMinor <= 16 {
r.Log.Info("Skipping controller setup, as EndpointSlices are not supported on current kubernetes version", "VersionMajor", r.VersionMajor, "VersionMinor", r.VersionMinor)
return nil
}

r.abstractServiceLabelsReconciler.obj = &discoveryv1beta1.EndpointSlice{}
return ctrl.NewControllerManagedBy(mgr).
For(r.obj, r.abstractServiceLabelsReconciler.forOptionPerInstanceName()).
Complete(r)
}
43 changes: 43 additions & 0 deletions controllers/service_labels/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
Copyright 2020 Clastix Labs.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package service_labels

import "fmt"

type NonTenantObject struct {
objectName string
}

func NewNonTenantObject(objectName string) error {
return &NonTenantObject{objectName: objectName}
}

func (n NonTenantObject) Error() string {
return fmt.Sprintf("Skipping labels sync for %s as it doesn't belong to tenant", n.objectName)
}

type NoServicesMetadata struct {
objectName string
}

func NewNoServicesMetadata(objectName string) error {
return &NoServicesMetadata{objectName: objectName}
}

func (n NoServicesMetadata) Error() string {
return fmt.Sprintf("Skipping labels sync for %s because no AdditionalLabels or AdditionalAnnotations presents in Tenant spec", n.objectName)
}
40 changes: 40 additions & 0 deletions controllers/service_labels/service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
Copyright 2020 Clastix Labs.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package service_labels

import (
"github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
ctrl "sigs.k8s.io/controller-runtime"
)

type ServicesLabelsReconciler struct {
abstractServiceLabelsReconciler

Log logr.Logger
}

func (r *ServicesLabelsReconciler) SetupWithManager(mgr ctrl.Manager) error {
r.abstractServiceLabelsReconciler = abstractServiceLabelsReconciler{
obj: &corev1.Service{},
scheme: mgr.GetScheme(),
log: r.Log,
}
return ctrl.NewControllerManagedBy(mgr).
For(r.abstractServiceLabelsReconciler.obj, r.abstractServiceLabelsReconciler.forOptionPerInstanceName()).
Complete(r)
}
Loading

0 comments on commit 078588a

Please sign in to comment.