github.com/verrazzano/verrazzano@v1.7.0/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  	clustersv1alpha1 "github.com/verrazzano/verrazzano/cluster-operator/apis/clusters/v1alpha1"
    10  	"github.com/verrazzano/verrazzano/cluster-operator/internal/capi"
    11  	"github.com/verrazzano/verrazzano/pkg/constants"
    12  	vzstring "github.com/verrazzano/verrazzano/pkg/string"
    13  	"github.com/verrazzano/verrazzano/platform-operator/controllers/verrazzano/component/common"
    14  	"go.uber.org/zap"
    15  	appsv1 "k8s.io/api/apps/v1"
    16  	v1 "k8s.io/api/core/v1"
    17  	netv1 "k8s.io/api/networking/v1"
    18  	rbacv1 "k8s.io/api/rbac/v1"
    19  	"k8s.io/apimachinery/pkg/api/errors"
    20  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    21  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    22  	"k8s.io/apimachinery/pkg/runtime"
    23  	"k8s.io/apimachinery/pkg/types"
    24  	"k8s.io/client-go/rest"
    25  	"k8s.io/client-go/tools/clientcmd"
    26  	ctrl "sigs.k8s.io/controller-runtime"
    27  	"sigs.k8s.io/controller-runtime/pkg/client"
    28  	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
    29  )
    30  
    31  const (
    32  	finalizerName                = "verrazzano.io/capi-cluster"
    33  	clusterProvisionerLabel      = "cluster.verrazzano.io/provisioner"
    34  	clusterStatusSuffix          = "-cluster-status"
    35  	clusterIDKey                 = "clusterId"
    36  	clusterRegistrationStatusKey = "clusterRegistration"
    37  	registrationRetrieved        = "retrieved"
    38  	registrationInitiated        = "initiated"
    39  )
    40  
    41  type CAPIClusterReconciler struct {
    42  	client.Client
    43  	Scheme              *runtime.Scheme
    44  	Log                 *zap.SugaredLogger
    45  	RancherRegistrar    *RancherRegistration
    46  	RancherIngressHost  string
    47  	RancherEnabled      bool
    48  	VerrazzanoRegistrar *VerrazzanoRegistration
    49  }
    50  
    51  func CAPIClusterClientObject() client.Object {
    52  	obj := &unstructured.Unstructured{}
    53  	obj.SetGroupVersionKind(capi.GVKCAPICluster)
    54  	return obj
    55  }
    56  
    57  // SetupWithManager creates a new controller and adds it to the manager
    58  func (r *CAPIClusterReconciler) SetupWithManager(mgr ctrl.Manager) error {
    59  	return ctrl.NewControllerManagedBy(mgr).
    60  		For(CAPIClusterClientObject()).
    61  		Complete(r)
    62  }
    63  
    64  // Reconcile is the main controller reconcile function
    65  func (r *CAPIClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    66  	r.Log.Infof("Reconciling CAPI cluster: %v", req.NamespacedName)
    67  
    68  	cluster := &unstructured.Unstructured{}
    69  	cluster.SetGroupVersionKind(capi.GVKCAPICluster)
    70  	err := r.Get(context.TODO(), req.NamespacedName, cluster)
    71  	if err != nil && !errors.IsNotFound(err) {
    72  		return ctrl.Result{}, err
    73  	}
    74  
    75  	if errors.IsNotFound(err) {
    76  		r.Log.Debugf("CAPI cluster %v not found, nothing to do", req.NamespacedName)
    77  		return ctrl.Result{}, nil
    78  	}
    79  
    80  	// if the deletion timestamp is set, unregister the corresponding Rancher cluster
    81  	if !cluster.GetDeletionTimestamp().IsZero() {
    82  		if vzstring.SliceContainsString(cluster.GetFinalizers(), finalizerName) {
    83  			err = r.unregisterCluster(ctx, cluster)
    84  			if err != nil {
    85  				r.Log.Warnf("Unable to unregister cluster %s: %v.  Cluster deletion will proceed.", cluster.GetName(), err)
    86  			}
    87  		}
    88  
    89  		if err := r.removeFinalizer(cluster); err != nil {
    90  			return ctrl.Result{}, err
    91  		}
    92  
    93  		// delete the cluster id secret
    94  		clusterRegistrationStatusSecret := &v1.Secret{
    95  			ObjectMeta: metav1.ObjectMeta{
    96  				Namespace: constants.VerrazzanoCAPINamespace,
    97  				Name:      cluster.GetName() + clusterStatusSuffix,
    98  			},
    99  		}
   100  		err = r.Delete(ctx, clusterRegistrationStatusSecret)
   101  		if err != nil {
   102  			if !errors.IsNotFound(err) {
   103  				return ctrl.Result{}, err
   104  			}
   105  		}
   106  
   107  		return ctrl.Result{}, nil
   108  	}
   109  
   110  	// obtain and persist the API endpoint IP address for the admin cluster
   111  	err = r.createAdminAccessConfigMap(ctx)
   112  	if err != nil {
   113  		return ctrl.Result{}, err
   114  	}
   115  
   116  	vmcName := r.getVMCName(cluster)
   117  	// ensure a base VMC
   118  	vmc := &clustersv1alpha1.VerrazzanoManagedCluster{
   119  		ObjectMeta: metav1.ObjectMeta{
   120  			Name:      vmcName,
   121  			Namespace: constants.VerrazzanoMultiClusterNamespace,
   122  		},
   123  	}
   124  	if _, err = r.createOrUpdateWorkloadClusterVMC(ctx, cluster, vmc, func() error {
   125  		vmc.Spec = clustersv1alpha1.VerrazzanoManagedClusterSpec{
   126  			Description: fmt.Sprintf("%s VerrazzanoManagedCluster Resource", cluster.GetName()),
   127  		}
   128  		return nil
   129  	}); err != nil {
   130  		return ctrl.Result{}, err
   131  	}
   132  
   133  	if r.RancherEnabled {
   134  		// Is Rancher Deployment ready
   135  		r.Log.Debugf("Attempting cluster regisration with Rancher")
   136  		result, err := r.RancherRegistrar.doReconcile(ctx, cluster)
   137  		if err != nil {
   138  			return result, err
   139  		}
   140  	}
   141  
   142  	// add a finalizer to the CAPI cluster if it doesn't already exist
   143  	if err = r.ensureFinalizer(cluster); err != nil {
   144  		return ctrl.Result{}, err
   145  	}
   146  
   147  	r.Log.Debugf("Attempting cluster regisration with Verrazzano")
   148  	return verrazzanoReconcileFn(ctx, cluster, r)
   149  }
   150  
   151  // createOrUpdateWorkloadClusterVMC creates or updates the VMC resource for the workload cluster
   152  func (r *CAPIClusterReconciler) createOrUpdateWorkloadClusterVMC(ctx context.Context, cluster *unstructured.Unstructured, vmc *clustersv1alpha1.VerrazzanoManagedCluster, f controllerutil.MutateFn) (*clustersv1alpha1.VerrazzanoManagedCluster, error) {
   153  	if _, err := ctrl.CreateOrUpdate(ctx, r.Client, vmc, f); err != nil {
   154  		r.Log.Errorf("Failed to create or update the VMC for cluster %s: %v", cluster.GetName(), err)
   155  		return nil, err
   156  	}
   157  
   158  	return vmc, nil
   159  }
   160  
   161  // createAdminAccessConfigMap creates the config map required for the creation of the admin accessing kubeconfig
   162  func (r *CAPIClusterReconciler) createAdminAccessConfigMap(ctx context.Context) error {
   163  	ep := &v1.Endpoints{}
   164  	if err := r.Get(ctx, types.NamespacedName{Name: "kubernetes", Namespace: "default"}, ep); err != nil {
   165  		return err
   166  	}
   167  	apiServerIP := ep.Subsets[0].Addresses[0].IP
   168  
   169  	// create the admin server IP config map
   170  	cm := &v1.ConfigMap{
   171  		ObjectMeta: metav1.ObjectMeta{
   172  			Name:      "verrazzano-admin-cluster",
   173  			Namespace: constants.VerrazzanoMultiClusterNamespace,
   174  		},
   175  	}
   176  	if _, err := ctrl.CreateOrUpdate(ctx, r.Client, cm, func() error {
   177  		if cm.Data == nil {
   178  			cm.Data = make(map[string]string)
   179  		}
   180  		cm.Data["server"] = fmt.Sprintf("https://%s:6443", apiServerIP)
   181  
   182  		return nil
   183  	}); err != nil {
   184  		r.Log.Errorf("Failed to create the Verrazzano admin cluster config map: %v", err)
   185  		return err
   186  	}
   187  	return nil
   188  }
   189  
   190  // unregisterCluster removes the cluster from Rancher and/or Verrazzano.
   191  func (r *CAPIClusterReconciler) unregisterCluster(ctx context.Context, cluster *unstructured.Unstructured) error {
   192  	var err error
   193  	if r.RancherEnabled {
   194  		err = clusterRancherUnregistrationFn(ctx, r.RancherRegistrar, cluster)
   195  		if err != nil {
   196  			return err
   197  		}
   198  	}
   199  	if err = UnregisterVerrazzanoCluster(ctx, r.VerrazzanoRegistrar, cluster); err != nil {
   200  		return err
   201  	}
   202  
   203  	// remove the VMC
   204  	vmc := &clustersv1alpha1.VerrazzanoManagedCluster{
   205  		ObjectMeta: metav1.ObjectMeta{
   206  			Name:      cluster.GetName(),
   207  			Namespace: constants.VerrazzanoMultiClusterNamespace,
   208  		},
   209  	}
   210  	err = r.Delete(ctx, vmc)
   211  	if err != nil {
   212  		if errors.IsNotFound(err) {
   213  			r.Log.Infof("VMC for cluster %s not found - nothing to do", cluster.GetName())
   214  			return nil
   215  		}
   216  		return err
   217  	}
   218  
   219  	return nil
   220  }
   221  
   222  // ensureFinalizer adds a finalizer to the CAPI cluster if the finalizer is not already present
   223  func (r *CAPIClusterReconciler) ensureFinalizer(cluster *unstructured.Unstructured) error {
   224  	if finalizers, added := vzstring.SliceAddString(cluster.GetFinalizers(), finalizerName); added {
   225  		cluster.SetFinalizers(finalizers)
   226  		if err := r.Update(context.TODO(), cluster); err != nil {
   227  			return err
   228  		}
   229  	}
   230  	return nil
   231  }
   232  
   233  // removeFinalizer removes the finalizer from the Rancher cluster resource
   234  func (r *CAPIClusterReconciler) removeFinalizer(cluster *unstructured.Unstructured) error {
   235  	finalizers := vzstring.RemoveStringFromSlice(cluster.GetFinalizers(), finalizerName)
   236  	cluster.SetFinalizers(finalizers)
   237  
   238  	if err := r.Update(context.TODO(), cluster); err != nil {
   239  		return err
   240  	}
   241  	return nil
   242  }
   243  
   244  func (r *CAPIClusterReconciler) getVMCName(cluster *unstructured.Unstructured) string {
   245  	// check for existence of a Rancher cluster management resource
   246  	rancherMgmtCluster := &unstructured.Unstructured{}
   247  	rancherMgmtCluster.SetGroupVersionKind(common.GetRancherMgmtAPIGVKForKind("Cluster"))
   248  	err := r.Get(context.TODO(), types.NamespacedName{Name: cluster.GetName(), Namespace: cluster.GetNamespace()}, rancherMgmtCluster)
   249  	if err != nil {
   250  		return cluster.GetName()
   251  	}
   252  	// return the display Name
   253  	return rancherMgmtCluster.UnstructuredContent()["spec"].(map[string]interface{})["displayName"].(string)
   254  }
   255  
   256  // getClusterClient returns a controller runtime client configured for the workload cluster
   257  func getClusterClient(restConfig *rest.Config) (client.Client, error) {
   258  	scheme := runtime.NewScheme()
   259  	_ = rbacv1.AddToScheme(scheme)
   260  	_ = v1.AddToScheme(scheme)
   261  	_ = netv1.AddToScheme(scheme)
   262  	_ = appsv1.AddToScheme(scheme)
   263  	_ = clustersv1alpha1.AddToScheme(scheme)
   264  
   265  	return client.New(restConfig, client.Options{Scheme: scheme})
   266  }
   267  
   268  // getClusterID returns the cluster ID assigned by Rancher for the given cluster
   269  func getClusterID(ctx context.Context, client client.Client, cluster *unstructured.Unstructured) string {
   270  	clusterID := ""
   271  
   272  	regStatusSecret, err := getClusterRegistrationStatusSecret(ctx, client, cluster)
   273  	if err != nil {
   274  		return clusterID
   275  	}
   276  	clusterID = string(regStatusSecret.Data[clusterIDKey])
   277  
   278  	return clusterID
   279  }
   280  
   281  // getClusterRegistrationStatus returns the Rancher registration status for the cluster
   282  func getClusterRegistrationStatus(ctx context.Context, c client.Client, cluster *unstructured.Unstructured) string {
   283  	clusterStatus := registrationRetrieved
   284  
   285  	regStatusSecret, err := getClusterRegistrationStatusSecret(ctx, c, cluster)
   286  	if err != nil {
   287  		return clusterStatus
   288  	}
   289  	clusterStatus = string(regStatusSecret.Data[clusterRegistrationStatusKey])
   290  
   291  	return clusterStatus
   292  }
   293  
   294  // getClusterRegistrationStatusSecret returns the secret that stores cluster status information
   295  func getClusterRegistrationStatusSecret(ctx context.Context, c client.Client, cluster *unstructured.Unstructured) (*v1.Secret, error) {
   296  	clusterIDSecret := &v1.Secret{}
   297  	secretName := types.NamespacedName{
   298  		Namespace: constants.VerrazzanoCAPINamespace,
   299  		Name:      cluster.GetName() + clusterStatusSuffix,
   300  	}
   301  	err := c.Get(ctx, secretName, clusterIDSecret)
   302  	if err != nil {
   303  		return nil, err
   304  	}
   305  	return clusterIDSecret, err
   306  }
   307  
   308  // persistClusterStatus stores the cluster status in the cluster status secret
   309  func persistClusterStatus(ctx context.Context, client client.Client, cluster *unstructured.Unstructured, log *zap.SugaredLogger, clusterID string, status string) error {
   310  	log.Debugf("Persisting cluster %s cluster id: %s", cluster.GetName(), clusterID)
   311  	clusterRegistrationStatusSecret := &v1.Secret{
   312  		ObjectMeta: metav1.ObjectMeta{
   313  			Name:      cluster.GetName() + clusterStatusSuffix,
   314  			Namespace: constants.VerrazzanoCAPINamespace,
   315  		},
   316  	}
   317  	_, err := ctrl.CreateOrUpdate(ctx, client, clusterRegistrationStatusSecret, func() error {
   318  		// Build the secret data
   319  		if clusterRegistrationStatusSecret.Data == nil {
   320  			clusterRegistrationStatusSecret.Data = make(map[string][]byte)
   321  		}
   322  		clusterRegistrationStatusSecret.Data[clusterIDKey] = []byte(clusterID)
   323  		clusterRegistrationStatusSecret.Data[clusterRegistrationStatusKey] = []byte(status)
   324  
   325  		return nil
   326  	})
   327  	if err != nil {
   328  		log.Errorf("Unable to persist status for cluster %s: %v", cluster.GetName(), err)
   329  		return err
   330  	}
   331  
   332  	return nil
   333  }
   334  
   335  // getWorkloadClusterKubeconfig returns a kubeconfig for accessing the workload cluster
   336  func getWorkloadClusterKubeconfig(client client.Client, cluster *unstructured.Unstructured, log *zap.SugaredLogger) ([]byte, error) {
   337  	// get the cluster kubeconfig
   338  	kubeconfigSecret := &v1.Secret{}
   339  	err := client.Get(context.TODO(), types.NamespacedName{Name: fmt.Sprintf("%s-kubeconfig", cluster.GetName()), Namespace: cluster.GetNamespace()}, kubeconfigSecret)
   340  	if err != nil {
   341  		log.Warn(err, "failed to obtain workload cluster kubeconfig resource. Re-queuing...")
   342  		return nil, err
   343  	}
   344  	kubeconfig, ok := kubeconfigSecret.Data["value"]
   345  	if !ok {
   346  		log.Error(err, "failed to read kubeconfig from resource")
   347  		return nil, fmt.Errorf("Unable to read kubeconfig from retrieved cluster resource")
   348  	}
   349  
   350  	return kubeconfig, nil
   351  }
   352  
   353  func getWorkloadClusterClient(client client.Client, log *zap.SugaredLogger, cluster *unstructured.Unstructured) (client.Client, error) {
   354  	// identify whether the workload cluster is using "untrusted" certs
   355  	kubeconfig, err := getWorkloadClusterKubeconfig(client, cluster, log)
   356  	if err != nil {
   357  		// requeue since we're waiting for cluster
   358  		return nil, err
   359  	}
   360  	// create a workload cluster client
   361  	// create workload cluster client
   362  	restConfig, err := clientcmd.RESTConfigFromKubeConfig(kubeconfig)
   363  	if err != nil {
   364  		log.Warnf("Failed getting rest config from workload kubeconfig")
   365  		return nil, err
   366  	}
   367  	workloadClient, err := getClusterClient(restConfig)
   368  	if err != nil {
   369  		return nil, err
   370  	}
   371  	return workloadClient, nil
   372  }