From 31dcff1b1c638d519797fb771c12277759db727d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Charles-Edouard=20Br=C3=A9t=C3=A9ch=C3=A9?=
 <charles.edouard@nirmata.com>
Date: Wed, 4 Sep 2024 21:43:12 +0200
Subject: [PATCH] feat: add global context entry openapi validation (#10998)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
---
 .../v2alpha1/global_context_entry_types.go        | 15 ++-------------
 .../kyverno.io_globalcontextentries.yaml          |  5 +++++
 .../kyverno/kyverno.io_globalcontextentries.yaml  |  5 +++++
 config/install-latest-testing.yaml                |  5 +++++
 .../globalcontext/validate-crd/chainsaw-test.yaml |  4 ++--
 5 files changed, 19 insertions(+), 15 deletions(-)

diff --git a/api/kyverno/v2alpha1/global_context_entry_types.go b/api/kyverno/v2alpha1/global_context_entry_types.go
index 5223658ca9..fe8df1f5b7 100644
--- a/api/kyverno/v2alpha1/global_context_entry_types.go
+++ b/api/kyverno/v2alpha1/global_context_entry_types.go
@@ -47,23 +47,15 @@ type GlobalContextEntry struct {
 	Status GlobalContextEntryStatus `json:"status,omitempty"`
 }
 
-// GetStatus returns the globalcontextentry status
-func (p *GlobalContextEntry) GetStatus() *GlobalContextEntryStatus {
-	return &p.Status
-}
-
 // Validate implements programmatic validation
 func (c *GlobalContextEntry) Validate() (errs field.ErrorList) {
 	errs = append(errs, c.Spec.Validate(field.NewPath("spec"))...)
 	return errs
 }
 
-// IsNamespaced indicates if the policy is namespace scoped
-func (c *GlobalContextEntry) IsNamespaced() bool {
-	return false
-}
-
 // GlobalContextEntrySpec stores policy exception spec
+// +kubebuilder:oneOf:={required:{kubernetesResource}}
+// +kubebuilder:oneOf:={required:{apiCall}}
 type GlobalContextEntrySpec struct {
 	// Stores a list of Kubernetes resources which will be cached.
 	// Mutually exclusive with APICall.
@@ -170,14 +162,11 @@ func (e *ExternalAPICall) Validate(path *field.Path) (errs field.ErrorList) {
 	if e.RefreshInterval.Duration == 0*time.Second {
 		errs = append(errs, field.Required(path.Child("refreshIntervalSeconds"), "A Resource entry requires a refresh interval greater than 0 seconds"))
 	}
-
 	if (e.Service == nil && e.URLPath == "") || (e.Service != nil && e.URLPath != "") {
 		errs = append(errs, field.Forbidden(path.Child("service"), "An External API call should either have Service or URLPath"))
 	}
-
 	if e.Data != nil && e.Method != "POST" {
 		errs = append(errs, field.Forbidden(path.Child("method"), "An External API call with data should have method as POST"))
 	}
-
 	return errs
 }
diff --git a/charts/kyverno/charts/crds/templates/kyverno.io/kyverno.io_globalcontextentries.yaml b/charts/kyverno/charts/crds/templates/kyverno.io/kyverno.io_globalcontextentries.yaml
index 11e31feb01..82bea71d98 100644
--- a/charts/kyverno/charts/crds/templates/kyverno.io/kyverno.io_globalcontextentries.yaml
+++ b/charts/kyverno/charts/crds/templates/kyverno.io/kyverno.io_globalcontextentries.yaml
@@ -61,6 +61,11 @@ spec:
             type: object
           spec:
             description: Spec declares policy exception behaviors.
+            oneOf:
+            - required:
+              - kubernetesResource
+            - required:
+              - apiCall
             properties:
               apiCall:
                 description: |-
diff --git a/config/crds/kyverno/kyverno.io_globalcontextentries.yaml b/config/crds/kyverno/kyverno.io_globalcontextentries.yaml
index 583a156df8..e5101b9c18 100644
--- a/config/crds/kyverno/kyverno.io_globalcontextentries.yaml
+++ b/config/crds/kyverno/kyverno.io_globalcontextentries.yaml
@@ -55,6 +55,11 @@ spec:
             type: object
           spec:
             description: Spec declares policy exception behaviors.
+            oneOf:
+            - required:
+              - kubernetesResource
+            - required:
+              - apiCall
             properties:
               apiCall:
                 description: |-
diff --git a/config/install-latest-testing.yaml b/config/install-latest-testing.yaml
index c8d13df83e..eba0d017c2 100644
--- a/config/install-latest-testing.yaml
+++ b/config/install-latest-testing.yaml
@@ -24802,6 +24802,11 @@ spec:
             type: object
           spec:
             description: Spec declares policy exception behaviors.
+            oneOf:
+            - required:
+              - kubernetesResource
+            - required:
+              - apiCall
             properties:
               apiCall:
                 description: |-
diff --git a/test/conformance/chainsaw/globalcontext/validate-crd/chainsaw-test.yaml b/test/conformance/chainsaw/globalcontext/validate-crd/chainsaw-test.yaml
index 6b61af90c0..4bd9ae56cb 100644
--- a/test/conformance/chainsaw/globalcontext/validate-crd/chainsaw-test.yaml
+++ b/test/conformance/chainsaw/globalcontext/validate-crd/chainsaw-test.yaml
@@ -19,7 +19,7 @@ spec:
             expect:
               - check:
                   ($error): |-
-                    admission webhook "kyverno-svc.kyverno.svc" denied the request: spec.kubernetesResource: Forbidden: A global context entry should either have KubernetesResource or APICall
+                    GlobalContextEntry.kyverno.io "ingress-2" is invalid: <nil>: Invalid value: "": "spec" must validate one and only one schema (oneOf). Found 2 valid alternatives
     - name: step-03
       try:
         - apply:
@@ -27,4 +27,4 @@ spec:
             expect:
               - check:
                   ($error): |-
-                    admission webhook "kyverno-svc.kyverno.svc" denied the request: spec.kubernetesResource: Forbidden: A global context entry should either have KubernetesResource or APICall
+                    GlobalContextEntry.kyverno.io "ingress-3" is invalid: [<nil>: Invalid value: "": "spec" must validate one and only one schema (oneOf). Found none valid, spec.kubernetesResource: Required value]