diff --git a/.gitignore b/.gitignore index fb99ac0..0f6655b 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,4 @@ _certs bin dist/ *.tgz +.DS_Store diff --git a/Makefile b/Makefile index 622da4d..a9115c3 100644 --- a/Makefile +++ b/Makefile @@ -36,7 +36,7 @@ GIT_COMMIT := $(shell git rev-parse --short HEAD || echo 'local') GIT_DIRTY := $(shell git diff --quiet && echo 'clean' || echo 'dirty') GIT_VERSION := $(shell go mod edit -json | jq '.Require[] | select(.Path == "k8s.io/client-go") | .Version' --raw-output)+kflex-$(shell git describe --tags --match='v*' --abbrev=14 "$(GIT_COMMIT)^{commit}" 2>/dev/null || echo v0.0.0-$(GIT_COMMIT)) BUILD_DATE := $(shell date -u +'%Y-%m-%dT%H:%M:%SZ') -MAIN_VERSION := $(shell git describe --tags --match='v*' --abbrev=14 "$(GIT_COMMIT)^{commit}") +MAIN_VERSION := $(shell git tag -l --sort=-v:refname | head -n1) LDFLAGS := \ -X main.Version=${MAIN_VERSION}.${GIT_COMMIT} \ -X main.BuildDate=${BUILD_DATE} \ @@ -186,7 +186,7 @@ CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen ENVTEST ?= $(LOCALBIN)/setup-envtest ## Tool Versions -KUSTOMIZE_VERSION ?= v5.0.0 +KUSTOMIZE_VERSION ?= v5.1.0 CONTROLLER_TOOLS_VERSION ?= v0.11.3 KUSTOMIZE_INSTALL_SCRIPT ?= "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" diff --git a/api/v1alpha1/conditions.go b/api/v1alpha1/conditions.go index d67921b..d91c765 100644 --- a/api/v1alpha1/conditions.go +++ b/api/v1alpha1/conditions.go @@ -38,9 +38,8 @@ type ControlPlaneCondition struct { } // areConditionsEqual compares two ControlPlaneCondition structs and -// returns true if they are equal (excluding LastTransitionTime -// -// and LastUpdateTime), false otherwise. +// returns true if they are equal (excluding LastTransitionTime and LastUpdateTime), +// false otherwise. func AreConditionsEqual(c1, c2 ControlPlaneCondition) bool { if c1.Type != c2.Type || c1.Status != c2.Status || c1.Reason != c2.Reason || c1.Message != c2.Message { return false @@ -101,3 +100,86 @@ func AreConditionSlicesSame(c1, c2 []ControlPlaneCondition) bool { } return true } + +func EnsureCondition(cp *ControlPlane, newCondition ControlPlaneCondition) { + if cp.Status.Conditions == nil { + cp.Status.Conditions = []ControlPlaneCondition{} + } + cp.Status.Conditions = SetCondition(cp.Status.Conditions, newCondition) +} + +// Creating returns a condition that indicates the cp is currently +// being created. +func ConditionCreating() ControlPlaneCondition { + return ControlPlaneCondition{ + Type: TypeReady, + Status: corev1.ConditionFalse, + LastTransitionTime: metav1.Now(), + LastUpdateTime: metav1.Now(), + Reason: ReasonCreating, + } +} + +// Deleting returns a condition that indicates the cp is currently +// being deleted. +func ConditionDeleting() ControlPlaneCondition { + return ControlPlaneCondition{ + Type: TypeReady, + Status: corev1.ConditionFalse, + LastTransitionTime: metav1.Now(), + LastUpdateTime: metav1.Now(), + Reason: ReasonDeleting, + } +} + +// Available returns a condition that indicates the resource is +// currently observed to be available for use. +func ConditionAvailable() ControlPlaneCondition { + return ControlPlaneCondition{ + Type: TypeReady, + Status: corev1.ConditionTrue, + LastTransitionTime: metav1.Now(), + LastUpdateTime: metav1.Now(), + Reason: ReasonAvailable, + } +} + +// Unavailable returns a condition that indicates the resource is not +// currently available for use. +func ConditionUnavailable() ControlPlaneCondition { + return ControlPlaneCondition{ + Type: TypeReady, + Status: corev1.ConditionFalse, + LastTransitionTime: metav1.Now(), + LastUpdateTime: metav1.Now(), + Reason: ReasonUnavailable, + } +} + +// ReconcileSuccess returns a condition indicating that KubeFlex reconciled the resource +func ConditionReconcileSuccess() ControlPlaneCondition { + return ControlPlaneCondition{ + Type: TypeSynced, + Status: corev1.ConditionTrue, + LastTransitionTime: metav1.Now(), + LastUpdateTime: metav1.Now(), + Reason: ReasonReconcileSuccess, + } +} + +// ReconcileError returns a condition indicating that KubeFlex encountered an +// error while reconciling the resource. +func ConditionReconcileError(err error) ControlPlaneCondition { + return ControlPlaneCondition{ + Type: TypeSynced, + Status: corev1.ConditionFalse, + LastTransitionTime: metav1.Now(), + LastUpdateTime: metav1.Now(), + Reason: ReasonReconcileError, + Message: err.Error(), + } +} + +func myAppend(list *[]string, value string) { + *list = append(*list, value) +} diff --git a/api/v1alpha1/controlplane_types.go b/api/v1alpha1/controlplane_types.go index 58fc2d3..94c18b5 100644 --- a/api/v1alpha1/controlplane_types.go +++ b/api/v1alpha1/controlplane_types.go @@ -33,11 +33,13 @@ type ControlPlaneStatus struct { ObservedGeneration int64 `json:"observedGeneration"` } -//+kubebuilder:object:root=true -//+kubebuilder:subresource:status -//+kubebuilder:resource:scope=Cluster - // ControlPlane is the Schema for the controlplanes API +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="SYNCED",type="string",JSONPath=".status.conditions[?(@.type=='Synced')].status" +// +kubebuilder:printcolumn:name="READY",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status" +// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp" +// +kubebuilder:resource:scope=Cluster,shortName=cp type ControlPlane struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` diff --git a/config/crd/bases/tenancy.kflex.kubestellar.org_controlplanes.yaml b/config/crd/bases/tenancy.kflex.kubestellar.org_controlplanes.yaml index 1955cca..e3f2105 100644 --- a/config/crd/bases/tenancy.kflex.kubestellar.org_controlplanes.yaml +++ b/config/crd/bases/tenancy.kflex.kubestellar.org_controlplanes.yaml @@ -12,10 +12,22 @@ spec: kind: ControlPlane listKind: ControlPlaneList plural: controlplanes + shortNames: + - cp singular: controlplane scope: Cluster versions: - - name: v1alpha1 + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=='Synced')].status + name: SYNCED + type: string + - jsonPath: .status.conditions[?(@.type=='Ready')].status + name: READY + type: string + - jsonPath: .metadata.creationTimestamp + name: AGE + type: date + name: v1alpha1 schema: openAPIV3Schema: description: ControlPlane is the Schema for the controlplanes API diff --git a/internal/controller/controlplane_controller.go b/internal/controller/controlplane_controller.go index e8e4aa2..1219762 100644 --- a/internal/controller/controlplane_controller.go +++ b/internal/controller/controlplane_controller.go @@ -18,24 +18,20 @@ package controller import ( "context" - "fmt" - "time" + "github.com/pkg/errors" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" networkingv1 "k8s.io/api/networking/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/client-go/util/workqueue" 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" clog "sigs.k8s.io/controller-runtime/pkg/log" tenancyv1alpha1 "github.com/kubestellar/kubeflex/api/v1alpha1" "github.com/kubestellar/kubeflex/pkg/certs" + "github.com/kubestellar/kubeflex/pkg/util" ) // ControlPlaneReconciler reconciles a ControlPlane object @@ -80,92 +76,86 @@ func (r *ControlPlaneReconciler) Reconcile(ctx context.Context, req ctrl.Request return ctrl.Result{}, err } hcp := hostedControlPlane.DeepCopy() - ownerRef := &v1.OwnerReference{ - Kind: hcp.Kind, - APIVersion: hcp.APIVersion, - Name: hcp.Name, - UID: hcp.UID, - } - confGen := certs.ConfigGen{ - CpName: hcp.Name, - CpHost: hcp.Name, - CpPort: SecurePort, + + // check if API server is already in a ready state + ready, _ := util.IsAPIServerDeploymentReady(r.Client, *hcp) + if ready { + tenancyv1alpha1.EnsureCondition(hcp, tenancyv1alpha1.ConditionAvailable()) + } else { + tenancyv1alpha1.EnsureCondition(hcp, tenancyv1alpha1.ConditionUnavailable()) } - if err = r.ReconcileNamespace(ctx, hcp.Name, ownerRef); err != nil { - return ctrl.Result{}, err + if err = r.ReconcileNamespace(ctx, hcp); err != nil { + return r.UpdateStatusForSyncingError(hcp, err) } - crts, err := r.ReconcileCertsSecret(ctx, hcp.Name, ownerRef) + crts, err := r.ReconcileCertsSecret(ctx, hcp) if err != nil { - return ctrl.Result{}, err + return r.UpdateStatusForSyncingError(hcp, err) } // reconcile kubeconfig for admin + confGen := certs.ConfigGen{CpName: hcp.Name, CpHost: hcp.Name, CpPort: SecurePort} confGen.Target = certs.Admin - if err = r.ReconcileKubeconfigSecret(ctx, crts, confGen, ownerRef); err != nil { - return ctrl.Result{}, err + if err = r.ReconcileKubeconfigSecret(ctx, crts, confGen, hcp); err != nil { + return r.UpdateStatusForSyncingError(hcp, err) } // reconcile kubeconfig for cm confGen.Target = certs.ControllerManager confGen.CpHost = hcp.Name - if err = r.ReconcileKubeconfigSecret(ctx, crts, confGen, ownerRef); err != nil { - return ctrl.Result{}, err + if err = r.ReconcileKubeconfigSecret(ctx, crts, confGen, hcp); err != nil { + return r.UpdateStatusForSyncingError(hcp, err) } - if err = r.ReconcileAPIServerDeployment(ctx, hcp.Name, ownerRef); err != nil { - return ctrl.Result{}, err + if err = r.ReconcileAPIServerDeployment(ctx, hcp); err != nil { + return r.UpdateStatusForSyncingError(hcp, err) } - if err = r.ReconcileAPIServerService(ctx, hcp.Name, ownerRef); err != nil { - return ctrl.Result{}, err + if err = r.ReconcileAPIServerService(ctx, hcp); err != nil { + return r.UpdateStatusForSyncingError(hcp, err) } - if err = r.ReconcileAPIServerIngress(ctx, hcp.Name, ownerRef); err != nil { - return ctrl.Result{}, err + if err = r.ReconcileAPIServerIngress(ctx, hcp); err != nil { + return r.UpdateStatusForSyncingError(hcp, err) } - if err = r.ReconcileCMDeployment(ctx, hcp.Name, ownerRef); err != nil { - return ctrl.Result{}, err + if err = r.ReconcileCMDeployment(ctx, hcp); err != nil { + return r.UpdateStatusForSyncingError(hcp, err) } - log.Info("Hosted control plane", "my-name-is", hcp.Name) - - return ctrl.Result{}, nil + return r.UpdateStatusForSyncingSuccess(ctx, hcp) } // SetupWithManager sets up the controller with the Manager. func (r *ControlPlaneReconciler) SetupWithManager(mgr ctrl.Manager) error { - b := ctrl.NewControllerManagedBy(mgr). + return ctrl.NewControllerManagedBy(mgr). For(&tenancyv1alpha1.ControlPlane{}). - WithOptions(controller.Options{ - RateLimiter: workqueue.NewItemExponentialFailureRateLimiter(1*time.Second, 10*time.Second), - }) - for _, handler := range r.eventHandlers() { - b.Watches(handler.obj, handler.handler) - } - if _, err := b.Build(r); err != nil { - return fmt.Errorf("failed setting up with a controller manager %w", err) - } - return nil + Owns(&corev1.Service{}). + Owns(&networkingv1.Ingress{}). + Owns(&appsv1.Deployment{}). + Owns(&appsv1.StatefulSet{}). + Owns(&corev1.Secret{}). + Owns(&corev1.ConfigMap{}). + Owns(&corev1.ServiceAccount{}). + Complete(r) } -type eventHandler struct { - obj client.Object - handler handler.EventHandler +func (r *ControlPlaneReconciler) UpdateStatusForSyncingError(hcp *tenancyv1alpha1.ControlPlane, e error) (ctrl.Result, error) { + tenancyv1alpha1.EnsureCondition(hcp, tenancyv1alpha1.ConditionReconcileError(e)) + err := r.Status().Update(context.Background(), hcp) + if err != nil { + return ctrl.Result{}, errors.Wrap(e, err.Error()) + } + return ctrl.Result{}, err } -func (r *ControlPlaneReconciler) eventHandlers() []eventHandler { - - handlers := []eventHandler{ - {obj: &corev1.Service{}, handler: handler.EnqueueRequestForOwner(r.Scheme, r.RESTMapper(), &corev1.Service{}, handler.OnlyControllerOwner())}, - {obj: &networkingv1.Ingress{}, handler: handler.EnqueueRequestForOwner(r.Scheme, r.RESTMapper(), &networkingv1.Ingress{}, handler.OnlyControllerOwner())}, - {obj: &appsv1.Deployment{}, handler: handler.EnqueueRequestForOwner(r.Scheme, r.RESTMapper(), &appsv1.Deployment{}, handler.OnlyControllerOwner())}, - {obj: &appsv1.StatefulSet{}, handler: handler.EnqueueRequestForOwner(r.Scheme, r.RESTMapper(), &appsv1.StatefulSet{}, handler.OnlyControllerOwner())}, - {obj: &corev1.Secret{}, handler: handler.EnqueueRequestForOwner(r.Scheme, r.RESTMapper(), &corev1.Secret{}, handler.OnlyControllerOwner())}, - {obj: &corev1.ConfigMap{}, handler: handler.EnqueueRequestForOwner(r.Scheme, r.RESTMapper(), &corev1.ConfigMap{}, handler.OnlyControllerOwner())}, - {obj: &corev1.ServiceAccount{}, handler: handler.EnqueueRequestForOwner(r.Scheme, r.RESTMapper(), &corev1.ServiceAccount{}, handler.OnlyControllerOwner())}, +func (r *ControlPlaneReconciler) UpdateStatusForSyncingSuccess(ctx context.Context, hcp *tenancyv1alpha1.ControlPlane) (ctrl.Result, error) { + _ = clog.FromContext(ctx) + tenancyv1alpha1.EnsureCondition(hcp, tenancyv1alpha1.ConditionReconcileSuccess()) + err := r.Status().Update(context.Background(), hcp) + if err != nil { + return ctrl.Result{}, err } - return handlers + return ctrl.Result{}, err } diff --git a/internal/controller/deployment.go b/internal/controller/deployment.go index 503515c..65a67c7 100644 --- a/internal/controller/deployment.go +++ b/internal/controller/deployment.go @@ -28,24 +28,24 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/utils/pointer" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" clog "sigs.k8s.io/controller-runtime/pkg/log" + tenancyv1alpha1 "github.com/kubestellar/kubeflex/api/v1alpha1" "github.com/kubestellar/kubeflex/pkg/util" ) const ( - APIServerDeploymentName = "kube-apiserver" - CMDeploymentName = "kube-controller-manager" - SecurePort = 9444 - cmHealthzPort = 10257 + SecurePort = 9444 + cmHealthzPort = 10257 ) -func (r *ControlPlaneReconciler) ReconcileAPIServerDeployment(ctx context.Context, name string, owner *metav1.OwnerReference) error { +func (r *ControlPlaneReconciler) ReconcileAPIServerDeployment(ctx context.Context, hcp *tenancyv1alpha1.ControlPlane) error { _ = clog.FromContext(ctx) - namespace := util.GenerateNamespaceFromControlPlaneName(name) + namespace := util.GenerateNamespaceFromControlPlaneName(hcp.Name) deployment := &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ - Name: APIServerDeploymentName, + Name: util.APIServerDeploymentName, Namespace: namespace, }, } @@ -53,13 +53,14 @@ func (r *ControlPlaneReconciler) ReconcileAPIServerDeployment(ctx context.Contex err := r.Client.Get(context.TODO(), client.ObjectKeyFromObject(deployment), deployment, &client.GetOptions{}) if err != nil { if apierrors.IsNotFound(err) { - deployment, err = r.generateAPIServerDeployment(namespace, name) + deployment, err = r.generateAPIServerDeployment(namespace, hcp.Name) if err != nil { return err } - util.EnsureOwnerRef(deployment, owner) - err = r.Client.Create(context.TODO(), deployment, &client.CreateOptions{}) - if err != nil { + if err := controllerutil.SetControllerReference(hcp, deployment, r.Scheme); err != nil { + return err + } + if err = r.Client.Create(context.TODO(), deployment, &client.CreateOptions{}); err != nil { return err } } @@ -68,12 +69,12 @@ func (r *ControlPlaneReconciler) ReconcileAPIServerDeployment(ctx context.Contex return nil } -func (r *ControlPlaneReconciler) ReconcileCMDeployment(ctx context.Context, name string, owner *metav1.OwnerReference) error { +func (r *ControlPlaneReconciler) ReconcileCMDeployment(ctx context.Context, hcp *tenancyv1alpha1.ControlPlane) error { _ = clog.FromContext(ctx) - namespace := util.GenerateNamespaceFromControlPlaneName(name) + namespace := util.GenerateNamespaceFromControlPlaneName(hcp.Name) deployment := &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ - Name: CMDeploymentName, + Name: util.CMDeploymentName, Namespace: namespace, }, } @@ -81,13 +82,14 @@ func (r *ControlPlaneReconciler) ReconcileCMDeployment(ctx context.Context, name err := r.Client.Get(context.TODO(), client.ObjectKeyFromObject(deployment), deployment, &client.GetOptions{}) if err != nil { if apierrors.IsNotFound(err) { - deployment, err = r.generateCMDeployment(name, namespace) + deployment, err = r.generateCMDeployment(hcp.Name, namespace) if err != nil { return err } - util.EnsureOwnerRef(deployment, owner) - err = r.Client.Create(context.TODO(), deployment, &client.CreateOptions{}) - if err != nil { + if err := controllerutil.SetControllerReference(hcp, deployment, r.Scheme); err != nil { + return err + } + if err = r.Client.Create(context.TODO(), deployment, &client.CreateOptions{}); err != nil { return err } } @@ -103,7 +105,7 @@ func (r *ControlPlaneReconciler) generateAPIServerDeployment(namespace, dbName s } deployment := &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ - Name: APIServerDeploymentName, + Name: util.APIServerDeploymentName, Namespace: namespace, Labels: map[string]string{ "component": "kube-apiserver", @@ -263,7 +265,7 @@ func (r *ControlPlaneReconciler) generateAPIServerDeployment(namespace, dbName s func (r *ControlPlaneReconciler) generateCMDeployment(cpName, namespace string) (*appsv1.Deployment, error) { deployment := &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ - Name: CMDeploymentName, + Name: util.CMDeploymentName, Namespace: namespace, Labels: map[string]string{ "component": "kube-controller-manager", diff --git a/internal/controller/ingress.go b/internal/controller/ingress.go index c1a4a93..129691b 100644 --- a/internal/controller/ingress.go +++ b/internal/controller/ingress.go @@ -25,7 +25,10 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/utils/pointer" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" clog "sigs.k8s.io/controller-runtime/pkg/log" + + tenancyv1alpha1 "github.com/kubestellar/kubeflex/api/v1alpha1" ) const ( @@ -36,14 +39,14 @@ var ( pathTypePrefix = networkingv1.PathTypePrefix ) -func (r *ControlPlaneReconciler) ReconcileAPIServerIngress(ctx context.Context, name string, owner *metav1.OwnerReference) error { +func (r *ControlPlaneReconciler) ReconcileAPIServerIngress(ctx context.Context, hcp *tenancyv1alpha1.ControlPlane) error { _ = clog.FromContext(ctx) - namespace := util.GenerateNamespaceFromControlPlaneName(name) + namespace := util.GenerateNamespaceFromControlPlaneName(hcp.Name) // create service object ingress := &networkingv1.Ingress{ ObjectMeta: metav1.ObjectMeta{ - Name: name, + Name: hcp.Name, Namespace: namespace, }, } @@ -51,10 +54,11 @@ func (r *ControlPlaneReconciler) ReconcileAPIServerIngress(ctx context.Context, err := r.Client.Get(context.TODO(), client.ObjectKeyFromObject(ingress), ingress, &client.GetOptions{}) if err != nil { if apierrors.IsNotFound(err) { - ingress = generateAPIServerIngress(name, namespace) - util.EnsureOwnerRef(ingress, owner) - err = r.Client.Create(context.TODO(), ingress, &client.CreateOptions{}) - if err != nil { + ingress = generateAPIServerIngress(hcp.Name, namespace) + if err := controllerutil.SetControllerReference(hcp, ingress, r.Scheme); err != nil { + return nil + } + if err = r.Client.Create(context.TODO(), ingress, &client.CreateOptions{}); err != nil { return err } } diff --git a/internal/controller/namespace.go b/internal/controller/namespace.go index 36e1792..63a7707 100644 --- a/internal/controller/namespace.go +++ b/internal/controller/namespace.go @@ -24,12 +24,15 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" clog "sigs.k8s.io/controller-runtime/pkg/log" + + tenancyv1alpha1 "github.com/kubestellar/kubeflex/api/v1alpha1" ) -func (r *ControlPlaneReconciler) ReconcileNamespace(ctx context.Context, name string, owner *metav1.OwnerReference) error { +func (r *ControlPlaneReconciler) ReconcileNamespace(ctx context.Context, hcp *tenancyv1alpha1.ControlPlane) error { _ = clog.FromContext(ctx) - namespace := util.GenerateNamespaceFromControlPlaneName(name) + namespace := util.GenerateNamespaceFromControlPlaneName(hcp.Name) // create namespace object ns := &v1.Namespace{ @@ -41,9 +44,10 @@ func (r *ControlPlaneReconciler) ReconcileNamespace(ctx context.Context, name st err := r.Client.Get(context.TODO(), client.ObjectKeyFromObject(ns), ns, &client.GetOptions{}) if err != nil { if apierrors.IsNotFound(err) { - util.EnsureOwnerRef(ns, owner) - err = r.Client.Create(context.TODO(), ns, &client.CreateOptions{}) - if err != nil { + if err := controllerutil.SetControllerReference(hcp, ns, r.Scheme); err != nil { + return err + } + if err = r.Client.Create(context.TODO(), ns, &client.CreateOptions{}); err != nil { return err } } diff --git a/internal/controller/secret.go b/internal/controller/secret.go index 878fbbd..0e64587 100644 --- a/internal/controller/secret.go +++ b/internal/controller/secret.go @@ -23,15 +23,17 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" clog "sigs.k8s.io/controller-runtime/pkg/log" + tenancyv1alpha1 "github.com/kubestellar/kubeflex/api/v1alpha1" "github.com/kubestellar/kubeflex/pkg/certs" "github.com/kubestellar/kubeflex/pkg/util" ) -func (r *ControlPlaneReconciler) ReconcileCertsSecret(ctx context.Context, name string, owner *metav1.OwnerReference) (*certs.Certs, error) { +func (r *ControlPlaneReconciler) ReconcileCertsSecret(ctx context.Context, hcp *tenancyv1alpha1.ControlPlane) (*certs.Certs, error) { _ = clog.FromContext(ctx) - namespace := util.GenerateNamespaceFromControlPlaneName(name) + namespace := util.GenerateNamespaceFromControlPlaneName(hcp.Name) // create certs secret object csecret := &v1.Secret{ @@ -44,13 +46,14 @@ func (r *ControlPlaneReconciler) ReconcileCertsSecret(ctx context.Context, name err := r.Client.Get(context.TODO(), client.ObjectKeyFromObject(csecret), csecret, &client.GetOptions{}) if err != nil { if apierrors.IsNotFound(err) { - csecret, crts, err := generateCertsSecret(ctx, name, namespace) + csecret, crts, err := generateCertsSecret(ctx, hcp.Name, namespace) if err != nil { return nil, err } - util.EnsureOwnerRef(csecret, owner) - err = r.Client.Create(context.TODO(), csecret, &client.CreateOptions{}) - if err != nil { + if err := controllerutil.SetControllerReference(hcp, csecret, r.Scheme); err != nil { + return nil, err + } + if err = r.Client.Create(context.TODO(), csecret, &client.CreateOptions{}); err != nil { return nil, err } return crts, nil @@ -60,7 +63,7 @@ func (r *ControlPlaneReconciler) ReconcileCertsSecret(ctx context.Context, name return nil, nil } -func (r *ControlPlaneReconciler) ReconcileKubeconfigSecret(ctx context.Context, crts *certs.Certs, conf certs.ConfigGen, owner *metav1.OwnerReference) error { +func (r *ControlPlaneReconciler) ReconcileKubeconfigSecret(ctx context.Context, crts *certs.Certs, conf certs.ConfigGen, hcp *tenancyv1alpha1.ControlPlane) error { // TODO - temp hack - we should make this independent of the certs gen. // Should gen kconfig from certs secret otherwise it may fail if certs are not generated before this func if crts == nil { @@ -79,9 +82,10 @@ func (r *ControlPlaneReconciler) ReconcileKubeconfigSecret(ctx context.Context, err = r.Client.Get(context.TODO(), client.ObjectKeyFromObject(csecret), csecret, &client.GetOptions{}) if err != nil { if apierrors.IsNotFound(err) { - util.EnsureOwnerRef(csecret, owner) - err = r.Client.Create(context.TODO(), csecret, &client.CreateOptions{}) - if err != nil { + if err := controllerutil.SetControllerReference(hcp, csecret, r.Scheme); err != nil { + return err + } + if err = r.Client.Create(context.TODO(), csecret, &client.CreateOptions{}); err != nil { return err } } diff --git a/internal/controller/service.go b/internal/controller/service.go index 8ff114f..e48efca 100644 --- a/internal/controller/service.go +++ b/internal/controller/service.go @@ -24,17 +24,20 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" clog "sigs.k8s.io/controller-runtime/pkg/log" + + tenancyv1alpha1 "github.com/kubestellar/kubeflex/api/v1alpha1" ) -func (r *ControlPlaneReconciler) ReconcileAPIServerService(ctx context.Context, name string, owner *metav1.OwnerReference) error { +func (r *ControlPlaneReconciler) ReconcileAPIServerService(ctx context.Context, hcp *tenancyv1alpha1.ControlPlane) error { _ = clog.FromContext(ctx) - namespace := util.GenerateNamespaceFromControlPlaneName(name) + namespace := util.GenerateNamespaceFromControlPlaneName(hcp.Name) // create service object service := &corev1.Service{ ObjectMeta: metav1.ObjectMeta{ - Name: name, + Name: hcp.Name, Namespace: namespace, }, } @@ -42,8 +45,10 @@ func (r *ControlPlaneReconciler) ReconcileAPIServerService(ctx context.Context, err := r.Client.Get(context.TODO(), client.ObjectKeyFromObject(service), service, &client.GetOptions{}) if err != nil { if apierrors.IsNotFound(err) { - service := generateAPIServerService(name, namespace) - util.EnsureOwnerRef(service, owner) + service := generateAPIServerService(hcp.Name, namespace) + if err := controllerutil.SetControllerReference(hcp, service, r.Scheme); err != nil { + return nil + } err = r.Client.Create(context.TODO(), service, &client.CreateOptions{}) if err != nil { return err @@ -62,7 +67,7 @@ func generateAPIServerService(name, namespace string) *corev1.Service { }, Spec: corev1.ServiceSpec{ Selector: map[string]string{ - "app": APIServerDeploymentName, + "app": util.APIServerDeploymentName, }, Ports: []corev1.ServicePort{ { diff --git a/pkg/util/ownerref.go b/pkg/util/ownerref.go deleted file mode 100644 index a82f921..0000000 --- a/pkg/util/ownerref.go +++ /dev/null @@ -1,47 +0,0 @@ -/* -Copyright 2023 The KubeStellar 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 util - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -func EnsureOwnerRef(resource client.Object, ownerRef *metav1.OwnerReference) { - if ownerRef == nil { - return - } - ownerRefs := resource.GetOwnerReferences() - i := getOwnerRefIndex(ownerRefs, ownerRef) - if i == -1 { - ownerRefs = append(ownerRefs, *ownerRef) - } else { - ownerRefs[i] = *ownerRef - } - resource.SetOwnerReferences(ownerRefs) -} - -func getOwnerRefIndex(list []metav1.OwnerReference, ref *metav1.OwnerReference) int { - for i := range list { - // NOTE: The APIVersion may have changed with a new API Version, however the UID should remain the - // same. Use either to identify the owner reference. - if list[i].Kind == ref.Kind && (list[i].APIVersion == ref.APIVersion || list[i].UID == ref.UID) && list[i].Name == ref.Name { - return i - } - } - return -1 -} diff --git a/pkg/util/status_check.go b/pkg/util/status_check.go index 6778d00..329aac1 100644 --- a/pkg/util/status_check.go +++ b/pkg/util/status_check.go @@ -24,8 +24,12 @@ import ( v1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/watch" "k8s.io/client-go/kubernetes" + "sigs.k8s.io/controller-runtime/pkg/client" + + tenancyv1alpha1 "github.com/kubestellar/kubeflex/api/v1alpha1" ) // TODO - refactor in a single base "WaitFor" function that can operate on the resource types @@ -120,3 +124,34 @@ func WaitForNamespaceDeletion(clientset kubernetes.Clientset, name string) error } } } + +func IsAPIServerDeploymentReady(c client.Client, hcp tenancyv1alpha1.ControlPlane) (bool, error) { + ns := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: GenerateNamespaceFromControlPlaneName(hcp.Name), + }, + } + if err := c.Get(context.Background(), types.NamespacedName{Name: ns.Name}, ns); err != nil { + return false, err + } + + d := &v1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: APIServerDeploymentName, + Namespace: ns.Name, + }, + } + + if err := c.Get(context.Background(), types.NamespacedName{Name: d.Name, Namespace: d.Namespace}, d); err != nil { + return false, err + } + + // we need to ensure that there is al least one replicas in the spec + if d.Status.ReadyReplicas == d.Status.Replicas && + d.Status.Replicas == *d.Spec.Replicas && + *d.Spec.Replicas > 0 { + return true, nil + } + + return false, nil +} diff --git a/pkg/util/util.go b/pkg/util/util.go index 17301b5..fdea717 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -24,10 +24,12 @@ import ( ) const ( - ProjectName = "kubeflex" - DBReleaseName = "postgres" - SystemNamespace = "kubeflex-system" - IngressSecurePort = "9443" + APIServerDeploymentName = "kube-apiserver" + CMDeploymentName = "kube-controller-manager" + ProjectName = "kubeflex" + DBReleaseName = "postgres" + SystemNamespace = "kubeflex-system" + IngressSecurePort = "9443" ) func GenerateNamespaceFromControlPlaneName(name string) string {