github.com/verrazzano/verrazzano@v1.7.0/application-operator/controllers/webhooks/verrazzanoproject_webhook.go (about)

     1  // Copyright (c) 2021, 2022, 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 webhooks
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"net/http"
    10  
    11  	"github.com/verrazzano/verrazzano/application-operator/apis/clusters/v1alpha1"
    12  	"github.com/verrazzano/verrazzano/application-operator/metricsexporter"
    13  
    14  	"github.com/verrazzano/verrazzano/application-operator/constants"
    15  	k8sadmission "k8s.io/api/admission/v1"
    16  	"sigs.k8s.io/controller-runtime/pkg/client"
    17  	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
    18  )
    19  
    20  // VerrazzanoProjectValidator is a struct holding objects used during VerrazzanoProject validation.
    21  type VerrazzanoProjectValidator struct {
    22  	client  client.Client
    23  	decoder *admission.Decoder
    24  }
    25  
    26  // InjectClient injects the client.
    27  func (v *VerrazzanoProjectValidator) InjectClient(c client.Client) error {
    28  	v.client = c
    29  	return nil
    30  }
    31  
    32  // InjectDecoder injects the decoder.
    33  func (v *VerrazzanoProjectValidator) InjectDecoder(d *admission.Decoder) error {
    34  	v.decoder = d
    35  	return nil
    36  }
    37  
    38  // Handle performs validation of created or updated VerrazzanoProject resources.
    39  func (v *VerrazzanoProjectValidator) Handle(ctx context.Context, req admission.Request) admission.Response {
    40  	counterMetricObject, errorCounterMetricObject, handleDurationMetricObject, zapLogForMetrics, err := metricsexporter.ExposeControllerMetrics("MultiClusterComponentValidator", metricsexporter.VzProjHandleCounter, metricsexporter.VzProjHandleError, metricsexporter.VzProjHandleDuration)
    41  	if err != nil {
    42  		return admission.Response{}
    43  	}
    44  	handleDurationMetricObject.TimerStart()
    45  	defer handleDurationMetricObject.TimerStop()
    46  
    47  	prj := &v1alpha1.VerrazzanoProject{}
    48  	err = v.decoder.Decode(req, prj)
    49  	if err != nil {
    50  		errorCounterMetricObject.Inc(zapLogForMetrics, err)
    51  		return admission.Errored(http.StatusBadRequest, err)
    52  	}
    53  
    54  	if prj.ObjectMeta.DeletionTimestamp.IsZero() {
    55  		switch req.Operation {
    56  		case k8sadmission.Create, k8sadmission.Update:
    57  			return translateErrorToResponse(validateVerrazzanoProject(v.client, prj))
    58  		}
    59  	}
    60  	counterMetricObject.Inc(zapLogForMetrics, err)
    61  
    62  	return admission.Allowed("")
    63  }
    64  
    65  // validateVerrazzanoProject performs validation checks on the resource
    66  func validateVerrazzanoProject(c client.Client, vp *v1alpha1.VerrazzanoProject) error {
    67  	if vp.ObjectMeta.Namespace != constants.VerrazzanoMultiClusterNamespace {
    68  		return fmt.Errorf("Namespace for the resource must be %q", constants.VerrazzanoMultiClusterNamespace)
    69  	}
    70  
    71  	if len(vp.Spec.Template.Namespaces) == 0 {
    72  		return fmt.Errorf("One or more namespaces must be provided")
    73  	}
    74  
    75  	if err := validateNetworkPolicies(vp); err != nil {
    76  		return err
    77  	}
    78  
    79  	if err := validateNamespaceCanBeUsed(c, vp); err != nil {
    80  		return err
    81  	}
    82  
    83  	if err := validateMultiClusterResource(c, vp); err != nil {
    84  		return err
    85  	}
    86  
    87  	return nil
    88  }
    89  
    90  // validateNetworkPolicies validates the network polices specified in the project
    91  func validateNetworkPolicies(vp *v1alpha1.VerrazzanoProject) error {
    92  	// Build the set of project namespaces for validation
    93  	nsSet := make(map[string]bool)
    94  	for _, ns := range vp.Spec.Template.Namespaces {
    95  		nsSet[ns.Metadata.Name] = true
    96  	}
    97  	// Validate that the policy applies to a namespace in the project
    98  	for _, policyTemplate := range vp.Spec.Template.NetworkPolicies {
    99  		if ok := nsSet[policyTemplate.Metadata.Namespace]; !ok {
   100  			return fmt.Errorf("namespace %s used in NetworkPolicy %s does not exist in project",
   101  				policyTemplate.Metadata.Namespace, policyTemplate.Metadata.Name)
   102  		}
   103  	}
   104  	return nil
   105  }
   106  
   107  func validateNamespaceCanBeUsed(c client.Client, vp *v1alpha1.VerrazzanoProject) error {
   108  	projectsList := &v1alpha1.VerrazzanoProjectList{}
   109  	listOptions := &client.ListOptions{Namespace: constants.VerrazzanoMultiClusterNamespace}
   110  	err := c.List(context.TODO(), projectsList, listOptions)
   111  	if err != nil {
   112  		return fmt.Errorf("failed to get existing Verrazzano projects: %s", err)
   113  	}
   114  
   115  	for _, currentNS := range vp.Spec.Template.Namespaces {
   116  		for _, existingProject := range projectsList.Items {
   117  			if existingProject.Name == vp.Name {
   118  				continue
   119  			}
   120  			for _, existingNS := range existingProject.Spec.Template.Namespaces {
   121  				if existingNS.Metadata.Name == currentNS.Metadata.Name {
   122  					return fmt.Errorf("project namespace %s already being used by project %s. projects cannot share a namespace", existingNS.Metadata.Name, existingProject.Name)
   123  				}
   124  			}
   125  		}
   126  	}
   127  	return nil
   128  }