github.com/verrazzano/verrazzano@v1.7.1/cluster-operator/apis/clusters/v1alpha1/verrazzanomanagedcluster_webhook.go (about)

     1  // Copyright (c) 2021, 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 v1alpha1
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"net/url"
    10  
    11  	"github.com/verrazzano/verrazzano/pkg/vzcr"
    12  	"github.com/verrazzano/verrazzano/platform-operator/apis/verrazzano/v1beta1"
    13  	"github.com/verrazzano/verrazzano/platform-operator/constants"
    14  	"go.uber.org/zap"
    15  	corev1 "k8s.io/api/core/v1"
    16  	"k8s.io/apimachinery/pkg/api/errors"
    17  	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    18  	"k8s.io/apimachinery/pkg/runtime"
    19  	"k8s.io/apimachinery/pkg/types"
    20  	ctrl "sigs.k8s.io/controller-runtime"
    21  	"sigs.k8s.io/controller-runtime/pkg/client"
    22  	"sigs.k8s.io/controller-runtime/pkg/webhook"
    23  )
    24  
    25  var getClientFunc = getClient
    26  var _ webhook.Validator = &VerrazzanoManagedCluster{}
    27  
    28  // SetupWebhookWithManager is used to let the controller manager know about the webhook
    29  func (v *VerrazzanoManagedCluster) SetupWebhookWithManager(mgr ctrl.Manager) error {
    30  	return ctrl.NewWebhookManagedBy(mgr).
    31  		For(v).
    32  		Complete()
    33  }
    34  
    35  // ValidateCreate implements webhook.Validator so a webhook will be registered for the type
    36  func (v *VerrazzanoManagedCluster) ValidateCreate() error {
    37  	log := zap.S().With("source", "webhook", "operation", "create", "resource", fmt.Sprintf("%s:%s", v.Namespace, v.Name))
    38  	log.Debug("Validate create")
    39  
    40  	if v.ObjectMeta.Namespace != constants.VerrazzanoMultiClusterNamespace {
    41  		return fmt.Errorf("Namespace for the resource must be %s", constants.VerrazzanoMultiClusterNamespace)
    42  	}
    43  	client, err := getClientFunc()
    44  	if err != nil {
    45  		return err
    46  	}
    47  	vz, err := v.validateVerrazzanoInstalled(client)
    48  	if err != nil {
    49  		return err
    50  	}
    51  
    52  	// The secret and configmap are required fields _only_ if Rancher is disabled
    53  	if !vzcr.IsRancherEnabled(vz) {
    54  		err = v.validateSecret(client)
    55  		if err != nil {
    56  			return err
    57  		}
    58  
    59  		err = v.validateConfigMap(client)
    60  		if err != nil {
    61  			return err
    62  		}
    63  	}
    64  	return nil
    65  }
    66  
    67  // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
    68  func (v *VerrazzanoManagedCluster) ValidateUpdate(old runtime.Object) error {
    69  	log := zap.S().With("source", "webhook", "operation", "update", "resource", fmt.Sprintf("%s:%s", v.Namespace, v.Name))
    70  	log.Debug("Validate update")
    71  
    72  	oldResource := old.(*VerrazzanoManagedCluster)
    73  	log.Debugf("oldResource: %v", oldResource)
    74  	log.Debugf("v: %v", v)
    75  	return nil
    76  }
    77  
    78  // ValidateDelete implements webhook.Validator so a webhook will be registered for the type
    79  func (v *VerrazzanoManagedCluster) ValidateDelete() error {
    80  	// Webhook is not configured for deletes so function will not be called.
    81  	return nil
    82  }
    83  
    84  // getClient returns a controller runtime client for the Verrazzano resource
    85  func getClient() (client.Client, error) {
    86  	config, err := ctrl.GetConfig()
    87  	if err != nil {
    88  		return nil, err
    89  	}
    90  	return client.New(config, client.Options{Scheme: newScheme()})
    91  }
    92  
    93  // newScheme creates a new scheme that includes this package's object for use by client
    94  func newScheme() *runtime.Scheme {
    95  	scheme := runtime.NewScheme()
    96  	AddToScheme(scheme)
    97  	corev1.AddToScheme(scheme)
    98  	scheme.AddKnownTypes(v1beta1.SchemeGroupVersion, &v1beta1.Verrazzano{}, &v1beta1.VerrazzanoList{})
    99  	meta_v1.AddToGroupVersion(scheme, v1beta1.SchemeGroupVersion)
   100  
   101  	return scheme
   102  }
   103  
   104  // validateSecret enforces that the CA secret name was specified and that the secret exists
   105  func (v VerrazzanoManagedCluster) validateSecret(client client.Client) error {
   106  	log := zap.S().With("source", "webhook", "operation", "update", "resource", fmt.Sprintf("%s:%s", v.Namespace, v.Name))
   107  	log.Debug("Validate secret")
   108  
   109  	if len(v.Spec.CASecret) == 0 {
   110  		log.Debugf("No CA secret in namespace %s defined, using well-known CA", constants.VerrazzanoMultiClusterNamespace)
   111  		return nil
   112  	}
   113  	secret := corev1.Secret{}
   114  	nsn := types.NamespacedName{
   115  		Namespace: constants.VerrazzanoMultiClusterNamespace,
   116  		Name:      v.Spec.CASecret,
   117  	}
   118  	err := client.Get(context.TODO(), nsn, &secret)
   119  	if err == nil {
   120  		return nil
   121  	}
   122  	if errors.IsNotFound(err) {
   123  		return fmt.Errorf("The CA secret %s does not exist in namespace %s", v.Spec.CASecret, constants.VerrazzanoMultiClusterNamespace)
   124  	}
   125  	return fmt.Errorf("Error getting the CA secret %s namespace %s. Error: %s", v.Spec.CASecret, constants.VerrazzanoMultiClusterNamespace, err.Error())
   126  }
   127  
   128  // validateConfigMap enforces that the verrazzano-admin-cluster secret name exists and server key has non-empty value
   129  func (v VerrazzanoManagedCluster) validateConfigMap(client client.Client) error {
   130  	cm := corev1.ConfigMap{}
   131  	nsn := types.NamespacedName{
   132  		Namespace: constants.VerrazzanoMultiClusterNamespace,
   133  		Name:      constants.AdminClusterConfigMapName,
   134  	}
   135  	err := client.Get(context.TODO(), nsn, &cm)
   136  	if err != nil {
   137  		if errors.IsNotFound(err) {
   138  			return fmt.Errorf("The ConfigMap %s does not exist in namespace %s", constants.AdminClusterConfigMapName, constants.VerrazzanoMultiClusterNamespace)
   139  		}
   140  		return fmt.Errorf("Error getting the ConfigMap %s namespace %s. Error: %s", constants.AdminClusterConfigMapName, constants.VerrazzanoMultiClusterNamespace, err.Error())
   141  	}
   142  	_, err = url.ParseRequestURI(cm.Data[constants.ServerDataKey])
   143  	if err != nil {
   144  		return fmt.Errorf("Data with key %q contains invalid url %q in the ConfigMap %s namespace %s", constants.ServerDataKey, cm.Data[constants.ServerDataKey], constants.AdminClusterConfigMapName, constants.VerrazzanoMultiClusterNamespace)
   145  	}
   146  	return nil
   147  }
   148  
   149  // validateVerrazzanoInstalled enforces that a Verrazzano installation successfully completed
   150  func (v VerrazzanoManagedCluster) validateVerrazzanoInstalled(localClient client.Client) (*v1beta1.Verrazzano, error) {
   151  	// Get the Verrazzano resource
   152  	verrazzano := v1beta1.VerrazzanoList{}
   153  	err := localClient.List(context.TODO(), &verrazzano, &client.ListOptions{})
   154  	if err != nil {
   155  		return nil, fmt.Errorf("Verrazzano must be installed: %v", err)
   156  	}
   157  
   158  	// Verify the state is install complete
   159  	if len(verrazzano.Items) > 0 {
   160  		for _, cond := range verrazzano.Items[0].Status.Conditions {
   161  			if cond.Type == v1beta1.CondInstallComplete {
   162  				return &verrazzano.Items[0], nil
   163  			}
   164  		}
   165  	}
   166  
   167  	return nil, fmt.Errorf("the Verrazzano install must successfully complete (run the command %q to view the install status)", "kubectl get verrazzano -A")
   168  }