1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-28 02:18:15 +00:00

test(globalcontext): add e2e tests (#9661)

* fix(globalcontext): validation

Signed-off-by: Khaled Emara <khaled.emara@nirmata.com>

* fix(globalcontext): use existence instead of ready for now

Signed-off-by: Khaled Emara <khaled.emara@nirmata.com>

* chore(globalcontext): improve not ready error message

Signed-off-by: Khaled Emara <khaled.emara@nirmata.com>

* fix(globalcontext): allow any APICall

Signed-off-by: Khaled Emara <khaled.emara@nirmata.com>

* fix(globalcontext): prevent double marshal

Signed-off-by: Khaled Emara <khaled.emara@nirmata.com>

* test(globalcontext): add e2e tests

Signed-off-by: Khaled Emara <khaled.emara@nirmata.com>

* chore(globalcontext): move vaildation to OpenAPI V3

Signed-off-by: Khaled Emara <khaled.emara@nirmata.com>

---------

Signed-off-by: Khaled Emara <khaled.emara@nirmata.com>
Co-authored-by: shuting <shuting@nirmata.com>
This commit is contained in:
Khaled Emara 2024-02-06 19:03:32 +02:00 committed by GitHub
parent ace5b59003
commit 1eda4789d1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
40 changed files with 590 additions and 14 deletions

View file

@ -97,6 +97,7 @@ type ContextEntry struct {
Variable *Variable `json:"variable,omitempty" yaml:"variable,omitempty"`
// GlobalContextEntryReference is a reference to a cached global context entry.
// +kubebuilder:validation:Required
GlobalReference *GlobalContextEntryReference `json:"globalReference,omitempty" yaml:"globalReference,omitempty"`
}

View file

@ -142,9 +142,6 @@ type ExternalAPICall struct {
// Validate implements programmatic validation
func (e *ExternalAPICall) Validate(path *field.Path) (errs field.ErrorList) {
if e.Service.URL == "" {
errs = append(errs, field.Required(path.Child("url"), "An External API Call entry requires a url"))
}
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"))
}

View file

@ -426,7 +426,7 @@ func (c *controller) reconcileMutatingWebhookConfiguration(ctx context.Context,
func (c *controller) isGlobalContextEntryReady(name string, gctxentries []*kyvernov2alpha1.GlobalContextEntry) bool {
for _, gctxentry := range gctxentries {
if gctxentry.Name == name {
return gctxentry.Status.Ready
return true
}
}
return false
@ -466,7 +466,7 @@ func (c *controller) updatePolicyStatuses(ctx context.Context) error {
for _, ctxEntry := range rule.Context {
if ctxEntry.GlobalReference != nil {
if !c.isGlobalContextEntryReady(ctxEntry.GlobalReference.Name, gctxentries) {
ready, message = false, "Not ready yet"
ready, message = false, "global context entry not ready"
break
}
}

View file

@ -100,9 +100,14 @@ func (g *gctxLoader) loadGctxData() ([]byte, error) {
return nil, err
}
jsonData, err := json.Marshal(data)
if err != nil {
return nil, err
var jsonData []byte
if _, ok := data.([]byte); ok {
jsonData = data.([]byte)
} else {
jsonData, err = json.Marshal(data)
if err != nil {
return nil, err
}
}
g.logger.V(6).Info("fetched json data", "name", g.entry.Name, "jsondata", jsonData)

View file

@ -70,6 +70,5 @@ func (e *entry) setData(data any) {
}
func doCall(ctx context.Context, caller apicall.Caller, call kyvernov1.APICall) (any, error) {
// TODO: unmarshall json ?
return caller.Execute(ctx, &call)
}

View file

@ -680,7 +680,7 @@ func getAllowedVariables(background bool, target bool) *regexp.Regexp {
func addContextVariables(entries []kyvernov1.ContextEntry, ctx *enginecontext.MockContext) {
for _, contextEntry := range entries {
if contextEntry.APICall != nil || contextEntry.ImageRegistry != nil || contextEntry.Variable != nil {
if contextEntry.APICall != nil || contextEntry.GlobalReference != nil || contextEntry.ImageRegistry != nil || contextEntry.Variable != nil {
ctx.AddVariable(contextEntry.Name + "*")
}
@ -1197,13 +1197,15 @@ func validateRuleContext(rule kyvernov1.Rule) error {
}
var err error
if entry.ConfigMap != nil && entry.APICall == nil && entry.ImageRegistry == nil && entry.Variable == nil {
if entry.ConfigMap != nil && entry.APICall == nil && entry.GlobalReference == nil && entry.ImageRegistry == nil && entry.Variable == nil {
err = validateConfigMap(entry)
} else if entry.ConfigMap == nil && entry.APICall != nil && entry.ImageRegistry == nil && entry.Variable == nil {
} else if entry.ConfigMap == nil && entry.APICall != nil && entry.GlobalReference == nil && entry.ImageRegistry == nil && entry.Variable == nil {
err = validateAPICall(entry)
} else if entry.ConfigMap == nil && entry.APICall == nil && entry.ImageRegistry != nil && entry.Variable == nil {
} else if entry.ConfigMap == nil && entry.APICall == nil && entry.GlobalReference != nil && entry.ImageRegistry == nil && entry.Variable == nil {
err = validateGlobalReference(entry)
} else if entry.ConfigMap == nil && entry.APICall == nil && entry.GlobalReference == nil && entry.ImageRegistry != nil && entry.Variable == nil {
err = validateImageRegistry(entry)
} else if entry.ConfigMap == nil && entry.APICall == nil && entry.ImageRegistry == nil && entry.Variable != nil {
} else if entry.ConfigMap == nil && entry.APICall == nil && entry.GlobalReference == nil && entry.ImageRegistry == nil && entry.Variable != nil {
err = validateVariable(entry)
} else {
return fmt.Errorf("exactly one of configMap or apiCall or imageRegistry or variable is required for context entries")
@ -1311,6 +1313,26 @@ func validateAPICall(entry kyvernov1.ContextEntry) error {
return nil
}
func validateGlobalReference(entry kyvernov1.ContextEntry) error {
if entry.GlobalReference == nil {
return nil
}
// If JMESPath contains variables, the validation will fail because it's not
// possible to infer which value will be inserted by the variable
// Skip validation if a variable is detected
jmesPath := variables.ReplaceAllVars(entry.GlobalReference.JMESPath, func(s string) string { return "kyvernojmespathvariable" })
if !strings.Contains(jmesPath, "kyvernojmespathvariable") && entry.GlobalReference.JMESPath != "" {
if _, err := jmespath.NewParser().Parse(entry.GlobalReference.JMESPath); err != nil {
return fmt.Errorf("failed to parse JMESPath %s: %v", entry.GlobalReference.JMESPath, err)
}
}
return nil
}
func validateImageRegistry(entry kyvernov1.ContextEntry) error {
if entry.ImageRegistry.Reference == "" {
return fmt.Errorf("a ref is required for imageRegistry context entry")

View file

@ -0,0 +1,11 @@
## Description
This test verifies that Global Context Entries are evaluated correctly.
## Expected Behavior
`new-deployment` should be created.
## Reference Issues

View file

@ -0,0 +1,23 @@
apiVersion: chainsaw.kyverno.io/v1alpha1
kind: Test
metadata:
creationTimestamp: null
name: resource-correct
spec:
steps:
- name: scenario
try:
- apply:
file: namespace.yaml
- apply:
file: main-deployment.yaml
- apply:
file: gctxentry.yaml
- apply:
file: clusterpolicy.yaml
- apply:
file: new-deployment.yaml
- assert:
file: clusterpolicy-succeeded.yaml
- assert:
file: new-deployment-exists.yaml

View file

@ -0,0 +1,9 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: namespace-has-coordinator
status:
conditions:
- reason: Succeeded
status: "True"
type: Ready

View file

@ -0,0 +1,33 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: namespace-has-coordinator
spec:
validationFailureAction: Enforce
failurePolicy: Fail
rules:
- name: main-deployment-exists
context:
- name: deploymentCount
globalReference:
name: deployments
jmesPath: "items | length(@)"
match:
all:
- resources:
kinds:
- Pod
preconditions:
all:
- key: '{{ request.operation }}'
operator: AnyIn
value:
- CREATE
- UPDATE
validate:
deny:
conditions:
any:
- key: "{{ deploymentCount }}"
operator: Equal
value: 0

View file

@ -0,0 +1,8 @@
apiVersion: kyverno.io/v2alpha1
kind: GlobalContextEntry
metadata:
name: deployments
spec:
apiCall:
urlPath: "/apis/apps/v1/namespaces/test-globalcontext/deployments"
refreshInterval: 10s

View file

@ -0,0 +1,22 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: main-deployment
namespace: test-globalcontext
labels:
app: main-deployment
spec:
replicas: 1
selector:
matchLabels:
app: main-deployment
template:
metadata:
labels:
app: main-deployment
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80

View file

@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: test-globalcontext

View file

@ -0,0 +1,7 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: new-deployment
namespace: test-globalcontext
labels:
app: new-deployment

View file

@ -0,0 +1,22 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: new-deployment
namespace: test-globalcontext
labels:
app: new-deployment
spec:
replicas: 1
selector:
matchLabels:
app: new-deployment
template:
metadata:
labels:
app: new-deployment
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80

View file

@ -0,0 +1,11 @@
## Description
This test verifies that policies are not ready if referenced Global Context Entries don't exist.
## Expected Behavior
`new-deployment` should not be created because the policy is not ready.
## Reference Issues

View file

@ -0,0 +1,26 @@
apiVersion: chainsaw.kyverno.io/v1alpha1
kind: Test
metadata:
creationTimestamp: null
name: resource-not-exist
spec:
steps:
- name: setup
try:
- apply:
file: namespace.yaml
- apply:
file: main-deployment.yaml
- apply:
file: gctxentry.yaml
- apply:
file: clusterpolicy.yaml
- assert:
file: clusterpolicy-failed.yaml
- name: negative
try:
- apply:
expect:
- check:
($error != null): true
file: new-deployment.yaml

View file

@ -0,0 +1,9 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: namespace-has-coordinator
status:
conditions:
- reason: Failed
status: "False"
type: Ready

View file

@ -0,0 +1,33 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: namespace-has-coordinator
spec:
validationFailureAction: Enforce
failurePolicy: Fail
rules:
- name: main-deployment-exists
context:
- name: deploymentCount
globalReference:
name: non-existent-reference
jmesPath: "items | length(@)"
match:
all:
- resources:
kinds:
- Pod
preconditions:
all:
- key: '{{ request.operation }}'
operator: AnyIn
value:
- CREATE
- UPDATE
validate:
deny:
conditions:
any:
- key: "{{ deploymentCount }}"
operator: Equal
value: 0

View file

@ -0,0 +1,8 @@
apiVersion: kyverno.io/v2alpha1
kind: GlobalContextEntry
metadata:
name: deployments
spec:
apiCall:
urlPath: "/apis/apps/v1/namespaces/test-globalcontext/deployments"
refreshInterval: 10s

View file

@ -0,0 +1,22 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: main-deployment
namespace: test-globalcontext
labels:
app: main-deployment
spec:
replicas: 1
selector:
matchLabels:
app: main-deployment
template:
metadata:
labels:
app: main-deployment
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80

View file

@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: test-globalcontext

View file

@ -0,0 +1,22 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: new-deployment
namespace: test-globalcontext
labels:
app: new-deployment
spec:
replicas: 1
selector:
matchLabels:
app: new-deployment
template:
metadata:
labels:
app: new-deployment
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80

View file

@ -0,0 +1,11 @@
## Description
This test verifies that Global Context Entries are evaluated correctly.
## Expected Behavior
`new-deployment` should be created.
## Reference Issues

View file

@ -0,0 +1,23 @@
apiVersion: chainsaw.kyverno.io/v1alpha1
kind: Test
metadata:
creationTimestamp: null
name: resource-correct
spec:
steps:
- name: scenario
try:
- apply:
file: namespace.yaml
- apply:
file: main-deployment.yaml
- apply:
file: gctxentry.yaml
- apply:
file: clusterpolicy.yaml
- apply:
file: new-deployment.yaml
- assert:
file: clusterpolicy-succeeded.yaml
- assert:
file: new-deployment-exists.yaml

View file

@ -0,0 +1,9 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: namespace-has-coordinator
status:
conditions:
- reason: Succeeded
status: "True"
type: Ready

View file

@ -0,0 +1,33 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: namespace-has-coordinator
spec:
validationFailureAction: Enforce
failurePolicy: Fail
rules:
- name: main-deployment-exists
context:
- name: deploymentCount
globalReference:
name: deployments
jmesPath: "length(@)"
match:
all:
- resources:
kinds:
- Pod
preconditions:
all:
- key: '{{ request.operation }}'
operator: AnyIn
value:
- CREATE
- UPDATE
validate:
deny:
conditions:
any:
- key: "{{ deploymentCount }}"
operator: Equal
value: 0

View file

@ -0,0 +1,10 @@
apiVersion: kyverno.io/v2alpha1
kind: GlobalContextEntry
metadata:
name: deployments
spec:
kubernetesResource:
group: apps
version: v1
resource: deployments
namespace: test-globalcontext

View file

@ -0,0 +1,22 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: main-deployment
namespace: test-globalcontext
labels:
app: main-deployment
spec:
replicas: 1
selector:
matchLabels:
app: main-deployment
template:
metadata:
labels:
app: main-deployment
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80

View file

@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: test-globalcontext

View file

@ -0,0 +1,7 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: new-deployment
namespace: test-globalcontext
labels:
app: new-deployment

View file

@ -0,0 +1,22 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: new-deployment
namespace: test-globalcontext
labels:
app: new-deployment
spec:
replicas: 1
selector:
matchLabels:
app: new-deployment
template:
metadata:
labels:
app: new-deployment
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80

View file

@ -0,0 +1,11 @@
## Description
This test verifies that policies are not ready if referenced Global Context Entries don't exist.
## Expected Behavior
`new-deployment` should not be created because the policy is not ready.
## Reference Issues

View file

@ -0,0 +1,26 @@
apiVersion: chainsaw.kyverno.io/v1alpha1
kind: Test
metadata:
creationTimestamp: null
name: resource-not-exist
spec:
steps:
- name: setup
try:
- apply:
file: namespace.yaml
- apply:
file: main-deployment.yaml
- apply:
file: gctxentry.yaml
- apply:
file: clusterpolicy.yaml
- assert:
file: clusterpolicy-failed.yaml
- name: negative
try:
- apply:
expect:
- check:
($error != null): true
file: new-deployment.yaml

View file

@ -0,0 +1,9 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: namespace-has-coordinator
status:
conditions:
- reason: Failed
status: "False"
type: Ready

View file

@ -0,0 +1,33 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: namespace-has-coordinator
spec:
validationFailureAction: Enforce
failurePolicy: Fail
rules:
- name: main-deployment-exists
context:
- name: deploymentCount
globalReference:
name: non-existent-reference
jmesPath: "length(@)"
match:
all:
- resources:
kinds:
- Pod
preconditions:
all:
- key: '{{ request.operation }}'
operator: AnyIn
value:
- CREATE
- UPDATE
validate:
deny:
conditions:
any:
- key: "{{ deploymentCount }}"
operator: Equal
value: 0

View file

@ -0,0 +1,10 @@
apiVersion: kyverno.io/v2alpha1
kind: GlobalContextEntry
metadata:
name: deployments
spec:
kubernetesResource:
group: apps
version: v1
resource: deployments
namespace: test-globalcontext

View file

@ -0,0 +1,22 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: main-deployment
namespace: test-globalcontext
labels:
app: main-deployment
spec:
replicas: 1
selector:
matchLabels:
app: main-deployment
template:
metadata:
labels:
app: main-deployment
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80

View file

@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: test-globalcontext

View file

@ -0,0 +1,22 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: new-deployment
namespace: test-globalcontext
labels:
app: new-deployment
spec:
replicas: 1
selector:
matchLabels:
app: new-deployment
template:
metadata:
labels:
app: new-deployment
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80