Skip to content

Commit 2448d05

Browse files
authored
✨ Add clusteraddoncreate controller (#26)
Adding the controller that is responsible for creating clusterAddon objects for each cluster object in the management cluster. The clusterAddon object reconciles the cluster addons that should be applied in each workload cluster. Signed-off-by: janiskemper <janis.kemper@syself.com>
1 parent 4fda66b commit 2448d05

5 files changed

Lines changed: 227 additions & 0 deletions

File tree

cmd/main.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,13 @@ func main() {
135135
os.Exit(1)
136136
}
137137

138+
if err = (&controller.ClusterAddonCreateReconciler{
139+
Client: mgr.GetClient(),
140+
}).SetupWithManager(ctx, mgr, controllerruntimecontroller.Options{}); err != nil {
141+
setupLog.Error(err, "unable to create controller", "controller", "ClusterAddonCreate")
142+
os.Exit(1)
143+
}
144+
138145
//+kubebuilder:scaffold:builder
139146

140147
if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {

config/rbac/role.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@ rules:
3939
- patch
4040
- update
4141
- watch
42+
- apiGroups:
43+
- clusterstack.x-k8s.io
44+
resources:
45+
- clusteraddons
46+
verbs:
47+
- create
4248
- apiGroups:
4349
- clusterstack.x-k8s.io
4450
resources:
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/*
2+
Copyright 2023 The Kubernetes Authors.
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 controller
18+
19+
import (
20+
"context"
21+
"fmt"
22+
23+
csov1alpha1 "github.com/SovereignCloudStack/cluster-stack-operator/api/v1alpha1"
24+
corev1 "k8s.io/api/core/v1"
25+
apierrors "k8s.io/apimachinery/pkg/api/errors"
26+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27+
"k8s.io/apimachinery/pkg/types"
28+
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
29+
"sigs.k8s.io/cluster-api/util/predicates"
30+
ctrl "sigs.k8s.io/controller-runtime"
31+
"sigs.k8s.io/controller-runtime/pkg/client"
32+
"sigs.k8s.io/controller-runtime/pkg/controller"
33+
"sigs.k8s.io/controller-runtime/pkg/event"
34+
"sigs.k8s.io/controller-runtime/pkg/log"
35+
"sigs.k8s.io/controller-runtime/pkg/predicate"
36+
"sigs.k8s.io/controller-runtime/pkg/reconcile"
37+
)
38+
39+
// ClusterAddonCreateReconciler reconciles a Cluster object.
40+
type ClusterAddonCreateReconciler struct {
41+
client.Client
42+
WatchFilterValue string
43+
}
44+
45+
//+kubebuilder:rbac:groups=clusterstack.x-k8s.io,resources=clusteraddons,verbs=create
46+
47+
// Reconcile is part of the main Kubernetes reconciliation loop which aims to
48+
// move the current state of the cluster closer to the desired state.
49+
func (r *ClusterAddonCreateReconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) {
50+
cluster := &clusterv1.Cluster{}
51+
if err := r.Get(ctx, req.NamespacedName, cluster); err != nil {
52+
// if the cluster is not found, exit the reconciliation
53+
if apierrors.IsNotFound(err) {
54+
return reconcile.Result{}, nil
55+
}
56+
return reconcile.Result{}, fmt.Errorf("failed to get cluster: %w", err)
57+
}
58+
59+
clusterAddonName := fmt.Sprintf("cluster-addon-%s", cluster.Name)
60+
61+
var existingClusterAddon csov1alpha1.ClusterAddon
62+
err := r.Get(ctx, types.NamespacedName{Namespace: cluster.Namespace, Name: clusterAddonName}, &existingClusterAddon)
63+
// no error means that the object already exists - nothing to do
64+
if err == nil {
65+
return reconcile.Result{}, nil
66+
}
67+
// unexpected error - return it
68+
if !apierrors.IsNotFound(err) {
69+
return reconcile.Result{}, fmt.Errorf("failed to get ClusterAddon object: %w", err)
70+
}
71+
72+
// clusterAddon could not be found - create it
73+
clusterAddon := &csov1alpha1.ClusterAddon{
74+
ObjectMeta: metav1.ObjectMeta{Name: clusterAddonName, Namespace: cluster.Namespace},
75+
TypeMeta: metav1.TypeMeta{Kind: "ClusterAddon", APIVersion: "clusterstack.x-k8s.io/v1alpha1"},
76+
Spec: csov1alpha1.ClusterAddonSpec{
77+
ClusterRef: &corev1.ObjectReference{
78+
APIVersion: cluster.APIVersion,
79+
Kind: cluster.Kind,
80+
Name: cluster.Name,
81+
UID: cluster.UID,
82+
Namespace: cluster.Namespace,
83+
},
84+
},
85+
}
86+
87+
clusterAddon.OwnerReferences = append(clusterAddon.OwnerReferences, metav1.OwnerReference{
88+
APIVersion: cluster.APIVersion,
89+
Kind: cluster.Kind,
90+
Name: cluster.Name,
91+
UID: cluster.UID,
92+
})
93+
94+
if err := r.Create(ctx, clusterAddon); err != nil {
95+
return reconcile.Result{}, fmt.Errorf("failed to create ClusterAddon object: %w", err)
96+
}
97+
return reconcile.Result{}, nil
98+
}
99+
100+
// SetupWithManager sets up the controller with the Manager.
101+
func (r *ClusterAddonCreateReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error {
102+
return ctrl.NewControllerManagedBy(mgr).
103+
WithOptions(options).
104+
For(&clusterv1.Cluster{}).
105+
WithEventFilter(predicates.ResourceNotPausedAndHasFilterLabel(log.FromContext(ctx), r.WatchFilterValue)).
106+
WithEventFilter(predicate.Funcs{
107+
// We're only interested in the create events for a cluster object
108+
DeleteFunc: func(e event.DeleteEvent) bool {
109+
return false
110+
},
111+
GenericFunc: func(e event.GenericEvent) bool {
112+
return false
113+
},
114+
UpdateFunc: func(e event.UpdateEvent) bool {
115+
return false
116+
},
117+
}).
118+
Complete(r)
119+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*
2+
Copyright 2023 The Kubernetes Authors.
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 controller
18+
19+
import (
20+
"fmt"
21+
22+
csov1alpha1 "github.com/SovereignCloudStack/cluster-stack-operator/api/v1alpha1"
23+
. "github.com/onsi/ginkgo/v2"
24+
. "github.com/onsi/gomega"
25+
corev1 "k8s.io/api/core/v1"
26+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27+
"k8s.io/apimachinery/pkg/types"
28+
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
29+
)
30+
31+
var _ = Describe("ClusterAddonCreateReconciler", func() {
32+
var (
33+
cluster *clusterv1.Cluster
34+
clusterStackRelease *csov1alpha1.ClusterStackRelease
35+
testNs *corev1.Namespace
36+
key types.NamespacedName
37+
)
38+
39+
BeforeEach(func() {
40+
var err error
41+
testNs, err = testEnv.CreateNamespace(ctx, "clusteraddoncreate-reconciler")
42+
Expect(err).NotTo(HaveOccurred())
43+
44+
clusterStackRelease = &csov1alpha1.ClusterStackRelease{
45+
ObjectMeta: metav1.ObjectMeta{
46+
Name: testClusterStackName,
47+
Namespace: testNs.Name,
48+
},
49+
}
50+
Expect(testEnv.Create(ctx, clusterStackRelease)).To(Succeed())
51+
52+
cluster = &clusterv1.Cluster{
53+
ObjectMeta: metav1.ObjectMeta{
54+
Name: "testcluster",
55+
Namespace: testNs.Name,
56+
Finalizers: []string{clusterv1.ClusterFinalizer},
57+
},
58+
Spec: clusterv1.ClusterSpec{
59+
Topology: &clusterv1.Topology{
60+
Class: testClusterStackName,
61+
},
62+
},
63+
}
64+
Expect(testEnv.Create(ctx, cluster)).To(Succeed())
65+
66+
key = types.NamespacedName{Name: fmt.Sprintf("cluster-addon-%s", cluster.Name), Namespace: testNs.Name}
67+
})
68+
69+
AfterEach(func() {
70+
Expect(testEnv.Cleanup(ctx, testNs, cluster, clusterStackRelease)).To(Succeed())
71+
})
72+
73+
Context("Basic test", func() {
74+
It("creates the clusterAddon object", func() {
75+
Eventually(func() bool {
76+
var foundClusterAddon csov1alpha1.ClusterAddon
77+
if err := testEnv.Get(ctx, key, &foundClusterAddon); err != nil {
78+
testEnv.GetLogger().Info(err.Error())
79+
return false
80+
}
81+
82+
if foundClusterAddon.Spec.ClusterRef.Name != cluster.Name {
83+
testEnv.GetLogger().Info("wrong cluster ref name", "got", foundClusterAddon.Spec.ClusterRef.Name, "want", cluster.Name)
84+
return false
85+
}
86+
87+
return true
88+
}, timeout, interval).Should(BeTrue())
89+
})
90+
})
91+
})

internal/controller/controller_suite_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ var _ = BeforeSuite(func() {
6464
ReleaseDirectory: "./../../test/releases",
6565
}).SetupWithManager(ctx, testEnv.Manager, c.Options{})).To(Succeed())
6666

67+
Expect((&ClusterAddonCreateReconciler{
68+
Client: testEnv.Manager.GetClient(),
69+
}).SetupWithManager(ctx, testEnv.Manager, c.Options{})).To(Succeed())
70+
6771
go func() {
6872
defer GinkgoRecover()
6973
Expect(testEnv.StartManager(ctx)).To(Succeed())

0 commit comments

Comments
 (0)