Skip to content

Commit b36e26e

Browse files
muraeeenxebre
andauthored
Introduce rosaControlPlane for managed kubernetes (#4453)
* Introduce rosaControlPlane for managed kubernetes * fixed capatilized resource names * code cleanup * move rosacluster_types.go to exp/api * Add AvailabilityZones field to ROSAControlPlane * implemented deletion * adressed code review * adapt code after CAPI bump to v1.5.0 --------- Co-authored-by: enxebre <[email protected]>
1 parent 9eb88c7 commit b36e26e

16 files changed

+2361
-1
lines changed

Makefile

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,7 @@ generate-go-apis: ## Alias for .build/generate-go-apis
223223
paths=./$(EXP_DIR)/api/... \
224224
paths=./bootstrap/eks/api/... \
225225
paths=./controlplane/eks/api/... \
226+
paths=./controlplane/rosa/api/... \
226227
paths=./iam/api/... \
227228
paths=./controllers/... \
228229
paths=./$(EXP_DIR)/controllers/... \
@@ -272,6 +273,14 @@ generate-go-apis: ## Alias for .build/generate-go-apis
272273
--output-file-base=zz_generated.conversion $(GEN_OUTPUT_BASE) \
273274
--go-header-file=./hack/boilerplate/boilerplate.generatego.txt
274275

276+
$(CONVERSION_GEN) \
277+
--input-dirs=./controlplane/rosa/api/v1beta2 \
278+
--extra-peer-dirs=sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta1 \
279+
--extra-peer-dirs=sigs.k8s.io/cluster-api/api/v1beta1 \
280+
--build-tag=ignore_autogenerated_conversions \
281+
--output-file-base=zz_generated.conversion $(GEN_OUTPUT_BASE) \
282+
--go-header-file=./hack/boilerplate/boilerplate.generatego.txt
283+
275284
touch $@
276285

277286
##@ lint and verify:
@@ -374,7 +383,7 @@ managers: ## Alias for manager-aws-infrastructure
374383

375384
.PHONY: manager-aws-infrastructure
376385
manager-aws-infrastructure: ## Build manager binary
377-
CGO_ENABLED=0 GOOS=linux GOARCH=${ARCH} go build -ldflags "${LDFLAGS} -extldflags '-static'" -o $(BIN_DIR)/manager .
386+
CGO_ENABLED=0 GOARCH=${ARCH} go build -ldflags "${LDFLAGS} -extldflags '-static'" -o $(BIN_DIR)/manager .
378387

379388
##@ test:
380389

config/crd/bases/controlplane.cluster.x-k8s.io_rosacontrolplanes.yaml

Lines changed: 339 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
---
2+
apiVersion: apiextensions.k8s.io/v1
3+
kind: CustomResourceDefinition
4+
metadata:
5+
annotations:
6+
controller-gen.kubebuilder.io/version: v0.12.1
7+
name: rosaclusters.infrastructure.cluster.x-k8s.io
8+
spec:
9+
group: infrastructure.cluster.x-k8s.io
10+
names:
11+
categories:
12+
- cluster-api
13+
kind: ROSACluster
14+
listKind: ROSAClusterList
15+
plural: rosaclusters
16+
shortNames:
17+
- rosac
18+
singular: rosacluster
19+
scope: Namespaced
20+
versions:
21+
- additionalPrinterColumns:
22+
- description: Cluster to which this AWSManagedControl belongs
23+
jsonPath: .metadata.labels.cluster\.x-k8s\.io/cluster-name
24+
name: Cluster
25+
type: string
26+
- description: Control plane infrastructure is ready for worker nodes
27+
jsonPath: .status.ready
28+
name: Ready
29+
type: string
30+
- description: API Endpoint
31+
jsonPath: .spec.controlPlaneEndpoint.host
32+
name: Endpoint
33+
priority: 1
34+
type: string
35+
name: v1beta2
36+
schema:
37+
openAPIV3Schema:
38+
properties:
39+
apiVersion:
40+
description: 'APIVersion defines the versioned schema of this representation
41+
of an object. Servers should convert recognized schemas to the latest
42+
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
43+
type: string
44+
kind:
45+
description: 'Kind is a string value representing the REST resource this
46+
object represents. Servers may infer this from the endpoint the client
47+
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
48+
type: string
49+
metadata:
50+
type: object
51+
spec:
52+
properties:
53+
controlPlaneEndpoint:
54+
description: ControlPlaneEndpoint represents the endpoint used to
55+
communicate with the control plane.
56+
properties:
57+
host:
58+
description: The hostname on which the API server is serving.
59+
type: string
60+
port:
61+
description: The port on which the API server is serving.
62+
format: int32
63+
type: integer
64+
required:
65+
- host
66+
- port
67+
type: object
68+
type: object
69+
status:
70+
description: ROSAClusterStatus defines the observed state of ROSACluster
71+
properties:
72+
failureDomains:
73+
additionalProperties:
74+
description: FailureDomainSpec is the Schema for Cluster API failure
75+
domains. It allows controllers to understand how many failure
76+
domains a cluster can optionally span across.
77+
properties:
78+
attributes:
79+
additionalProperties:
80+
type: string
81+
description: Attributes is a free form map of attributes an
82+
infrastructure provider might use or require.
83+
type: object
84+
controlPlane:
85+
description: ControlPlane determines if this failure domain
86+
is suitable for use by control plane machines.
87+
type: boolean
88+
type: object
89+
description: FailureDomains specifies a list fo available availability
90+
zones that can be used
91+
type: object
92+
ready:
93+
description: Ready is when the ROSAControlPlane has a API server URL.
94+
type: boolean
95+
type: object
96+
type: object
97+
served: true
98+
storage: true
99+
subresources:
100+
status: {}

config/rbac/role.yaml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,15 @@ rules:
135135
- get
136136
- patch
137137
- update
138+
- apiGroups:
139+
- controlplane.cluster.x-k8s.io
140+
resources:
141+
- rosacontrolplanes
142+
- rosacontrolplanes/status
143+
verbs:
144+
- get
145+
- list
146+
- watch
138147
- apiGroups:
139148
- ""
140149
resources:
@@ -331,3 +340,22 @@ rules:
331340
- get
332341
- patch
333342
- update
343+
- apiGroups:
344+
- infrastructure.cluster.x-k8s.io
345+
resources:
346+
- rosaclusters
347+
verbs:
348+
- delete
349+
- get
350+
- list
351+
- patch
352+
- update
353+
- watch
354+
- apiGroups:
355+
- infrastructure.cluster.x-k8s.io
356+
resources:
357+
- rosaclusters/status
358+
verbs:
359+
- get
360+
- patch
361+
- update

controllers/rosacluster_controller.go

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
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 controllers
18+
19+
import (
20+
"context"
21+
"fmt"
22+
23+
"github.com/pkg/errors"
24+
apierrors "k8s.io/apimachinery/pkg/api/errors"
25+
"k8s.io/apimachinery/pkg/types"
26+
"k8s.io/client-go/tools/record"
27+
"k8s.io/klog/v2"
28+
ctrl "sigs.k8s.io/controller-runtime"
29+
"sigs.k8s.io/controller-runtime/pkg/client"
30+
"sigs.k8s.io/controller-runtime/pkg/controller"
31+
"sigs.k8s.io/controller-runtime/pkg/handler"
32+
"sigs.k8s.io/controller-runtime/pkg/reconcile"
33+
"sigs.k8s.io/controller-runtime/pkg/source"
34+
35+
infrav1 "sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta2"
36+
rosacontrolplanev1 "sigs.k8s.io/cluster-api-provider-aws/v2/controlplane/rosa/api/v1beta2"
37+
expinfrav1 "sigs.k8s.io/cluster-api-provider-aws/v2/exp/api/v1beta2"
38+
"sigs.k8s.io/cluster-api-provider-aws/v2/pkg/logger"
39+
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
40+
"sigs.k8s.io/cluster-api/util"
41+
"sigs.k8s.io/cluster-api/util/annotations"
42+
"sigs.k8s.io/cluster-api/util/patch"
43+
"sigs.k8s.io/cluster-api/util/predicates"
44+
)
45+
46+
// ROSAClusterReconciler reconciles ROSACluster.
47+
type ROSAClusterReconciler struct {
48+
client.Client
49+
Recorder record.EventRecorder
50+
WatchFilterValue string
51+
}
52+
53+
// +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=rosaclusters,verbs=get;list;watch;update;patch;delete
54+
// +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=rosaclusters/status,verbs=get;update;patch
55+
// +kubebuilder:rbac:groups=controlplane.cluster.x-k8s.io,resources=rosacontrolplanes;rosacontrolplanes/status,verbs=get;list;watch
56+
// +kubebuilder:rbac:groups=cluster.x-k8s.io,resources=clusters;clusters/status,verbs=get;list;watch
57+
// +kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch;create;update;patch;delete
58+
59+
func (r *ROSAClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.Result, reterr error) {
60+
log := ctrl.LoggerFrom(ctx)
61+
log.Info("Reconciling ROSACluster")
62+
63+
// Fetch the ROSACluster instance
64+
rosaCluster := &expinfrav1.ROSACluster{}
65+
err := r.Get(ctx, req.NamespacedName, rosaCluster)
66+
if err != nil {
67+
if apierrors.IsNotFound(err) {
68+
return reconcile.Result{}, nil
69+
}
70+
return reconcile.Result{}, err
71+
}
72+
73+
// Fetch the Cluster.
74+
cluster, err := util.GetOwnerCluster(ctx, r.Client, rosaCluster.ObjectMeta)
75+
if err != nil {
76+
return reconcile.Result{}, err
77+
}
78+
if cluster == nil {
79+
log.Info("Cluster Controller has not yet set OwnerRef")
80+
return reconcile.Result{}, nil
81+
}
82+
83+
if annotations.IsPaused(cluster, rosaCluster) {
84+
log.Info("ROSACluster or linked Cluster is marked as paused. Won't reconcile")
85+
return reconcile.Result{}, nil
86+
}
87+
88+
log = log.WithValues("cluster", cluster.Name)
89+
90+
controlPlane := &rosacontrolplanev1.ROSAControlPlane{}
91+
controlPlaneRef := types.NamespacedName{
92+
Name: cluster.Spec.ControlPlaneRef.Name,
93+
Namespace: cluster.Spec.ControlPlaneRef.Namespace,
94+
}
95+
96+
if err := r.Get(ctx, controlPlaneRef, controlPlane); err != nil {
97+
return reconcile.Result{}, fmt.Errorf("failed to get control plane ref: %w", err)
98+
}
99+
100+
log = log.WithValues("controlPlane", controlPlaneRef.Name)
101+
102+
patchHelper, err := patch.NewHelper(rosaCluster, r.Client)
103+
if err != nil {
104+
return reconcile.Result{}, fmt.Errorf("failed to init patch helper: %w", err)
105+
}
106+
107+
// Set the values from the managed control plane
108+
rosaCluster.Status.Ready = true
109+
rosaCluster.Spec.ControlPlaneEndpoint = controlPlane.Spec.ControlPlaneEndpoint
110+
// rosaCluster.Status.FailureDomains = controlPlane.Status.FailureDomains
111+
112+
if err := patchHelper.Patch(ctx, rosaCluster); err != nil {
113+
return reconcile.Result{}, fmt.Errorf("failed to patch ROSACluster: %w", err)
114+
}
115+
116+
log.Info("Successfully reconciled ROSACluster")
117+
118+
return reconcile.Result{}, nil
119+
}
120+
121+
func (r *ROSAClusterReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error {
122+
log := logger.FromContext(ctx)
123+
124+
rosaCluster := &expinfrav1.ROSACluster{}
125+
126+
controller, err := ctrl.NewControllerManagedBy(mgr).
127+
WithOptions(options).
128+
For(rosaCluster).
129+
WithEventFilter(predicates.ResourceNotPausedAndHasFilterLabel(ctrl.LoggerFrom(ctx), r.WatchFilterValue)).
130+
Build(r)
131+
132+
if err != nil {
133+
return fmt.Errorf("error creating controller: %w", err)
134+
}
135+
136+
// Add a watch for clusterv1.Cluster unpaise
137+
if err = controller.Watch(
138+
source.Kind(mgr.GetCache(), &clusterv1.Cluster{}),
139+
handler.EnqueueRequestsFromMapFunc(util.ClusterToInfrastructureMapFunc(ctx, infrav1.GroupVersion.WithKind("ROSACluster"), mgr.GetClient(), &expinfrav1.ROSACluster{})),
140+
predicates.ClusterUnpaused(log.GetLogger()),
141+
); err != nil {
142+
return fmt.Errorf("failed adding a watch for ready clusters: %w", err)
143+
}
144+
145+
// Add a watch for ROSAControlPlane
146+
if err = controller.Watch(
147+
source.Kind(mgr.GetCache(), &rosacontrolplanev1.ROSAControlPlane{}),
148+
handler.EnqueueRequestsFromMapFunc(r.rosaControlPlaneToManagedCluster(log)),
149+
); err != nil {
150+
return fmt.Errorf("failed adding watch on ROSAControlPlane: %w", err)
151+
}
152+
153+
return nil
154+
}
155+
156+
func (r *ROSAClusterReconciler) rosaControlPlaneToManagedCluster(log *logger.Logger) handler.MapFunc {
157+
return func(ctx context.Context, o client.Object) []ctrl.Request {
158+
ROSAControlPlane, ok := o.(*rosacontrolplanev1.ROSAControlPlane)
159+
if !ok {
160+
log.Error(errors.Errorf("expected an ROSAControlPlane, got %T instead", o), "failed to map ROSAControlPlane")
161+
return nil
162+
}
163+
164+
log := log.WithValues("objectMapper", "awsmcpTomc", "ROSAcontrolplane", klog.KRef(ROSAControlPlane.Namespace, ROSAControlPlane.Name))
165+
166+
if !ROSAControlPlane.ObjectMeta.DeletionTimestamp.IsZero() {
167+
log.Info("ROSAControlPlane has a deletion timestamp, skipping mapping")
168+
return nil
169+
}
170+
171+
if ROSAControlPlane.Spec.ControlPlaneEndpoint.IsZero() {
172+
log.Debug("ROSAControlPlane has no control plane endpoint, skipping mapping")
173+
return nil
174+
}
175+
176+
cluster, err := util.GetOwnerCluster(ctx, r.Client, ROSAControlPlane.ObjectMeta)
177+
if err != nil {
178+
log.Error(err, "failed to get owning cluster")
179+
return nil
180+
}
181+
if cluster == nil {
182+
log.Info("no owning cluster, skipping mapping")
183+
return nil
184+
}
185+
186+
managedClusterRef := cluster.Spec.InfrastructureRef
187+
if managedClusterRef == nil || managedClusterRef.Kind != "ROSACluster" {
188+
log.Info("InfrastructureRef is nil or not ROSACluster, skipping mapping")
189+
return nil
190+
}
191+
192+
return []ctrl.Request{
193+
{
194+
NamespacedName: types.NamespacedName{
195+
Name: managedClusterRef.Name,
196+
Namespace: managedClusterRef.Namespace,
197+
},
198+
},
199+
}
200+
}
201+
}

0 commit comments

Comments
 (0)