Skip to content

Commit

Permalink
Add watch for ClusterResourceSet resources
Browse files Browse the repository at this point in the history
  • Loading branch information
Sedef committed Jul 30, 2020
1 parent a2a78ce commit 8d3fb61
Show file tree
Hide file tree
Showing 3 changed files with 204 additions and 1 deletion.
70 changes: 69 additions & 1 deletion exp/addons/controllers/clusterresourceset_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (

"github.com/go-logr/logr"
"github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
Expand All @@ -35,12 +36,14 @@ import (
clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha3"
"sigs.k8s.io/cluster-api/controllers/remote"
addonsv1 "sigs.k8s.io/cluster-api/exp/addons/api/v1alpha3"
resourcepredicates "sigs.k8s.io/cluster-api/exp/addons/controllers/predicates"
"sigs.k8s.io/cluster-api/util"
"sigs.k8s.io/cluster-api/util/conditions"
"sigs.k8s.io/cluster-api/util/patch"
"sigs.k8s.io/cluster-api/util/predicates"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/source"
Expand All @@ -65,7 +68,7 @@ type ClusterResourceSetReconciler struct {
}

func (r *ClusterResourceSetReconciler) SetupWithManager(mgr ctrl.Manager, options controller.Options) error {
_, err := ctrl.NewControllerManagedBy(mgr).
controller, err := ctrl.NewControllerManagedBy(mgr).
For(&addonsv1.ClusterResourceSet{}).
Watches(
&source.Kind{Type: &clusterv1.Cluster{}},
Expand All @@ -78,6 +81,27 @@ func (r *ClusterResourceSetReconciler) SetupWithManager(mgr ctrl.Manager, option
return errors.Wrap(err, "failed setting up with a controller manager")
}

err = controller.Watch(
&source.Kind{Type: &corev1.ConfigMap{}},
&handler.EnqueueRequestsFromMapFunc{
ToRequests: handler.ToRequestsFunc(r.resourceToClusterResourceSet),
},
resourcepredicates.ResourceCreate(r.Log),
)
if err != nil {
return errors.Wrap(err, "failed adding Watch for ConfigMaps to controller manager")
}

err = controller.Watch(
&source.Kind{Type: &corev1.Secret{}},
&handler.EnqueueRequestsFromMapFunc{
ToRequests: handler.ToRequestsFunc(r.resourceToClusterResourceSet),
},
resourcepredicates.AddonsSecretCreate(r.Log),
)
if err != nil {
return errors.Wrap(err, "failed adding Watch for Secret to controller manager")
}
r.scheme = mgr.GetScheme()
return nil
}
Expand Down Expand Up @@ -390,3 +414,47 @@ func (r *ClusterResourceSetReconciler) clusterToClusterResourceSet(o handler.Map
}
return result
}

// resourceToClusterResourceSet is mapper function that maps resources to ClusterResourceSet
func (r *ClusterResourceSetReconciler) resourceToClusterResourceSet(o handler.MapObject) []ctrl.Request {
result := []ctrl.Request{}

// Add all ClusterResourceSet owners.
for _, owner := range o.Meta.GetOwnerReferences() {
if owner.Kind == "ClusterResourceSet" {
name := client.ObjectKey{Namespace: o.Meta.GetNamespace(), Name: owner.Name}
result = append(result, ctrl.Request{NamespacedName: name})
}
}

// If there is any ClusterResourceSet owner, that means the resource is reconciled before,
// and existing owners are the only matching ClusterResourceSets to this resource, so no need to return all ClusterResourceSets.
if len(result) > 0 {
return result
}

// Only core group is accepted as resources group
if o.Object.GetObjectKind().GroupVersionKind().Group != "" {
return result
}

crsList := &addonsv1.ClusterResourceSetList{}
if err := r.Client.List(context.Background(), crsList, client.InNamespace(o.Meta.GetNamespace())); err != nil {
return nil
}
objKind, err := apiutil.GVKForObject(o.Object, r.scheme)
if err != nil {
return nil
}
for _, crs := range crsList.Items {
for _, resource := range crs.Spec.Resources {
if resource.Kind == objKind.Kind && resource.Name == o.Meta.GetName() {
name := client.ObjectKey{Namespace: o.Meta.GetNamespace(), Name: crs.Name}
result = append(result, ctrl.Request{NamespacedName: name})
break
}
}
}

return result
}
75 changes: 75 additions & 0 deletions exp/addons/controllers/clusterresourceset_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,4 +217,79 @@ apiVersion: v1`,
return apierrors.IsNotFound(err)
}, timeout).Should(BeTrue())
})
It("Should reconcile a ClusterResourceSet when a resource is created that is part of ClusterResourceSet resources", func() {

labels := map[string]string{"foo2": "bar2"}
newCMName := "test-configmap2"

clusterResourceSetInstance := &addonsv1.ClusterResourceSet{
ObjectMeta: metav1.ObjectMeta{
Name: "test-clusterresourceset",
Namespace: defaultNamespaceName,
},
Spec: addonsv1.ClusterResourceSetSpec{
ClusterSelector: metav1.LabelSelector{
MatchLabels: labels,
},
Resources: []addonsv1.ResourceRef{{Name: newCMName, Kind: "ConfigMap"}},
},
}
// Create the ClusterResourceSet.
Expect(testEnv.Create(ctx, clusterResourceSetInstance)).To(Succeed())
defer func() {
Expect(testEnv.Delete(ctx, clusterResourceSetInstance)).To(Succeed())
}()

testCluster.SetLabels(labels)
Expect(testEnv.Update(ctx, testCluster)).To(Succeed())

By("Verifying ClusterResourceSetBinding is created with cluster owner reference")
// Wait until ClusterResourceSetBinding is created for the Cluster
clusterResourceSetBindingKey := client.ObjectKey{
Namespace: testCluster.Namespace,
Name: testCluster.Name,
}
Eventually(func() bool {
binding := &addonsv1.ClusterResourceSetBinding{}

err := testEnv.Get(ctx, clusterResourceSetBindingKey, binding)
return err == nil
}, timeout).Should(BeTrue())

// Initially ConfiMap is missing, so no resources in the binding.
Eventually(func() bool {
binding := &addonsv1.ClusterResourceSetBinding{}

err := testEnv.Get(ctx, clusterResourceSetBindingKey, binding)
if err == nil {
if len(binding.Spec.Bindings) > 0 && len(binding.Spec.Bindings[0].Resources) == 0 {
return true
}
}
return false
}, timeout).Should(BeTrue())

testConfigmap := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: newCMName,
Namespace: defaultNamespaceName,
},
Data: map[string]string{},
}
Expect(testEnv.Create(ctx, testConfigmap)).To(Succeed())

// When the ConfigMap resource is created, CRS should get reconciled immediately.
Eventually(func() bool {
binding := &addonsv1.ClusterResourceSetBinding{}

err := testEnv.Get(ctx, clusterResourceSetBindingKey, binding)
if err == nil {
if len(binding.Spec.Bindings[0].Resources) > 0 && binding.Spec.Bindings[0].Resources[0].Name == newCMName {
return true
}
}
return false
}, timeout).Should(BeTrue())
Expect(testEnv.Delete(ctx, testConfigmap)).To(Succeed())
})
})
60 changes: 60 additions & 0 deletions exp/addons/controllers/predicates/resource_predicates.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
Copyright 2020 The Kubernetes Authors.
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 predicates

import (
"github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
addonsv1 "sigs.k8s.io/cluster-api/exp/addons/api/v1alpha3"
"sigs.k8s.io/controller-runtime/pkg/event"
"sigs.k8s.io/controller-runtime/pkg/predicate"
)

// ResourceCreate returns a predicate that returns true for a create event
func ResourceCreate(logger logr.Logger) predicate.Funcs {
return predicate.Funcs{
CreateFunc: func(e event.CreateEvent) bool { return true },
UpdateFunc: func(e event.UpdateEvent) bool { return false },
DeleteFunc: func(e event.DeleteEvent) bool { return false },
GenericFunc: func(e event.GenericEvent) bool { return false },
}
}

// AddonsSecretCreate returns a predicate that returns true for a Secret create event if in addons Secret type
func AddonsSecretCreate(logger logr.Logger) predicate.Funcs {
log := logger.WithValues("predicate", "SecretCreateOrUpdate")

return predicate.Funcs{
CreateFunc: func(e event.CreateEvent) bool {
log = log.WithValues("eventType", "create")
s, ok := e.Object.(*corev1.Secret)
if !ok {
log.V(4).Info("Expected Secret", "secret", e.Object.GetObjectKind().GroupVersionKind().String())
return false
}
if string(s.Type) != string(addonsv1.ClusterResourceSetSecretType) {
log.V(4).Info("Expected Secret Type", "type", addonsv1.SecretClusterResourceSetResourceKind,
"got", string(s.Type))
return false
}
return true
},
UpdateFunc: func(e event.UpdateEvent) bool { return false },
DeleteFunc: func(e event.DeleteEvent) bool { return false },
GenericFunc: func(e event.GenericEvent) bool { return false },
}
}

0 comments on commit 8d3fb61

Please sign in to comment.