Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce rosaControlPlane for managed kubernetes #4453

Merged
merged 8 commits into from
Oct 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ generate-go-apis: ## Alias for .build/generate-go-apis
paths=./$(EXP_DIR)/api/... \
paths=./bootstrap/eks/api/... \
paths=./controlplane/eks/api/... \
paths=./controlplane/rosa/api/... \
paths=./iam/api/... \
paths=./controllers/... \
paths=./$(EXP_DIR)/controllers/... \
Expand Down Expand Up @@ -273,6 +274,14 @@ generate-go-apis: ## Alias for .build/generate-go-apis
--output-file-base=zz_generated.conversion $(GEN_OUTPUT_BASE) \
--go-header-file=./hack/boilerplate/boilerplate.generatego.txt

$(CONVERSION_GEN) \
--input-dirs=./controlplane/rosa/api/v1beta2 \
--extra-peer-dirs=sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta1 \
--extra-peer-dirs=sigs.k8s.io/cluster-api/api/v1beta1 \
--build-tag=ignore_autogenerated_conversions \
--output-file-base=zz_generated.conversion $(GEN_OUTPUT_BASE) \
--go-header-file=./hack/boilerplate/boilerplate.generatego.txt

touch $@

##@ lint and verify:
Expand Down Expand Up @@ -375,7 +384,7 @@ managers: ## Alias for manager-aws-infrastructure

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

##@ test:

Expand Down
339 changes: 339 additions & 0 deletions config/crd/bases/controlplane.cluster.x-k8s.io_rosacontrolplanes.yaml

Large diffs are not rendered by default.

