github.com/verrazzano/verrazzano@v1.7.1/cluster-operator/controllers/capi/capi_cluster_controller.go (about)

     1  // Copyright (c) 2023, Oracle and/or its affiliates.
     2  // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
     3  
     4  package capi
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  
    10  	clustersv1alpha1 "github.com/verrazzano/verrazzano/cluster-operator/apis/clusters/v1alpha1"
    11  	internalcapi "github.com/verrazzano/verrazzano/cluster-operator/internal/capi"
    12  	"github.com/verrazzano/verrazzano/pkg/constants"
    13  	vzstring "github.com/verrazzano/verrazzano/pkg/string"
    14  	"github.com/verrazzano/verrazzano/platform-operator/controllers/verrazzano/component/common"
    15  	"go.uber.org/zap"
    16  	v1 "k8s.io/api/core/v1"
    17  	"k8s.io/apimachinery/pkg/api/errors"
    18  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    19  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    20  	"k8s.io/apimachinery/pkg/runtime"
    21  	"k8s.io/apimachinery/pkg/types"
    22  	ctrl "sigs.k8s.io/controller-runtime"
    23  	"sigs.k8s.io/controller-runtime/pkg/client"
    24  	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
    25  )
    26  
    27  const (
    28  	finalizerName = "verrazzano.io/capi-cluster"
    29  )
    30  
    31  type CAPIClusterReconciler struct {
    32  	client.Client
    33  	Scheme             *runtime.Scheme
    34  	Log                *zap.SugaredLogger
    35  	RancherIngressHost string
    36  	RancherEnabled     bool
    37  }
    38  
    39  func CAPIClusterClientObject() client.Object {
    40  	obj := &unstructured.Unstructured{}
    41  	obj.SetGroupVersionKind(internalcapi.GVKCAPICluster)
    42  	return obj
    43  }
    44  
    45  // SetupWithManager creates a new controller and adds it to the manager
    46  func (r *CAPIClusterReconciler) SetupWithManager(mgr ctrl.Manager) error {
    47  	return ctrl.NewControllerManagedBy(mgr).
    48  		For(CAPIClusterClientObject()).
    49  		Complete(r)
    50  }
    51  
    52  // Reconcile is the main controller reconcile function
    53  func (r *CAPIClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    54  	r.Log.Infof("Reconciling CAPI cluster: %v", req.NamespacedName)
    55  
    56  	cluster := &unstructured.Unstructured{}
    57  	cluster.SetGroupVersionKind(internalcapi.GVKCAPICluster)
    58  	err := r.Get(context.TODO(), req.NamespacedName, cluster)
    59  	if err != nil && !errors.IsNotFound(err) {
    60  		return ctrl.Result{}, err
    61  	}
    62  
    63  	if errors.IsNotFound(err) {
    64  		r.Log.Debugf("CAPI cluster %v not found, nothing to do", req.NamespacedName)
    65  		return ctrl.Result{}, nil
    66  	}
    67  
    68  	// if the deletion timestamp is set, unregister the corresponding Rancher cluster
    69  	if !cluster.GetDeletionTimestamp().IsZero() {
    70  		if vzstring.SliceContainsString(cluster.GetFinalizers(), finalizerName) {
    71  			vmcName := r.getVMCName(cluster)
    72  			// ensure a base VMC
    73  			vmc := &clustersv1alpha1.VerrazzanoManagedCluster{
    74  				ObjectMeta: metav1.ObjectMeta{
    75  					Name:      vmcName,
    76  					Namespace: constants.VerrazzanoMultiClusterNamespace,
    77  				},
    78  			}
    79  			if err = r.Delete(ctx, vmc); err != nil {
    80  				if !errors.IsNotFound(err) {
    81  					return ctrl.Result{}, err
    82  				}
    83  			}
    84  		}
    85  
    86  		if err := r.removeFinalizer(cluster); err != nil {
    87  			return ctrl.Result{}, err
    88  		}
    89  
    90  		return ctrl.Result{}, nil
    91  	}
    92  
    93  	// obtain and persist the API endpoint IP address for the admin cluster
    94  	err = r.createAdminAccessConfigMap(ctx)
    95  	if err != nil {
    96  		return ctrl.Result{}, err
    97  	}
    98  
    99  	vmcName := r.getVMCName(cluster)
   100  	// ensure a base VMC
   101  	vmc := &clustersv1alpha1.VerrazzanoManagedCluster{
   102  		ObjectMeta: metav1.ObjectMeta{
   103  			Name:      vmcName,
   104  			Namespace: constants.VerrazzanoMultiClusterNamespace,
   105  		},
   106  	}
   107  	if _, err = r.createOrUpdateWorkloadClusterVMC(ctx, cluster, vmc, func() error {
   108  		vmc.Spec = clustersv1alpha1.VerrazzanoManagedClusterSpec{
   109  			Description: fmt.Sprintf("%s VerrazzanoManagedCluster Resource", cluster.GetName()),
   110  		}
   111  		return nil
   112  	}); err != nil {
   113  		return ctrl.Result{}, err
   114  	}
   115  	if err = r.setVMCClusterRef(ctx, cluster, vmc); err != nil {
   116  		return ctrl.Result{}, err
   117  	}
   118  
   119  	// add a finalizer to the CAPI cluster if it doesn't already exist
   120  	if err = r.ensureFinalizer(cluster); err != nil {
   121  		return ctrl.Result{}, err
   122  	}
   123  
   124  	return ctrl.Result{}, nil
   125  }
   126  
   127  func (r *CAPIClusterReconciler) setVMCClusterRef(ctx context.Context, cluster *unstructured.Unstructured, vmc *clustersv1alpha1.VerrazzanoManagedCluster) error {
   128  	vmc.Status.ClusterRef = &clustersv1alpha1.ClusterReference{
   129  		APIVersion: cluster.GetAPIVersion(),
   130  		Kind:       cluster.GetKind(),
   131  		Name:       cluster.GetName(),
   132  		Namespace:  cluster.GetNamespace(),
   133  	}
   134  	return r.Client.Status().Update(ctx, vmc, &client.UpdateOptions{})
   135  }
   136  
   137  // createOrUpdateWorkloadClusterVMC creates or updates the VMC resource for the workload cluster
   138  func (r *CAPIClusterReconciler) createOrUpdateWorkloadClusterVMC(ctx context.Context, cluster *unstructured.Unstructured, vmc *clustersv1alpha1.VerrazzanoManagedCluster, f controllerutil.MutateFn) (*clustersv1alpha1.VerrazzanoManagedCluster, error) {
   139  	if _, err := ctrl.CreateOrUpdate(ctx, r.Client, vmc, f); err != nil {
   140  		r.Log.Errorf("Failed to create or update the VMC for cluster %s: %v", cluster.GetName(), err)
   141  		return nil, err
   142  	}
   143  
   144  	return vmc, nil
   145  }
   146  
   147  // createAdminAccessConfigMap creates the config map required for the creation of the admin accessing kubeconfig
   148  func (r *CAPIClusterReconciler) createAdminAccessConfigMap(ctx context.Context) error {
   149  	ep := &v1.Endpoints{}
   150  	if err := r.Get(ctx, types.NamespacedName{Name: "kubernetes", Namespace: "default"}, ep); err != nil {
   151  		return err
   152  	}
   153  	apiServerIP := ep.Subsets[0].Addresses[0].IP
   154  
   155  	// create the admin server IP config map
   156  	cm := &v1.ConfigMap{
   157  		ObjectMeta: metav1.ObjectMeta{
   158  			Name:      "verrazzano-admin-cluster",
   159  			Namespace: constants.VerrazzanoMultiClusterNamespace,
   160  		},
   161  	}
   162  	if _, err := ctrl.CreateOrUpdate(ctx, r.Client, cm, func() error {
   163  		if cm.Data == nil {
   164  			cm.Data = make(map[string]string)
   165  		}
   166  		cm.Data["server"] = fmt.Sprintf("https://%s:6443", apiServerIP)
   167  
   168  		return nil
   169  	}); err != nil {
   170  		r.Log.Errorf("Failed to create the Verrazzano admin cluster config map: %v", err)
   171  		return err
   172  	}
   173  	return nil
   174  }
   175  
   176  // ensureFinalizer adds a finalizer to the CAPI cluster if the finalizer is not already present
   177  func (r *CAPIClusterReconciler) ensureFinalizer(cluster *unstructured.Unstructured) error {
   178  	if finalizers, added := vzstring.SliceAddString(cluster.GetFinalizers(), finalizerName); added {
   179  		cluster.SetFinalizers(finalizers)
   180  		if err := r.Update(context.TODO(), cluster); err != nil {
   181  			return err
   182  		}
   183  	}
   184  	return nil
   185  }
   186  
   187  // removeFinalizer removes the finalizer from the Rancher cluster resource
   188  func (r *CAPIClusterReconciler) removeFinalizer(cluster *unstructured.Unstructured) error {
   189  	finalizers := vzstring.RemoveStringFromSlice(cluster.GetFinalizers(), finalizerName)
   190  	cluster.SetFinalizers(finalizers)
   191  
   192  	if err := r.Update(context.TODO(), cluster); err != nil {
   193  		return err
   194  	}
   195  	return nil
   196  }
   197  
   198  func (r *CAPIClusterReconciler) getVMCName(cluster *unstructured.Unstructured) string {
   199  	// check for existence of a Rancher cluster management resource
   200  	rancherMgmtCluster := &unstructured.Unstructured{}
   201  	rancherMgmtCluster.SetGroupVersionKind(common.GetRancherMgmtAPIGVKForKind("Cluster"))
   202  	err := r.Get(context.TODO(), types.NamespacedName{Name: cluster.GetName(), Namespace: cluster.GetNamespace()}, rancherMgmtCluster)
   203  	if err != nil {
   204  		return cluster.GetName()
   205  	}
   206  	// return the display Name
   207  	return rancherMgmtCluster.UnstructuredContent()["spec"].(map[string]interface{})["displayName"].(string)
   208  }