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 }