100 changes: 100 additions & 0 deletions config/crd/bases/infrastructure.cluster.x-k8s.io_rosaclusters.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.12.1
name: rosaclusters.infrastructure.cluster.x-k8s.io
spec:
group: infrastructure.cluster.x-k8s.io
names:
categories:
- cluster-api
kind: ROSACluster
listKind: ROSAClusterList
plural: rosaclusters
shortNames:
- rosac
singular: rosacluster
scope: Namespaced
versions:
- additionalPrinterColumns:
- description: Cluster to which this AWSManagedControl belongs
jsonPath: .metadata.labels.cluster\.x-k8s\.io/cluster-name
name: Cluster
type: string
- description: Control plane infrastructure is ready for worker nodes
jsonPath: .status.ready
name: Ready
type: string
- description: API Endpoint
jsonPath: .spec.controlPlaneEndpoint.host
name: Endpoint
priority: 1
type: string
name: v1beta2
schema:
openAPIV3Schema:
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
of an object. Servers should convert recognized schemas to the latest
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this
object represents. Servers may infer this from the endpoint the client
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
properties:
controlPlaneEndpoint:
description: ControlPlaneEndpoint represents the endpoint used to
communicate with the control plane.
properties:
host:
description: The hostname on which the API server is serving.
type: string
port:
description: The port on which the API server is serving.
format: int32
type: integer
required:
- host
- port
type: object
type: object
status:
description: ROSAClusterStatus defines the observed state of ROSACluster
properties:
failureDomains:
additionalProperties:
description: FailureDomainSpec is the Schema for Cluster API failure
domains. It allows controllers to understand how many failure
domains a cluster can optionally span across.
properties:
attributes:
additionalProperties:
type: string
description: Attributes is a free form map of attributes an
infrastructure provider might use or require.
type: object
controlPlane:
description: ControlPlane determines if this failure domain
is suitable for use by control plane machines.
type: boolean
type: object
description: FailureDomains specifies a list fo available availability
zones that can be used
type: object
ready:
description: Ready is when the ROSAControlPlane has a API server URL.
type: boolean
type: object
type: object
served: true
storage: true
subresources:
status: {}
28 changes: 28 additions & 0 deletions config/rbac/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,15 @@ rules:
- get
- patch
- update
- apiGroups:
- controlplane.cluster.x-k8s.io
resources:
- rosacontrolplanes
- rosacontrolplanes/status
verbs:
- get
- list
- watch
- apiGroups:
- ""
resources:
Expand Down Expand Up @@ -331,3 +340,22 @@ rules:
- get
- patch
- update
- apiGroups:
- infrastructure.cluster.x-k8s.io
resources:
- rosaclusters
verbs:
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- infrastructure.cluster.x-k8s.io
resources:
- rosaclusters/status
verbs:
- get
- patch
- update
201 changes: 201 additions & 0 deletions controllers/rosacluster_controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
/*
Copyright 2023 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 controllers

import (
"context"
"fmt"

"github.com/pkg/errors"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/tools/record"
"k8s.io/klog/v2"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"sigs.k8s.io/controller-runtime/pkg/source"

infrav1 "sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta2"
rosacontrolplanev1 "sigs.k8s.io/cluster-api-provider-aws/v2/controlplane/rosa/api/v1beta2"
expinfrav1 "sigs.k8s.io/cluster-api-provider-aws/v2/exp/api/v1beta2"
"sigs.k8s.io/cluster-api-provider-aws/v2/pkg/logger"
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
"sigs.k8s.io/cluster-api/util"
"sigs.k8s.io/cluster-api/util/annotations"
"sigs.k8s.io/cluster-api/util/patch"
"sigs.k8s.io/cluster-api/util/predicates"
)

// ROSAClusterReconciler reconciles ROSACluster.
type ROSAClusterReconciler struct {
client.Client
Recorder record.EventRecorder
WatchFilterValue string
}

// +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=rosaclusters,verbs=get;list;watch;update;patch;delete
// +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=rosaclusters/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=controlplane.cluster.x-k8s.io,resources=rosacontrolplanes;rosacontrolplanes/status,verbs=get;list;watch
// +kubebuilder:rbac:groups=cluster.x-k8s.io,resources=clusters;clusters/status,verbs=get;list;watch
// +kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch;create;update;patch;delete

func (r *ROSAClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.Result, reterr error) {
log := ctrl.LoggerFrom(ctx)
log.Info("Reconciling ROSACluster")

// Fetch the ROSACluster instance
rosaCluster := &expinfrav1.ROSACluster{}
err := r.Get(ctx, req.NamespacedName, rosaCluster)
if err != nil {
if apierrors.IsNotFound(err) {
return reconcile.Result{}, nil
}
return reconcile.Result{}, err
}

// Fetch the Cluster.
cluster, err := util.GetOwnerCluster(ctx, r.Client, rosaCluster.ObjectMeta)
if err != nil {
return reconcile.Result{}, err
}
if cluster == nil {
log.Info("Cluster Controller has not yet set OwnerRef")
return reconcile.Result{}, nil
}

if annotations.IsPaused(cluster, rosaCluster) {
log.Info("ROSACluster or linked Cluster is marked as paused. Won't reconcile")
return reconcile.Result{}, nil
}

log = log.WithValues("cluster", cluster.Name)

controlPlane := &rosacontrolplanev1.ROSAControlPlane{}
controlPlaneRef := types.NamespacedName{
Name: cluster.Spec.ControlPlaneRef.Name,
Namespace: cluster.Spec.ControlPlaneRef.Namespace,
}

if err := r.Get(ctx, controlPlaneRef, controlPlane); err != nil {
return reconcile.Result{}, fmt.Errorf("failed to get control plane ref: %w", err)
}

log = log.WithValues("controlPlane", controlPlaneRef.Name)

patchHelper, err := patch.NewHelper(rosaCluster, r.Client)
if err != nil {
return reconcile.Result{}, fmt.Errorf("failed to init patch helper: %w", err)
}

// Set the values from the managed control plane
rosaCluster.Status.Ready = true
rosaCluster.Spec.ControlPlaneEndpoint = controlPlane.Spec.ControlPlaneEndpoint
// rosaCluster.Status.FailureDomains = controlPlane.Status.FailureDomains

if err := patchHelper.Patch(ctx, rosaCluster); err != nil {
return reconcile.Result{}, fmt.Errorf("failed to patch ROSACluster: %w", err)
}

log.Info("Successfully reconciled ROSACluster")

return reconcile.Result{}, nil
}

func (r *ROSAClusterReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error {
log := logger.FromContext(ctx)

rosaCluster := &expinfrav1.ROSACluster{}

controller, err := ctrl.NewControllerManagedBy(mgr).
WithOptions(options).
For(rosaCluster).
WithEventFilter(predicates.ResourceNotPausedAndHasFilterLabel(ctrl.LoggerFrom(ctx), r.WatchFilterValue)).
Build(r)

if err != nil {
return fmt.Errorf("error creating controller: %w", err)
}

// Add a watch for clusterv1.Cluster unpaise
if err = controller.Watch(
source.Kind(mgr.GetCache(), &clusterv1.Cluster{}),
handler.EnqueueRequestsFromMapFunc(util.ClusterToInfrastructureMapFunc(ctx, infrav1.GroupVersion.WithKind("ROSACluster"), mgr.GetClient(), &expinfrav1.ROSACluster{})),
predicates.ClusterUnpaused(log.GetLogger()),
); err != nil {
return fmt.Errorf("failed adding a watch for ready clusters: %w", err)
}

// Add a watch for ROSAControlPlane
if err = controller.Watch(
source.Kind(mgr.GetCache(), &rosacontrolplanev1.ROSAControlPlane{}),
handler.EnqueueRequestsFromMapFunc(r.rosaControlPlaneToManagedCluster(log)),
); err != nil {
return fmt.Errorf("failed adding watch on ROSAControlPlane: %w", err)
}

return nil
}

func (r *ROSAClusterReconciler) rosaControlPlaneToManagedCluster(log *logger.Logger) handler.MapFunc {
return func(ctx context.Context, o client.Object) []ctrl.Request {
ROSAControlPlane, ok := o.(*rosacontrolplanev1.ROSAControlPlane)
if !ok {
log.Error(errors.Errorf("expected an ROSAControlPlane, got %T instead", o), "failed to map ROSAControlPlane")
return nil
}

log := log.WithValues("objectMapper", "awsmcpTomc", "ROSAcontrolplane", klog.KRef(ROSAControlPlane.Namespace, ROSAControlPlane.Name))

if !ROSAControlPlane.ObjectMeta.DeletionTimestamp.IsZero() {
log.Info("ROSAControlPlane has a deletion timestamp, skipping mapping")
return nil
}

if ROSAControlPlane.Spec.ControlPlaneEndpoint.IsZero() {
log.Debug("ROSAControlPlane has no control plane endpoint, skipping mapping")
return nil
}

cluster, err := util.GetOwnerCluster(ctx, r.Client, ROSAControlPlane.ObjectMeta)
if err != nil {
log.Error(err, "failed to get owning cluster")
return nil
}
if cluster == nil {
log.Info("no owning cluster, skipping mapping")
return nil
}

managedClusterRef := cluster.Spec.InfrastructureRef
if managedClusterRef == nil || managedClusterRef.Kind != "ROSACluster" {
log.Info("InfrastructureRef is nil or not ROSACluster, skipping mapping")
return nil
}

return []ctrl.Request{
{
NamespacedName: types.NamespacedName{
Name: managedClusterRef.Name,
Namespace: managedClusterRef.Namespace,
},
},
}
}
}
Loading