From 86bae23ac6dd924e07fdc8b30cf51b5b8f06f5c9 Mon Sep 17 00:00:00 2001 From: "itamar.marom" Date: Tue, 6 Feb 2024 16:01:35 +0200 Subject: [PATCH] feat(validation): migrate validation to ValidatingAdmissionPolicy --- config/default/kustomization.yaml | 1 + config/validation/kustomization.yaml | 4 +++ config/validation/validation.yaml | 43 ++++++++++++++++++++++++++++ controllers/druid/handler.go | 39 ------------------------- 4 files changed, 48 insertions(+), 39 deletions(-) create mode 100644 config/validation/kustomization.yaml create mode 100644 config/validation/validation.yaml diff --git a/config/default/kustomization.yaml b/config/default/kustomization.yaml index dac66ea2..2e4b9653 100644 --- a/config/default/kustomization.yaml +++ b/config/default/kustomization.yaml @@ -16,6 +16,7 @@ bases: - ../crd - ../rbac - ../manager +- ../validation # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in # crd/kustomization.yaml #- ../webhook diff --git a/config/validation/kustomization.yaml b/config/validation/kustomization.yaml new file mode 100644 index 00000000..d93ec6ad --- /dev/null +++ b/config/validation/kustomization.yaml @@ -0,0 +1,4 @@ +resources: +- validation.yaml +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization \ No newline at end of file diff --git a/config/validation/validation.yaml b/config/validation/validation.yaml new file mode 100644 index 00000000..591f8ce4 --- /dev/null +++ b/config/validation/validation.yaml @@ -0,0 +1,43 @@ +apiVersion: admissionregistration.k8s.io/v1beta1 +kind: ValidatingAdmissionPolicy +metadata: + name: "druid.apache.org" +spec: + failurePolicy: Fail + matchConstraints: + resourceRules: + - apiGroups: ["druid.apache.org"] + apiVersions: ["v1alpha1"] + operations: ["CREATE", "UPDATE"] + resources: ["druids"] + validations: + - expression: | + object.spec.nodes.all(node, node.image != '' || object.spec.image != '') + messageExpression: "Object must have image specified either on object level or node level." + reason: "Invalid" + message: "Image is missing from spec. should be provided on either object level or node level." + - expression: | + object.spec.nodes.all(node, node.key.matches('[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')) + messageExpression: "All node groups must stand with kubernetes standard naming constraints." + reason: "Invalid" + message: "Node key does not matching kubernetes standard naming constraints." + - expression: | + object.spec.nodes.all(node, node.kind == 'StatefulSet' && node.volumeClaimTemplates.all(vct, vct.storageClassName == nil)) + messageExpression: "All node groups' volume claim templates must have storage class specified." + reason: "Forbidden" + message: "Node group has volume claim template without storage class which is not allowed." + - expression: | + object.spec.nodes.all(node, node.additionalContainers.all(nac, object.spec.additionalContainers.all(oac, nac.containerName != oac.containerName))) && + object.spec.nodes.all(node, node.additionalContainers.all(nac, node.additionalContainers.all(nac2, nac2.containerName != nac.containerName))) && + object.spec.additionalContainers.all(oac, object.spec.additionalContainers.all(oac2, oac2.containerName != oac.containerName)) + messageExpression: "All additional containers on all levels must have a unique container name." + reason: "Invalid" + message: "Node group has at least two additional containers with the same name." +--- +apiVersion: admissionregistration.k8s.io/v1beta1 +kind: ValidatingAdmissionPolicyBinding +metadata: + name: "druid.apache.org" +spec: + policyName: "druid.apache.org" + validationActions: [Deny, Audit] diff --git a/controllers/druid/handler.go b/controllers/druid/handler.go index 546e7747..bcc04a92 100644 --- a/controllers/druid/handler.go +++ b/controllers/druid/handler.go @@ -7,7 +7,6 @@ import ( "encoding/json" "errors" "fmt" - "regexp" "sort" "strconv" "time" @@ -40,12 +39,6 @@ var logger = logf.Log.WithName("druid_operator_handler") func deployDruidCluster(ctx context.Context, sdk client.Client, m *v1alpha1.Druid, emitEvents EventEmitter) error { - if err := verifyDruidSpec(m); err != nil { - e := fmt.Errorf("invalid DruidSpec[%s:%s] due to [%s]", m.Kind, m.Name, err.Error()) - emitEvents.EmitEventGeneric(m, "DruidOperatorInvalidSpec", "", e) - return nil - } - allNodeSpecs, err := getAllNodeSpecsInDruidPrescribedOrder(m) if err != nil { e := fmt.Errorf("invalid DruidSpec[%s:%s] due to [%s]", m.Kind, m.Name, err.Error()) @@ -1359,38 +1352,6 @@ func sendEvent(ctx context.Context, sdk client.Client, drd *v1alpha1.Druid, even } } -func verifyDruidSpec(drd *v1alpha1.Druid) error { - keyValidationRegex, err := regexp.Compile("[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*") - if err != nil { - return err - } - - if err = validateAdditionalContainersSpec(drd); err != nil { - return err - } - - if err = validateVolumeClaimTemplateSpec(drd); err != nil { - return err - } - - errorMsg := "" - for key, node := range drd.Spec.Nodes { - if drd.Spec.Image == "" && node.Image == "" { - errorMsg = fmt.Sprintf("%sImage missing from Druid Cluster Spec\n", errorMsg) - } - - if !keyValidationRegex.MatchString(key) { - errorMsg = fmt.Sprintf("%sNode[%s] Key must match k8s resource name regex '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*'", errorMsg, key) - } - } - - if errorMsg == "" { - return nil - } else { - return fmt.Errorf(errorMsg) - } -} - type keyAndNodeSpec struct { key string spec v1alpha1.DruidNodeSpec