1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2024-12-14 11:57:48 +00:00

support list foreach (#2522)

* support list foreach

* fix testcase for each

* fix mutate issue

* Fix mutate patch issue

* fix yaml

* fix e2e test foreach validate list

* code indentation

* fix comments

* delete unwanted files
This commit is contained in:
Vyankatesh Kudtarkar 2021-10-14 12:50:52 +05:30 committed by GitHub
parent 2089767c85
commit 2798287497
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 2378 additions and 2145 deletions

View file

@ -1440,156 +1440,164 @@ spec:
foreach:
description: ForEach applies policy rule changes to nested
elements.
properties:
context:
description: Context defines variables and data sources
that can be used during rule execution.
items:
description: ContextEntry adds variables and data
sources to a rule Context. Either a ConfigMap reference
or a APILookup must be provided.
items:
description: ForEach applies policy rule changes to nested
elements.
properties:
context:
description: Context defines variables and data sources
that can be used during rule execution.
items:
description: ContextEntry adds variables and data
sources to a rule Context. Either a ConfigMap
reference or a APILookup must be provided.
properties:
apiCall:
description: APICall defines an HTTP request
to the Kubernetes API server. The JSON data
retrieved is stored in the context.
properties:
jmesPath:
description: JMESPath is an optional JSON
Match Expression that can be used to transform
the JSON response returned from the API
server. For example a JMESPath of "items
| length(@)" applied to the API server
response to the URLPath "/apis/apps/v1/deployments"
will return the total count of deployments
across all namespaces.
type: string
urlPath:
description: URLPath is the URL path to
be used in the HTTP GET request to the
Kubernetes API server (e.g. "/api/v1/namespaces"
or "/apis/apps/v1/deployments"). The
format required is the same format used
by the `kubectl get --raw` command.
type: string
required:
- urlPath
type: object
configMap:
description: ConfigMap is the ConfigMap reference.
properties:
name:
description: Name is the ConfigMap name.
type: string
namespace:
description: Namespace is the ConfigMap
namespace.
type: string
required:
- name
type: object
name:
description: Name is the variable name.
type: string
type: object
type: array
list:
description: List specifies a JMESPath expression
that results in one or more elements to which the
validation logic is applied.
type: string
patchStrategicMerge:
description: PatchStrategicMerge is a strategic merge
patch used to modify resources. See https://kubernetes.io/docs/tasks/manage-kubernetes-objects/update-api-object-kubectl-patch/
and https://kubectl.docs.kubernetes.io/references/kustomize/patchesstrategicmerge/.
x-kubernetes-preserve-unknown-fields: true
preconditions:
description: 'Preconditions are used to determine
if a policy rule should be applied by evaluating
a set of conditions. The declaration can contain
nested `any` or `all` statements. See: https://kyverno.io/docs/writing-policies/preconditions/'
properties:
apiCall:
description: APICall defines an HTTP request to
the Kubernetes API server. The JSON data retrieved
is stored in the context.
properties:
jmesPath:
description: JMESPath is an optional JSON
Match Expression that can be used to transform
the JSON response returned from the API
server. For example a JMESPath of "items
| length(@)" applied to the API server response
to the URLPath "/apis/apps/v1/deployments"
will return the total count of deployments
across all namespaces.
type: string
urlPath:
description: URLPath is the URL path to be
used in the HTTP GET request to the Kubernetes
API server (e.g. "/api/v1/namespaces" or "/apis/apps/v1/deployments").
The format required is the same format used
by the `kubectl get --raw` command.
type: string
required:
- urlPath
type: object
configMap:
description: ConfigMap is the ConfigMap reference.
properties:
name:
description: Name is the ConfigMap name.
type: string
namespace:
description: Namespace is the ConfigMap namespace.
type: string
required:
- name
type: object
name:
description: Name is the variable name.
type: string
all:
description: AllConditions enable variable-based
conditional rule execution. This is useful for
finer control of when an rule is applied. A
condition can reference object data using JMESPath
notation. Here, all of the conditions need to
pass
items:
description: Condition defines variable-based
conditional criteria for rule execution.
properties:
key:
description: Key is the context entry (using
JMESPath) for conditional rule evaluation.
x-kubernetes-preserve-unknown-fields: true
operator:
description: Operator is the operation to
perform. Valid operators are Equals, NotEquals,
In and NotIn.
enum:
- Equals
- NotEquals
- In
- NotIn
- GreaterThanOrEquals
- GreaterThan
- LessThanOrEquals
- LessThan
- DurationGreaterThanOrEquals
- DurationGreaterThan
- DurationLessThanOrEquals
- DurationLessThan
type: string
value:
description: Value is the conditional value,
or set of values. The values can be fixed
set or can be variables declared using
using JMESPath.
x-kubernetes-preserve-unknown-fields: true
type: object
type: array
any:
description: AnyConditions enable variable-based
conditional rule execution. This is useful for
finer control of when an rule is applied. A
condition can reference object data using JMESPath
notation. Here, at least one of the conditions
need to pass
items:
description: Condition defines variable-based
conditional criteria for rule execution.
properties:
key:
description: Key is the context entry (using
JMESPath) for conditional rule evaluation.
x-kubernetes-preserve-unknown-fields: true
operator:
description: Operator is the operation to
perform. Valid operators are Equals, NotEquals,
In and NotIn.
enum:
- Equals
- NotEquals
- In
- NotIn
- GreaterThanOrEquals
- GreaterThan
- LessThanOrEquals
- LessThan
- DurationGreaterThanOrEquals
- DurationGreaterThan
- DurationLessThanOrEquals
- DurationLessThan
type: string
value:
description: Value is the conditional value,
or set of values. The values can be fixed
set or can be variables declared using
using JMESPath.
x-kubernetes-preserve-unknown-fields: true
type: object
type: array
type: object
type: array
list:
description: List specifies a JMESPath expression that
results in one or more elements to which the validation
logic is applied.
type: string
patchStrategicMerge:
description: PatchStrategicMerge is a strategic merge
patch used to modify resources. See https://kubernetes.io/docs/tasks/manage-kubernetes-objects/update-api-object-kubectl-patch/
and https://kubectl.docs.kubernetes.io/references/kustomize/patchesstrategicmerge/.
x-kubernetes-preserve-unknown-fields: true
preconditions:
description: 'Preconditions are used to determine if
a policy rule should be applied by evaluating a set
of conditions. The declaration can contain nested
`any` or `all` statements. See: https://kyverno.io/docs/writing-policies/preconditions/'
properties:
all:
description: AllConditions enable variable-based
conditional rule execution. This is useful for
finer control of when an rule is applied. A condition
can reference object data using JMESPath notation.
Here, all of the conditions need to pass
items:
description: Condition defines variable-based
conditional criteria for rule execution.
properties:
key:
description: Key is the context entry (using
JMESPath) for conditional rule evaluation.
x-kubernetes-preserve-unknown-fields: true
operator:
description: Operator is the operation to
perform. Valid operators are Equals, NotEquals,
In and NotIn.
enum:
- Equals
- NotEquals
- In
- NotIn
- GreaterThanOrEquals
- GreaterThan
- LessThanOrEquals
- LessThan
- DurationGreaterThanOrEquals
- DurationGreaterThan
- DurationLessThanOrEquals
- DurationLessThan
type: string
value:
description: Value is the conditional value,
or set of values. The values can be fixed
set or can be variables declared using using
JMESPath.
x-kubernetes-preserve-unknown-fields: true
type: object
type: array
any:
description: AnyConditions enable variable-based
conditional rule execution. This is useful for
finer control of when an rule is applied. A condition
can reference object data using JMESPath notation.
Here, at least one of the conditions need to pass
items:
description: Condition defines variable-based
conditional criteria for rule execution.
properties:
key:
description: Key is the context entry (using
JMESPath) for conditional rule evaluation.
x-kubernetes-preserve-unknown-fields: true
operator:
description: Operator is the operation to
perform. Valid operators are Equals, NotEquals,
In and NotIn.
enum:
- Equals
- NotEquals
- In
- NotIn
- GreaterThanOrEquals
- GreaterThan
- LessThanOrEquals
- LessThan
- DurationGreaterThanOrEquals
- DurationGreaterThan
- DurationLessThanOrEquals
- DurationLessThan
type: string
value:
description: Value is the conditional value,
or set of values. The values can be fixed
set or can be variables declared using using
JMESPath.
x-kubernetes-preserve-unknown-fields: true
type: object
type: array
type: object
x-kubernetes-preserve-unknown-fields: true
type: object
x-kubernetes-preserve-unknown-fields: true
type: object
type: array
overlay:
description: Overlay specifies an overlay pattern to modify
resources. DEPRECATED. Use PatchStrategicMerge instead.
@ -1661,175 +1669,183 @@ spec:
x-kubernetes-preserve-unknown-fields: true
type: object
foreach:
description: ForEach applies policy rule checks to nested
description: ForEach applies policy rule changes to nested
elements.
properties:
anyPattern:
description: AnyPattern specifies list of validation
patterns. At least one of the patterns must be satisfied
for the validation rule to succeed.
x-kubernetes-preserve-unknown-fields: true
context:
description: Context defines variables and data sources
that can be used during rule execution.
items:
description: ContextEntry adds variables and data
sources to a rule Context. Either a ConfigMap reference
or a APILookup must be provided.
items:
description: ForEach applies policy rule checks to nested
elements.
properties:
anyPattern:
description: AnyPattern specifies list of validation
patterns. At least one of the patterns must be satisfied
for the validation rule to succeed.
x-kubernetes-preserve-unknown-fields: true
context:
description: Context defines variables and data sources
that can be used during rule execution.
items:
description: ContextEntry adds variables and data
sources to a rule Context. Either a ConfigMap
reference or a APILookup must be provided.
properties:
apiCall:
description: APICall defines an HTTP request
to the Kubernetes API server. The JSON data
retrieved is stored in the context.
properties:
jmesPath:
description: JMESPath is an optional JSON
Match Expression that can be used to transform
the JSON response returned from the API
server. For example a JMESPath of "items
| length(@)" applied to the API server
response to the URLPath "/apis/apps/v1/deployments"
will return the total count of deployments
across all namespaces.
type: string
urlPath:
description: URLPath is the URL path to
be used in the HTTP GET request to the
Kubernetes API server (e.g. "/api/v1/namespaces"
or "/apis/apps/v1/deployments"). The
format required is the same format used
by the `kubectl get --raw` command.
type: string
required:
- urlPath
type: object
configMap:
description: ConfigMap is the ConfigMap reference.
properties:
name:
description: Name is the ConfigMap name.
type: string
namespace:
description: Namespace is the ConfigMap
namespace.
type: string
required:
- name
type: object
name:
description: Name is the variable name.
type: string
type: object
type: array
deny:
description: Deny defines conditions used to pass
or fail a validation rule.
properties:
apiCall:
description: APICall defines an HTTP request to
the Kubernetes API server. The JSON data retrieved
is stored in the context.
properties:
jmesPath:
description: JMESPath is an optional JSON
Match Expression that can be used to transform
the JSON response returned from the API
server. For example a JMESPath of "items
| length(@)" applied to the API server response
to the URLPath "/apis/apps/v1/deployments"
will return the total count of deployments
across all namespaces.
type: string
urlPath:
description: URLPath is the URL path to be
used in the HTTP GET request to the Kubernetes
API server (e.g. "/api/v1/namespaces" or "/apis/apps/v1/deployments").
The format required is the same format used
by the `kubectl get --raw` command.
type: string
required:
- urlPath
type: object
configMap:
description: ConfigMap is the ConfigMap reference.
properties:
name:
description: Name is the ConfigMap name.
type: string
namespace:
description: Namespace is the ConfigMap namespace.
type: string
required:
- name
type: object
name:
description: Name is the variable name.
type: string
conditions:
description: 'Multiple conditions can be declared
under an `any` or `all` statement. A direct
list of conditions (without `any` or `all` statements)
is also supported for backwards compatibility
but will be deprecated in the next major release.
See: https://kyverno.io/docs/writing-policies/validate/#deny-rules'
x-kubernetes-preserve-unknown-fields: true
type: object
type: array
deny:
description: Deny defines conditions used to pass or
fail a validation rule.
properties:
conditions:
description: 'Multiple conditions can be declared
under an `any` or `all` statement. A direct list
of conditions (without `any` or `all` statements)
is also supported for backwards compatibility
but will be deprecated in the next major release.
See: https://kyverno.io/docs/writing-policies/validate/#deny-rules'
x-kubernetes-preserve-unknown-fields: true
type: object
list:
description: List specifies a JMESPath expression that
results in one or more elements to which the validation
logic is applied.
type: string
pattern:
description: Pattern specifies an overlay-style pattern
used to check resources.
x-kubernetes-preserve-unknown-fields: true
preconditions:
description: 'Preconditions are used to determine if
a policy rule should be applied by evaluating a set
of conditions. The declaration can contain nested
`any` or `all` statements. See: https://kyverno.io/docs/writing-policies/preconditions/'
properties:
all:
description: AllConditions enable variable-based
conditional rule execution. This is useful for
finer control of when an rule is applied. A condition
can reference object data using JMESPath notation.
Here, all of the conditions need to pass
items:
description: Condition defines variable-based
conditional criteria for rule execution.
properties:
key:
description: Key is the context entry (using
JMESPath) for conditional rule evaluation.
x-kubernetes-preserve-unknown-fields: true
operator:
description: Operator is the operation to
perform. Valid operators are Equals, NotEquals,
In and NotIn.
enum:
- Equals
- NotEquals
- In
- NotIn
- GreaterThanOrEquals
- GreaterThan
- LessThanOrEquals
- LessThan
- DurationGreaterThanOrEquals
- DurationGreaterThan
- DurationLessThanOrEquals
- DurationLessThan
type: string
value:
description: Value is the conditional value,
or set of values. The values can be fixed
set or can be variables declared using using
JMESPath.
x-kubernetes-preserve-unknown-fields: true
type: object
type: array
any:
description: AnyConditions enable variable-based
conditional rule execution. This is useful for
finer control of when an rule is applied. A condition
can reference object data using JMESPath notation.
Here, at least one of the conditions need to pass
items:
description: Condition defines variable-based
conditional criteria for rule execution.
properties:
key:
description: Key is the context entry (using
JMESPath) for conditional rule evaluation.
x-kubernetes-preserve-unknown-fields: true
operator:
description: Operator is the operation to
perform. Valid operators are Equals, NotEquals,
In and NotIn.
enum:
- Equals
- NotEquals
- In
- NotIn
- GreaterThanOrEquals
- GreaterThan
- LessThanOrEquals
- LessThan
- DurationGreaterThanOrEquals
- DurationGreaterThan
- DurationLessThanOrEquals
- DurationLessThan
type: string
value:
description: Value is the conditional value,
or set of values. The values can be fixed
set or can be variables declared using using
JMESPath.
x-kubernetes-preserve-unknown-fields: true
type: object
type: array
type: object
x-kubernetes-preserve-unknown-fields: true
type: object
list:
description: List specifies a JMESPath expression
that results in one or more elements to which the
validation logic is applied.
type: string
pattern:
description: Pattern specifies an overlay-style pattern
used to check resources.
x-kubernetes-preserve-unknown-fields: true
preconditions:
description: 'Preconditions are used to determine
if a policy rule should be applied by evaluating
a set of conditions. The declaration can contain
nested `any` or `all` statements. See: https://kyverno.io/docs/writing-policies/preconditions/'
properties:
all:
description: AllConditions enable variable-based
conditional rule execution. This is useful for
finer control of when an rule is applied. A
condition can reference object data using JMESPath
notation. Here, all of the conditions need to
pass
items:
description: Condition defines variable-based
conditional criteria for rule execution.
properties:
key:
description: Key is the context entry (using
JMESPath) for conditional rule evaluation.
x-kubernetes-preserve-unknown-fields: true
operator:
description: Operator is the operation to
perform. Valid operators are Equals, NotEquals,
In and NotIn.
enum:
- Equals
- NotEquals
- In
- NotIn
- GreaterThanOrEquals
- GreaterThan
- LessThanOrEquals
- LessThan
- DurationGreaterThanOrEquals
- DurationGreaterThan
- DurationLessThanOrEquals
- DurationLessThan
type: string
value:
description: Value is the conditional value,
or set of values. The values can be fixed
set or can be variables declared using
using JMESPath.
x-kubernetes-preserve-unknown-fields: true
type: object
type: array
any:
description: AnyConditions enable variable-based
conditional rule execution. This is useful for
finer control of when an rule is applied. A
condition can reference object data using JMESPath
notation. Here, at least one of the conditions
need to pass
items:
description: Condition defines variable-based
conditional criteria for rule execution.
properties:
key:
description: Key is the context entry (using
JMESPath) for conditional rule evaluation.
x-kubernetes-preserve-unknown-fields: true
operator:
description: Operator is the operation to
perform. Valid operators are Equals, NotEquals,
In and NotIn.
enum:
- Equals
- NotEquals
- In
- NotIn
- GreaterThanOrEquals
- GreaterThan
- LessThanOrEquals
- LessThan
- DurationGreaterThanOrEquals
- DurationGreaterThan
- DurationLessThanOrEquals
- DurationLessThan
type: string
value:
description: Value is the conditional value,
or set of values. The values can be fixed
set or can be variables declared using
using JMESPath.
x-kubernetes-preserve-unknown-fields: true
type: object
type: array
type: object
x-kubernetes-preserve-unknown-fields: true
type: object
type: array
message:
description: Message specifies a custom message to be displayed
on failure.

View file

@ -1441,156 +1441,164 @@ spec:
foreach:
description: ForEach applies policy rule changes to nested
elements.
properties:
context:
description: Context defines variables and data sources
that can be used during rule execution.
items:
description: ContextEntry adds variables and data
sources to a rule Context. Either a ConfigMap reference
or a APILookup must be provided.
items:
description: ForEach applies policy rule changes to nested
elements.
properties:
context:
description: Context defines variables and data sources
that can be used during rule execution.
items:
description: ContextEntry adds variables and data
sources to a rule Context. Either a ConfigMap
reference or a APILookup must be provided.
properties:
apiCall:
description: APICall defines an HTTP request
to the Kubernetes API server. The JSON data
retrieved is stored in the context.
properties:
jmesPath:
description: JMESPath is an optional JSON
Match Expression that can be used to transform
the JSON response returned from the API
server. For example a JMESPath of "items
| length(@)" applied to the API server
response to the URLPath "/apis/apps/v1/deployments"
will return the total count of deployments
across all namespaces.
type: string
urlPath:
description: URLPath is the URL path to
be used in the HTTP GET request to the
Kubernetes API server (e.g. "/api/v1/namespaces"
or "/apis/apps/v1/deployments"). The
format required is the same format used
by the `kubectl get --raw` command.
type: string
required:
- urlPath
type: object
configMap:
description: ConfigMap is the ConfigMap reference.
properties:
name:
description: Name is the ConfigMap name.
type: string
namespace:
description: Namespace is the ConfigMap
namespace.
type: string
required:
- name
type: object
name:
description: Name is the variable name.
type: string
type: object
type: array
list:
description: List specifies a JMESPath expression
that results in one or more elements to which the
validation logic is applied.
type: string
patchStrategicMerge:
description: PatchStrategicMerge is a strategic merge
patch used to modify resources. See https://kubernetes.io/docs/tasks/manage-kubernetes-objects/update-api-object-kubectl-patch/
and https://kubectl.docs.kubernetes.io/references/kustomize/patchesstrategicmerge/.
x-kubernetes-preserve-unknown-fields: true
preconditions:
description: 'Preconditions are used to determine
if a policy rule should be applied by evaluating
a set of conditions. The declaration can contain
nested `any` or `all` statements. See: https://kyverno.io/docs/writing-policies/preconditions/'
properties:
apiCall:
description: APICall defines an HTTP request to
the Kubernetes API server. The JSON data retrieved
is stored in the context.
properties:
jmesPath:
description: JMESPath is an optional JSON
Match Expression that can be used to transform
the JSON response returned from the API
server. For example a JMESPath of "items
| length(@)" applied to the API server response
to the URLPath "/apis/apps/v1/deployments"
will return the total count of deployments
across all namespaces.
type: string
urlPath:
description: URLPath is the URL path to be
used in the HTTP GET request to the Kubernetes
API server (e.g. "/api/v1/namespaces" or "/apis/apps/v1/deployments").
The format required is the same format used
by the `kubectl get --raw` command.
type: string
required:
- urlPath
type: object
configMap:
description: ConfigMap is the ConfigMap reference.
properties:
name:
description: Name is the ConfigMap name.
type: string
namespace:
description: Namespace is the ConfigMap namespace.
type: string
required:
- name
type: object
name:
description: Name is the variable name.
type: string
all:
description: AllConditions enable variable-based
conditional rule execution. This is useful for
finer control of when an rule is applied. A
condition can reference object data using JMESPath
notation. Here, all of the conditions need to
pass
items:
description: Condition defines variable-based
conditional criteria for rule execution.
properties:
key:
description: Key is the context entry (using
JMESPath) for conditional rule evaluation.
x-kubernetes-preserve-unknown-fields: true
operator:
description: Operator is the operation to
perform. Valid operators are Equals, NotEquals,
In and NotIn.
enum:
- Equals
- NotEquals
- In
- NotIn
- GreaterThanOrEquals
- GreaterThan
- LessThanOrEquals
- LessThan
- DurationGreaterThanOrEquals
- DurationGreaterThan
- DurationLessThanOrEquals
- DurationLessThan
type: string
value:
description: Value is the conditional value,
or set of values. The values can be fixed
set or can be variables declared using
using JMESPath.
x-kubernetes-preserve-unknown-fields: true
type: object
type: array
any:
description: AnyConditions enable variable-based
conditional rule execution. This is useful for
finer control of when an rule is applied. A
condition can reference object data using JMESPath
notation. Here, at least one of the conditions
need to pass
items:
description: Condition defines variable-based
conditional criteria for rule execution.
properties:
key:
description: Key is the context entry (using
JMESPath) for conditional rule evaluation.
x-kubernetes-preserve-unknown-fields: true
operator:
description: Operator is the operation to
perform. Valid operators are Equals, NotEquals,
In and NotIn.
enum:
- Equals
- NotEquals
- In
- NotIn
- GreaterThanOrEquals
- GreaterThan
- LessThanOrEquals
- LessThan
- DurationGreaterThanOrEquals
- DurationGreaterThan
- DurationLessThanOrEquals
- DurationLessThan
type: string
value:
description: Value is the conditional value,
or set of values. The values can be fixed
set or can be variables declared using
using JMESPath.
x-kubernetes-preserve-unknown-fields: true
type: object
type: array
type: object
type: array
list:
description: List specifies a JMESPath expression that
results in one or more elements to which the validation
logic is applied.
type: string
patchStrategicMerge:
description: PatchStrategicMerge is a strategic merge
patch used to modify resources. See https://kubernetes.io/docs/tasks/manage-kubernetes-objects/update-api-object-kubectl-patch/
and https://kubectl.docs.kubernetes.io/references/kustomize/patchesstrategicmerge/.
x-kubernetes-preserve-unknown-fields: true
preconditions:
description: 'Preconditions are used to determine if
a policy rule should be applied by evaluating a set
of conditions. The declaration can contain nested
`any` or `all` statements. See: https://kyverno.io/docs/writing-policies/preconditions/'
properties:
all:
description: AllConditions enable variable-based
conditional rule execution. This is useful for
finer control of when an rule is applied. A condition
can reference object data using JMESPath notation.
Here, all of the conditions need to pass
items:
description: Condition defines variable-based
conditional criteria for rule execution.
properties:
key:
description: Key is the context entry (using
JMESPath) for conditional rule evaluation.
x-kubernetes-preserve-unknown-fields: true
operator:
description: Operator is the operation to
perform. Valid operators are Equals, NotEquals,
In and NotIn.
enum:
- Equals
- NotEquals
- In
- NotIn
- GreaterThanOrEquals
- GreaterThan
- LessThanOrEquals
- LessThan
- DurationGreaterThanOrEquals
- DurationGreaterThan
- DurationLessThanOrEquals
- DurationLessThan
type: string
value:
description: Value is the conditional value,
or set of values. The values can be fixed
set or can be variables declared using using
JMESPath.
x-kubernetes-preserve-unknown-fields: true
type: object
type: array
any:
description: AnyConditions enable variable-based
conditional rule execution. This is useful for
finer control of when an rule is applied. A condition
can reference object data using JMESPath notation.
Here, at least one of the conditions need to pass
items:
description: Condition defines variable-based
conditional criteria for rule execution.
properties:
key:
description: Key is the context entry (using
JMESPath) for conditional rule evaluation.
x-kubernetes-preserve-unknown-fields: true
operator:
description: Operator is the operation to
perform. Valid operators are Equals, NotEquals,
In and NotIn.
enum:
- Equals
- NotEquals
- In
- NotIn
- GreaterThanOrEquals
- GreaterThan
- LessThanOrEquals
- LessThan
- DurationGreaterThanOrEquals
- DurationGreaterThan
- DurationLessThanOrEquals
- DurationLessThan
type: string
value:
description: Value is the conditional value,
or set of values. The values can be fixed
set or can be variables declared using using
JMESPath.
x-kubernetes-preserve-unknown-fields: true
type: object
type: array
type: object
x-kubernetes-preserve-unknown-fields: true
type: object
x-kubernetes-preserve-unknown-fields: true
type: object
type: array
overlay:
description: Overlay specifies an overlay pattern to modify
resources. DEPRECATED. Use PatchStrategicMerge instead.
@ -1662,175 +1670,183 @@ spec:
x-kubernetes-preserve-unknown-fields: true
type: object
foreach:
description: ForEach applies policy rule checks to nested
description: ForEach applies policy rule changes to nested
elements.
properties:
anyPattern:
description: AnyPattern specifies list of validation
patterns. At least one of the patterns must be satisfied
for the validation rule to succeed.
x-kubernetes-preserve-unknown-fields: true
context:
description: Context defines variables and data sources
that can be used during rule execution.
items:
description: ContextEntry adds variables and data
sources to a rule Context. Either a ConfigMap reference
or a APILookup must be provided.
items:
description: ForEach applies policy rule checks to nested
elements.
properties:
anyPattern:
description: AnyPattern specifies list of validation
patterns. At least one of the patterns must be satisfied
for the validation rule to succeed.
x-kubernetes-preserve-unknown-fields: true
context:
description: Context defines variables and data sources
that can be used during rule execution.
items:
description: ContextEntry adds variables and data
sources to a rule Context. Either a ConfigMap
reference or a APILookup must be provided.
properties:
apiCall:
description: APICall defines an HTTP request
to the Kubernetes API server. The JSON data
retrieved is stored in the context.
properties:
jmesPath:
description: JMESPath is an optional JSON
Match Expression that can be used to transform
the JSON response returned from the API
server. For example a JMESPath of "items
| length(@)" applied to the API server
response to the URLPath "/apis/apps/v1/deployments"
will return the total count of deployments
across all namespaces.
type: string
urlPath:
description: URLPath is the URL path to
be used in the HTTP GET request to the
Kubernetes API server (e.g. "/api/v1/namespaces"
or "/apis/apps/v1/deployments"). The
format required is the same format used
by the `kubectl get --raw` command.
type: string
required:
- urlPath
type: object
configMap:
description: ConfigMap is the ConfigMap reference.
properties:
name:
description: Name is the ConfigMap name.
type: string
namespace:
description: Namespace is the ConfigMap
namespace.
type: string
required:
- name
type: object
name:
description: Name is the variable name.
type: string
type: object
type: array
deny:
description: Deny defines conditions used to pass
or fail a validation rule.
properties:
apiCall:
description: APICall defines an HTTP request to
the Kubernetes API server. The JSON data retrieved
is stored in the context.
properties:
jmesPath:
description: JMESPath is an optional JSON
Match Expression that can be used to transform
the JSON response returned from the API
server. For example a JMESPath of "items
| length(@)" applied to the API server response
to the URLPath "/apis/apps/v1/deployments"
will return the total count of deployments
across all namespaces.
type: string
urlPath:
description: URLPath is the URL path to be
used in the HTTP GET request to the Kubernetes
API server (e.g. "/api/v1/namespaces" or "/apis/apps/v1/deployments").
The format required is the same format used
by the `kubectl get --raw` command.
type: string
required:
- urlPath
type: object
configMap:
description: ConfigMap is the ConfigMap reference.
properties:
name:
description: Name is the ConfigMap name.
type: string
namespace:
description: Namespace is the ConfigMap namespace.
type: string
required:
- name
type: object
name:
description: Name is the variable name.
type: string
conditions:
description: 'Multiple conditions can be declared
under an `any` or `all` statement. A direct
list of conditions (without `any` or `all` statements)
is also supported for backwards compatibility
but will be deprecated in the next major release.
See: https://kyverno.io/docs/writing-policies/validate/#deny-rules'
x-kubernetes-preserve-unknown-fields: true
type: object
type: array
deny:
description: Deny defines conditions used to pass or
fail a validation rule.
properties:
conditions:
description: 'Multiple conditions can be declared
under an `any` or `all` statement. A direct list
of conditions (without `any` or `all` statements)
is also supported for backwards compatibility
but will be deprecated in the next major release.
See: https://kyverno.io/docs/writing-policies/validate/#deny-rules'
x-kubernetes-preserve-unknown-fields: true
type: object
list:
description: List specifies a JMESPath expression that
results in one or more elements to which the validation
logic is applied.
type: string
pattern:
description: Pattern specifies an overlay-style pattern
used to check resources.
x-kubernetes-preserve-unknown-fields: true
preconditions:
description: 'Preconditions are used to determine if
a policy rule should be applied by evaluating a set
of conditions. The declaration can contain nested
`any` or `all` statements. See: https://kyverno.io/docs/writing-policies/preconditions/'
properties:
all:
description: AllConditions enable variable-based
conditional rule execution. This is useful for
finer control of when an rule is applied. A condition
can reference object data using JMESPath notation.
Here, all of the conditions need to pass
items:
description: Condition defines variable-based
conditional criteria for rule execution.
properties:
key:
description: Key is the context entry (using
JMESPath) for conditional rule evaluation.
x-kubernetes-preserve-unknown-fields: true
operator:
description: Operator is the operation to
perform. Valid operators are Equals, NotEquals,
In and NotIn.
enum:
- Equals
- NotEquals
- In
- NotIn
- GreaterThanOrEquals
- GreaterThan
- LessThanOrEquals
- LessThan
- DurationGreaterThanOrEquals
- DurationGreaterThan
- DurationLessThanOrEquals
- DurationLessThan
type: string
value:
description: Value is the conditional value,
or set of values. The values can be fixed
set or can be variables declared using using
JMESPath.
x-kubernetes-preserve-unknown-fields: true
type: object
type: array
any:
description: AnyConditions enable variable-based
conditional rule execution. This is useful for
finer control of when an rule is applied. A condition
can reference object data using JMESPath notation.
Here, at least one of the conditions need to pass
items:
description: Condition defines variable-based
conditional criteria for rule execution.
properties:
key:
description: Key is the context entry (using
JMESPath) for conditional rule evaluation.
x-kubernetes-preserve-unknown-fields: true
operator:
description: Operator is the operation to
perform. Valid operators are Equals, NotEquals,
In and NotIn.
enum:
- Equals
- NotEquals
- In
- NotIn
- GreaterThanOrEquals
- GreaterThan
- LessThanOrEquals
- LessThan
- DurationGreaterThanOrEquals
- DurationGreaterThan
- DurationLessThanOrEquals
- DurationLessThan
type: string
value:
description: Value is the conditional value,
or set of values. The values can be fixed
set or can be variables declared using using
JMESPath.
x-kubernetes-preserve-unknown-fields: true
type: object
type: array
type: object
x-kubernetes-preserve-unknown-fields: true
type: object
list:
description: List specifies a JMESPath expression
that results in one or more elements to which the
validation logic is applied.
type: string
pattern:
description: Pattern specifies an overlay-style pattern
used to check resources.
x-kubernetes-preserve-unknown-fields: true
preconditions:
description: 'Preconditions are used to determine
if a policy rule should be applied by evaluating
a set of conditions. The declaration can contain
nested `any` or `all` statements. See: https://kyverno.io/docs/writing-policies/preconditions/'
properties:
all:
description: AllConditions enable variable-based
conditional rule execution. This is useful for
finer control of when an rule is applied. A
condition can reference object data using JMESPath
notation. Here, all of the conditions need to
pass
items:
description: Condition defines variable-based
conditional criteria for rule execution.
properties:
key:
description: Key is the context entry (using
JMESPath) for conditional rule evaluation.
x-kubernetes-preserve-unknown-fields: true
operator:
description: Operator is the operation to
perform. Valid operators are Equals, NotEquals,
In and NotIn.
enum:
- Equals
- NotEquals
- In
- NotIn
- GreaterThanOrEquals
- GreaterThan
- LessThanOrEquals
- LessThan
- DurationGreaterThanOrEquals
- DurationGreaterThan
- DurationLessThanOrEquals
- DurationLessThan
type: string
value:
description: Value is the conditional value,
or set of values. The values can be fixed
set or can be variables declared using
using JMESPath.
x-kubernetes-preserve-unknown-fields: true
type: object
type: array
any:
description: AnyConditions enable variable-based
conditional rule execution. This is useful for
finer control of when an rule is applied. A
condition can reference object data using JMESPath
notation. Here, at least one of the conditions
need to pass
items:
description: Condition defines variable-based
conditional criteria for rule execution.
properties:
key:
description: Key is the context entry (using
JMESPath) for conditional rule evaluation.
x-kubernetes-preserve-unknown-fields: true
operator:
description: Operator is the operation to
perform. Valid operators are Equals, NotEquals,
In and NotIn.
enum:
- Equals
- NotEquals
- In
- NotIn
- GreaterThanOrEquals
- GreaterThan
- LessThanOrEquals
- LessThan
- DurationGreaterThanOrEquals
- DurationGreaterThan
- DurationLessThanOrEquals
- DurationLessThan
type: string
value:
description: Value is the conditional value,
or set of values. The values can be fixed
set or can be variables declared using
using JMESPath.
x-kubernetes-preserve-unknown-fields: true
type: object
type: array
type: object
x-kubernetes-preserve-unknown-fields: true
type: object
type: array
message:
description: Message specifies a custom message to be displayed
on failure.

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -405,7 +405,7 @@ type Mutation struct {
// ForEach applies policy rule changes to nested elements.
// +optional
ForEachMutation *ForEachMutation `json:"foreach,omitempty" yaml:"foreach,omitempty"`
ForEachMutation []*ForEachMutation `json:"foreach,omitempty" yaml:"foreach,omitempty"`
}
// ForEach applies policy rule changes to nested elements.
@ -460,7 +460,9 @@ type Validation struct {
// +optional
Message string `json:"message,omitempty" yaml:"message,omitempty"`
ForEachValidation *ForEachValidation `json:"foreach,omitempty" yaml:"foreach,omitempty"`
// ForEach applies policy rule changes to nested elements.
// +optional
ForEachValidation []*ForEachValidation `json:"foreach,omitempty" yaml:"foreach,omitempty"`
// Pattern specifies an overlay-style pattern used to check resources.
// +kubebuilder:validation:XPreserveUnknownFields

View file

@ -134,14 +134,6 @@ func (in *Validation) DeserializeAnyPattern() ([]interface{}, error) {
return res, nil
}
func (in *Validation) DeserializeForEachAnyPattern() ([]interface{}, error) {
if in.ForEachValidation.AnyPattern == nil {
return nil, nil
}
res, nil := deserializePattern(in.ForEachValidation.AnyPattern)
return res, nil
}
func deserializePattern(pattern apiextensions.JSON) ([]interface{}, error) {
anyPattern, err := json.Marshal(pattern)
if err != nil {

View file

@ -15,7 +15,7 @@ type Handler interface {
}
// CreateMutateHandler initilizes a new instance of mutation handler
func CreateMutateHandler(ruleName string, mutate *kyverno.Mutation, patchedResource unstructured.Unstructured, context context.EvalInterface, logger logr.Logger) Handler {
func CreateMutateHandler(ruleName string, mutate *kyverno.Mutation, patchedResource unstructured.Unstructured, context context.EvalInterface, logger logr.Logger, foreachIndex int) Handler {
switch {
case isPatchStrategicMerge(mutate):
@ -31,7 +31,7 @@ func CreateMutateHandler(ruleName string, mutate *kyverno.Mutation, patchedResou
case isPatches(mutate):
return newPatchesHandler(ruleName, mutate, patchedResource, context, logger)
case isForEach(mutate):
return newForEachHandler(ruleName, mutate, patchedResource, context, logger)
return newForEachHandler(ruleName, mutate, patchedResource, context, logger, foreachIndex)
default:
return newEmptyHandler(patchedResource)
}
@ -66,20 +66,22 @@ type forEachHandler struct {
patchedResource unstructured.Unstructured
evalCtx context.EvalInterface
logger logr.Logger
foreachIndex int
}
func newForEachHandler(ruleName string, mutate *kyverno.Mutation, patchedResource unstructured.Unstructured, context context.EvalInterface, logger logr.Logger) Handler {
func newForEachHandler(ruleName string, mutate *kyverno.Mutation, patchedResource unstructured.Unstructured, context context.EvalInterface, logger logr.Logger, foreachIndex int) Handler {
return forEachHandler{
ruleName: ruleName,
mutation: mutate,
patchedResource: patchedResource,
evalCtx: context,
logger: logger,
foreachIndex: foreachIndex,
}
}
func (h forEachHandler) Handle() (response.RuleResponse, unstructured.Unstructured) {
return ProcessStrategicMergePatch(h.ruleName, h.mutation.ForEachMutation.PatchStrategicMerge, h.patchedResource, h.logger)
return ProcessStrategicMergePatch(h.ruleName, h.mutation.ForEachMutation[h.foreachIndex].PatchStrategicMerge, h.patchedResource, h.logger)
}
// patchesJSON6902Handler

View file

@ -99,7 +99,7 @@ func Mutate(policyContext *PolicyContext) (resp *response.EngineResponse) {
if rule.Mutation.ForEachMutation != nil {
ruleResp, patchedResource = mutateForEachResource(ruleCopy, policyContext, patchedResource, logger)
} else {
err, mutateResp := mutateResource(ruleCopy, policyContext.JSONContext, patchedResource, logger)
err, mutateResp := mutateResource(ruleCopy, policyContext.JSONContext, patchedResource, logger, 0)
if err != nil {
if mutateResp.skip {
ruleResp = ruleResponse(&policy.Spec.Rules[i], utils.Mutation, err.Error(), response.RuleStatusSkip)
@ -132,56 +132,60 @@ func Mutate(policyContext *PolicyContext) (resp *response.EngineResponse) {
}
func mutateForEachResource(rule *kyverno.Rule, ctx *PolicyContext, resource unstructured.Unstructured, logger logr.Logger) (*response.RuleResponse, unstructured.Unstructured) {
foreach := rule.Mutation.ForEachMutation
if foreach == nil {
foreachList := rule.Mutation.ForEachMutation
if foreachList == nil {
return nil, resource
}
if err := LoadContext(logger, foreach.Context, ctx.ResourceCache, ctx, rule.Name); err != nil {
logger.Error(err, "failed to load context")
return ruleError(rule, utils.Mutation, "failed to load context", err), resource
}
preconditionsPassed, err := checkPreconditions(logger, ctx, foreach.AnyAllConditions)
if err != nil {
return ruleError(rule, utils.Mutation, "failed to evaluate preconditions", err), resource
} else if !preconditionsPassed {
return ruleResponse(rule, utils.Mutation, "preconditions not met", response.RuleStatusSkip), resource
}
elements, err := evaluateList(foreach.List, ctx.JSONContext)
if err != nil {
msg := fmt.Sprintf("failed to evaluate list %s", foreach.List)
return ruleError(rule, utils.Mutation, msg, err), resource
}
ctx.JSONContext.Checkpoint()
defer ctx.JSONContext.Restore()
applyCount := 0
patchedResource := resource
allPatches := make([][]byte, 0)
for _, e := range elements {
ctx.JSONContext.Reset()
ctx := ctx.Copy()
if err := addElementToContext(ctx, e); err != nil {
logger.Error(err, "failed to add element to context")
return ruleError(rule, utils.Mutation, "failed to process foreach", err), resource
for foreachIndex, foreach := range foreachList {
if err := LoadContext(logger, foreach.Context, ctx.ResourceCache, ctx, rule.Name); err != nil {
logger.Error(err, "failed to load context")
return ruleError(rule, utils.Mutation, "failed to load context", err), resource
}
var skip = false
err, mutateResp := mutateResource(rule, ctx.JSONContext, patchedResource, logger)
if err != nil && !skip {
return ruleResponse(rule, utils.Mutation, err.Error(), response.RuleStatusError), resource
preconditionsPassed, err := checkPreconditions(logger, ctx, foreach.AnyAllConditions)
if err != nil {
return ruleError(rule, utils.Mutation, "failed to evaluate preconditions", err), resource
} else if !preconditionsPassed {
return ruleResponse(rule, utils.Mutation, "preconditions not met", response.RuleStatusSkip), resource
}
patchedResource = mutateResp.patchedResource
if len(mutateResp.patches) > 0 {
allPatches = append(allPatches, mutateResp.patches...)
elements, err := evaluateList(foreach.List, ctx.JSONContext)
if err != nil {
msg := fmt.Sprintf("failed to evaluate list %s", foreach.List)
return ruleError(rule, utils.Mutation, msg, err), resource
}
applyCount++
ctx.JSONContext.Checkpoint()
defer ctx.JSONContext.Restore()
for _, e := range elements {
ctx.JSONContext.Reset()
ctx := ctx.Copy()
if err := addElementToContext(ctx, e); err != nil {
logger.Error(err, "failed to add element to context")
return ruleError(rule, utils.Mutation, "failed to process foreach", err), resource
}
var skip = false
err, mutateResp := mutateResource(rule, ctx.JSONContext, patchedResource, logger, foreachIndex)
if err != nil && !skip {
return ruleResponse(rule, utils.Mutation, err.Error(), response.RuleStatusError), resource
}
patchedResource = mutateResp.patchedResource
if len(mutateResp.patches) > 0 {
allPatches = append(allPatches, mutateResp.patches...)
}
applyCount++
}
}
if applyCount == 0 {
@ -200,8 +204,12 @@ type mutateResponse struct {
message string
}
func mutateResource(rule *kyverno.Rule, ctx *context.Context, resource unstructured.Unstructured, logger logr.Logger) (error, *mutateResponse) {
func mutateResource(rule *kyverno.Rule, ctx *context.Context, resource unstructured.Unstructured, logger logr.Logger, foreachIndex int) (error, *mutateResponse) {
mutateResp := &mutateResponse{false, unstructured.Unstructured{}, nil, ""}
// Pre-conditions checks for the list of foreach rules should ideally be performed once.
// Currently they are performed for each entry in the foreach list.
// Also, the foreach index parameter should be removed and a set of patches should be passed in.
anyAllConditions, err := variables.SubstituteAllInPreconditions(logger, ctx, rule.AnyAllConditions)
if err != nil {
return errors.Wrapf(err, "failed to substitute vars in preconditions"), mutateResp
@ -222,8 +230,9 @@ func mutateResource(rule *kyverno.Rule, ctx *context.Context, resource unstructu
}
mutation := updatedRule.Mutation.DeepCopy()
mutateHandler := mutate.CreateMutateHandler(updatedRule.Name, mutation, resource, ctx, logger)
mutateHandler := mutate.CreateMutateHandler(updatedRule.Name, mutation, resource, ctx, logger, foreachIndex)
resp, patchedResource := mutateHandler.Handle()
if resp.Status == response.RuleStatusPass {
// - overlay pattern does not match the resource conditions
if resp.Patches == nil {

View file

@ -2,10 +2,11 @@ package engine
import (
"encoding/json"
"github.com/kyverno/kyverno/pkg/engine/response"
"reflect"
"testing"
"github.com/kyverno/kyverno/pkg/engine/response"
kyverno "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/engine/context"
"github.com/kyverno/kyverno/pkg/engine/utils"
@ -561,42 +562,44 @@ func Test_nonZeroIndexNumberPatchesJson6902(t *testing.T) {
func Test_foreach(t *testing.T) {
policyRaw := []byte(`{
"apiVersion": "kyverno.io/v1",
"kind": "ClusterPolicy",
"metadata": {
"name": "replace-image-registry"
},
"spec": {
"background": false,
"rules": [
{
"name": "replace-image-registry",
"match": {
"resources": {
"kinds": [
"Pod"
"apiVersion": "kyverno.io/v1",
"kind": "ClusterPolicy",
"metadata": {
"name": "replace-image-registry"
},
"spec": {
"background": false,
"rules": [
{
"name": "replace-image-registry",
"match": {
"resources": {
"kinds": [
"Pod"
]
}
},
"mutate": {
"foreach": [
{
"list": "request.object.spec.containers",
"patchStrategicMerge": {
"spec": {
"containers": [
{
"name": "{{ element.name }}",
"image": "registry.io/{{images.containers.{{element.name}}.path}}:{{images.containers.{{element.name}}.tag}}"
}
]
}
}
}
]
}
},
"mutate": {
"foreach": {
"list": "request.object.spec.containers",
"patchStrategicMerge": {
"spec": {
"containers": [
{
"name": "{{ element.name }}",
"image": "registry.io/{{images.containers.{{element.name}}.path}}:{{images.containers.{{element.name}}.tag}}"
}
]
}
}
}
}
}
]
}
}`)
]
}
}`)
resourceRaw := []byte(`{
"apiVersion": "v1",
"kind": "Pod",

View file

@ -157,25 +157,28 @@ func newValidator(log logr.Logger, ctx *PolicyContext, rule *kyverno.Rule) *vali
}
}
func newForeachValidator(log logr.Logger, ctx *PolicyContext, rule *kyverno.Rule) *validator {
func newForeachValidator(log logr.Logger, ctx *PolicyContext, rule *kyverno.Rule) []*validator {
ruleCopy := rule.DeepCopy()
val := []*validator{}
for _, foreach := range ruleCopy.Validation.ForEachValidation {
anyAllConditions, err := common.ToMap(foreach.AnyAllConditions)
if err != nil {
log.Error(err, "failed to convert ruleCopy.Validation.ForEachValidation.AnyAllConditions")
}
temp := validator{
log: log,
ctx: ctx,
rule: ruleCopy,
contextEntries: foreach.Context,
anyAllConditions: anyAllConditions,
pattern: foreach.Pattern,
anyPattern: foreach.AnyPattern,
deny: foreach.Deny,
}
val = append(val, &temp)
}
// Variable substitution expects JSON data, so we convert to a map
anyAllConditions, err := common.ToMap(ruleCopy.Validation.ForEachValidation.AnyAllConditions)
if err != nil {
log.Error(err, "failed to convert ruleCopy.Validation.ForEachValidation.AnyAllConditions")
}
return &validator{
log: log,
ctx: ctx,
rule: ruleCopy,
contextEntries: ruleCopy.Validation.ForEachValidation.Context,
anyAllConditions: anyAllConditions,
pattern: ruleCopy.Validation.ForEachValidation.Pattern,
anyPattern: ruleCopy.Validation.ForEachValidation.AnyPattern,
deny: ruleCopy.Validation.ForEachValidation.Deny,
}
return val
}
func (v *validator) validate() *response.RuleResponse {
@ -219,44 +222,48 @@ func (v *validator) validateForEach() *response.RuleResponse {
return ruleResponse(v.rule, utils.Validation, "preconditions not met", response.RuleStatusSkip)
}
foreach := v.rule.Validation.ForEachValidation
if foreach == nil {
foreachList := v.rule.Validation.ForEachValidation
applyCount := 0
if foreachList == nil {
return nil
}
elements, err := evaluateList(foreach.List, v.ctx.JSONContext)
if err != nil {
msg := fmt.Sprintf("failed to evaluate list %s", foreach.List)
return ruleError(v.rule, utils.Validation, msg, err)
}
v.ctx.JSONContext.Checkpoint()
defer v.ctx.JSONContext.Restore()
applyCount := 0
for _, e := range elements {
v.ctx.JSONContext.Reset()
ctx := v.ctx.Copy()
if err := addElementToContext(ctx, e); err != nil {
v.log.Error(err, "failed to add element to context")
return ruleError(v.rule, utils.Validation, "failed to process foreach", err)
for _, foreach := range foreachList {
elements, err := evaluateList(foreach.List, v.ctx.JSONContext)
if err != nil {
msg := fmt.Sprintf("failed to evaluate list %s", foreach.List)
return ruleError(v.rule, utils.Validation, msg, err)
}
foreachValidator := newForeachValidator(v.log, ctx, v.rule)
r := foreachValidator.validate()
if r == nil {
v.log.Info("skipping rule due to empty result")
continue
} else if r.Status == response.RuleStatusSkip {
v.log.Info("skipping rule as preconditions were not met")
continue
} else if r.Status != response.RuleStatusPass {
msg := fmt.Sprintf("validation failed in foreach rule for %v", r.Message)
return ruleResponse(v.rule, utils.Validation, msg, r.Status)
}
v.ctx.JSONContext.Checkpoint()
defer v.ctx.JSONContext.Restore()
applyCount++
for _, e := range elements {
v.ctx.JSONContext.Reset()
ctx := v.ctx.Copy()
if err := addElementToContext(ctx, e); err != nil {
v.log.Error(err, "failed to add element to context")
return ruleError(v.rule, utils.Validation, "failed to process foreach", err)
}
foreachValidatorList := newForeachValidator(v.log, ctx, v.rule)
for _, foreachValidator := range foreachValidatorList {
r := foreachValidator.validate()
if r == nil {
v.log.Info("skipping rule due to empty result")
continue
} else if r.Status == response.RuleStatusSkip {
v.log.Info("skipping rule as preconditions were not met")
continue
} else if r.Status != response.RuleStatusPass {
msg := fmt.Sprintf("validation failed in foreach rule for %v", r.Message)
return ruleResponse(v.rule, utils.Validation, msg, r.Status)
}
applyCount++
}
}
}
if applyCount == 0 {

View file

@ -2,9 +2,10 @@ package engine
import (
"encoding/json"
"github.com/kyverno/kyverno/pkg/engine/response"
"testing"
"github.com/kyverno/kyverno/pkg/engine/response"
kyverno "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/engine/context"
"github.com/kyverno/kyverno/pkg/engine/utils"
@ -2435,20 +2436,34 @@ func Test_foreach_container_pass(t *testing.T) {
policyraw := []byte(`{
"apiVersion": "kyverno.io/v1",
"kind": "ClusterPolicy",
"metadata": {"name": "test"},
"metadata": {
"name": "test"
},
"spec": {
"rules": [
{
"name": "test-path-not-exist",
"match": {"resources": { "kinds": [ "Deployment" ] } },
"validate": {
"foreach": {
"list": "request.object.spec.template.spec.containers",
"pattern": {
"name": "*-valid"
}
"match": {
"resources": {
"kinds": [
"Deployment"
]
}
}}]}}`)
},
"validate": {
"foreach": [
{
"list": "request.object.spec.template.spec.containers",
"pattern": {
"name": "*-valid"
}
}
]
}
}
]
}
}`)
testForEach(t, policyraw, resourceRaw, "", response.RuleStatusPass)
}
@ -2476,12 +2491,14 @@ func Test_foreach_container_fail(t *testing.T) {
"name": "test",
"match": {"resources": { "kinds": [ "Deployment" ] } },
"validate": {
"foreach": {
"list": "request.object.spec.template.spec.containers",
"pattern": {
"name": "*-valid"
"foreach": [
{
"list": "request.object.spec.template.spec.containers",
"pattern": {
"name": "*-valid"
}
}
}
]
}}]}}`)
testForEach(t, policyraw, resourceRaw, "", response.RuleStatusFail)
@ -2503,22 +2520,40 @@ func Test_foreach_container_deny_fail(t *testing.T) {
policyraw := []byte(`{
"apiVersion": "kyverno.io/v1",
"kind": "ClusterPolicy",
"metadata": {"name": "test"},
"metadata": {
"name": "test"
},
"spec": {
"rules": [
{
"name": "test",
"match": {"resources": { "kinds": [ "Deployment" ] } },
"validate": {
"foreach": {
"list": "request.object.spec.template.spec.containers",
"deny": {
"conditions": [
{"key": "{{ regex_match('{{element.image}}', 'docker.io') }}", "operator": "Equals", "value": false}
]
}
"match": {
"resources": {
"kinds": [
"Deployment"
]
}
}}]}}`)
},
"validate": {
"foreach": [
{
"list": "request.object.spec.template.spec.containers",
"deny": {
"conditions": [
{
"key": "{{ regex_match('{{element.image}}', 'docker.io') }}",
"operator": "Equals",
"value": false
}
]
}
}
]
}
}
]
}
}`)
testForEach(t, policyraw, resourceRaw, "", response.RuleStatusFail)
}
@ -2546,14 +2581,20 @@ func Test_foreach_container_deny_success(t *testing.T) {
"name": "test",
"match": {"resources": { "kinds": [ "Deployment" ] } },
"validate": {
"foreach": {
"list": "request.object.spec.template.spec.containers",
"deny": {
"conditions": [
{"key": "{{ regex_match('{{element.image}}', 'docker.io') }}", "operator": "Equals", "value": false}
]
"foreach": [
{
"list": "request.object.spec.template.spec.containers",
"deny": {
"conditions": [
{
"key": "{{ regex_match('{{element.image}}', 'docker.io') }}",
"operator": "Equals",
"value": false
}
]
}
}
}
]
}}]}}`)
testForEach(t, policyraw, resourceRaw, "", response.RuleStatusFail)
@ -2575,22 +2616,40 @@ func Test_foreach_container_deny_error(t *testing.T) {
policyraw := []byte(`{
"apiVersion": "kyverno.io/v1",
"kind": "ClusterPolicy",
"metadata": {"name": "test"},
"metadata": {
"name": "test"
},
"spec": {
"rules": [
{
"name": "test",
"match": {"resources": { "kinds": [ "Deployment" ] } },
"validate": {
"foreach": {
"list": "request.object.spec.template.spec.containers",
"deny": {
"conditions": [
{"key": "{{ regex_match_INVALID('{{request.object.image}}', 'docker.io') }}", "operator": "Equals", "value": false}
]
}
"match": {
"resources": {
"kinds": [
"Deployment"
]
}
}}]}}`)
},
"validate": {
"foreach": [
{
"list": "request.object.spec.template.spec.containers",
"deny": {
"conditions": [
{
"key": "{{ regex_match_INVALID('{{request.object.image}}', 'docker.io') }}",
"operator": "Equals",
"value": false
}
]
}
}
]
}
}
]
}
}`)
testForEach(t, policyraw, resourceRaw, "", response.RuleStatusError)
}
@ -2611,30 +2670,60 @@ func Test_foreach_context_preconditions(t *testing.T) {
policyraw := []byte(`{
"apiVersion": "kyverno.io/v1",
"kind": "ClusterPolicy",
"metadata": {"name": "test"},
"metadata": {
"name": "test"
},
"spec": {
"rules": [
{
"name": "test",
"match": {"resources": { "kinds": [ "Deployment" ] } },
"validate": {
"foreach": {
"list": "request.object.spec.template.spec.containers",
"context": [{"name": "img", "configMap": {"name": "mycmap", "namespace": "default"}}],
"preconditions": { "all": [
{
"key": "{{element.name}}",
"operator": "In",
"value": ["podvalid"]
}
]},
"deny": {
"conditions": [
{"key": "{{ element.image }}", "operator": "NotEquals", "value": "{{ img.data.{{ element.name }} }}"}
]
}
"match": {
"resources": {
"kinds": [
"Deployment"
]
}
}}]}}`)
},
"validate": {
"foreach": [
{
"list": "request.object.spec.template.spec.containers",
"context": [
{
"name": "img",
"configMap": {
"name": "mycmap",
"namespace": "default"
}
}
],
"preconditions": {
"all": [
{
"key": "{{element.name}}",
"operator": "In",
"value": [
"podvalid"
]
}
]
},
"deny": {
"conditions": [
{
"key": "{{ element.image }}",
"operator": "NotEquals",
"value": "{{ img.data.{{ element.name }} }}"
}
]
}
}
]
}
}
]
}
}`)
configMapVariableContext := store.Context{
Policies: []store.Policy{
@ -2675,30 +2764,61 @@ func Test_foreach_context_preconditions_fail(t *testing.T) {
policyraw := []byte(`{
"apiVersion": "kyverno.io/v1",
"kind": "ClusterPolicy",
"metadata": {"name": "test"},
"metadata": {
"name": "test"
},
"spec": {
"rules": [
{
"name": "test",
"match": {"resources": { "kinds": [ "Deployment" ] } },
"validate": {
"foreach": {
"list": "request.object.spec.template.spec.containers",
"context": [{"name": "img", "configMap": {"name": "mycmap", "namespace": "default"}}],
"preconditions": { "all": [
{
"key": "{{element.name}}",
"operator": "In",
"value": ["podvalid", "podinvalid"]
}
]},
"deny": {
"conditions": [
{"key": "{{ element.image }}", "operator": "NotEquals", "value": "{{ img.data.{{ element.name }} }}"}
]
}
"match": {
"resources": {
"kinds": [
"Deployment"
]
}
}}]}}`)
},
"validate": {
"foreach": [
{
"list": "request.object.spec.template.spec.containers",
"context": [
{
"name": "img",
"configMap": {
"name": "mycmap",
"namespace": "default"
}
}
],
"preconditions": {
"all": [
{
"key": "{{element.name}}",
"operator": "In",
"value": [
"podvalid",
"podinvalid"
]
}
]
},
"deny": {
"conditions": [
{
"key": "{{ element.image }}",
"operator": "NotEquals",
"value": "{{ img.data.{{ element.name }} }}"
}
]
}
}
]
}
}
]
}
}`)
configMapVariableContext := store.Context{
Policies: []store.Policy{

View file

@ -50,8 +50,10 @@ func (v *Validate) Validate() (string, error) {
}
if v.rule.ForEachValidation != nil {
if err := v.validateForEach(v.rule.ForEachValidation); err != nil {
return "", err
for _, foreach := range v.rule.ForEachValidation {
if err := v.validateForEach(foreach); err != nil {
return "", err
}
}
}

View file

@ -95,15 +95,6 @@ func generateCronJobRule(rule kyverno.Rule, controllers string, log logr.Logger)
return *cronJobRule
}
if (jobRule.Validation != nil) && (jobRule.Validation.ForEachValidation != nil) && (jobRule.Validation.ForEachValidation.Pattern != nil) {
newValidate := &kyverno.Validation{
Message: variables.FindAndShiftReferences(log, rule.Validation.Message, "spec/jobTemplate/spec/template", "pattern"),
ForEachValidation: jobRule.Validation.ForEachValidation,
}
cronJobRule.Validation = newValidate.DeepCopy()
return *cronJobRule
}
if (jobRule.Validation != nil) && (jobRule.Validation.AnyPattern != nil) {
var patterns []interface{}
anyPatterns, err := jobRule.Validation.DeserializeAnyPattern()
@ -128,25 +119,36 @@ func generateCronJobRule(rule kyverno.Rule, controllers string, log logr.Logger)
return *cronJobRule
}
if (jobRule.Validation != nil) && (jobRule.Validation.ForEachValidation != nil) && (jobRule.Validation.ForEachValidation.AnyPattern != nil) {
if (jobRule.Validation != nil) && len(jobRule.Validation.ForEachValidation) > 0 && jobRule.Validation.ForEachValidation != nil {
newForeachValidate := make([]*kyverno.ForEachValidation, len(jobRule.Validation.ForEachValidation))
for i, foreach := range rule.Validation.ForEachValidation {
newForeachValidate[i] = foreach
}
cronJobRule.Validation = &kyverno.Validation{
Message: variables.FindAndShiftReferences(log, rule.Validation.Message, "spec/jobTemplate/spec/template", "anyPattern"),
ForEachValidation: jobRule.Validation.ForEachValidation,
Message: variables.FindAndShiftReferences(log, rule.Validation.Message, "spec/template", "pattern"),
ForEachValidation: newForeachValidate,
}
return *cronJobRule
}
if (jobRule.Validation != nil) && (jobRule.Validation.ForEachValidation != nil) && (jobRule.Validation.ForEachValidation.Deny != nil) {
cronJobRule.Validation = &kyverno.Validation{
Message: variables.FindAndShiftReferences(log, rule.Validation.Message, "spec/jobTemplate/spec/template", "anyPattern"),
ForEachValidation: jobRule.Validation.ForEachValidation,
}
return *cronJobRule
}
if (jobRule.Mutation != nil) && len(jobRule.Mutation.ForEachMutation) > 0 && jobRule.Mutation.ForEachMutation != nil {
if (jobRule.Mutation != nil) && (jobRule.Mutation.ForEachMutation != nil) && (jobRule.Mutation.ForEachMutation.PatchStrategicMerge != nil) {
var newForeachMutation []*kyverno.ForEachMutation
for _, foreach := range rule.Mutation.ForEachMutation {
newForeachMutation = append(newForeachMutation, &kyverno.ForEachMutation{
List: foreach.List,
Context: foreach.Context,
AnyAllConditions: foreach.AnyAllConditions,
PatchStrategicMerge: map[string]interface{}{
"spec": map[string]interface{}{
"jobTemplate": foreach.PatchStrategicMerge,
},
},
})
}
cronJobRule.Mutation = &kyverno.Mutation{
ForEachMutation: jobRule.Mutation.ForEachMutation,
ForEachMutation: newForeachMutation,
}
return *cronJobRule
}

View file

@ -567,6 +567,7 @@ func generateRulePatches(policy kyverno.ClusterPolicy, controllers string, log l
// handle CronJob, it appends an additional rule
genRule = generateCronJobRule(rule, controllers, log)
if !reflect.DeepEqual(genRule, kyvernoRule{}) {
pbytes := convertToPatches(genRule, patchPostion)
pbytes = updateGenRuleByte(pbytes, "Cronjob", genRule)
@ -724,20 +725,22 @@ func generateRuleForControllers(rule kyverno.Rule, controllers string, log logr.
return *controllerRule
}
if rule.Mutation.ForEachMutation != nil && rule.Mutation.ForEachMutation.PatchStrategicMerge != nil {
newForeachMutation := &kyverno.Mutation{
ForEachMutation: &kyverno.ForEachMutation{
List: rule.Mutation.ForEachMutation.List,
Context: rule.Mutation.ForEachMutation.Context,
AnyAllConditions: rule.Mutation.ForEachMutation.AnyAllConditions,
if len(rule.Mutation.ForEachMutation) > 0 && rule.Mutation.ForEachMutation != nil {
var newForeachMutation []*kyverno.ForEachMutation
for _, foreach := range rule.Mutation.ForEachMutation {
newForeachMutation = append(newForeachMutation, &kyverno.ForEachMutation{
List: foreach.List,
AnyAllConditions: foreach.AnyAllConditions,
PatchStrategicMerge: map[string]interface{}{
"spec": map[string]interface{}{
"template": rule.Mutation.ForEachMutation.PatchStrategicMerge,
"template": foreach.PatchStrategicMerge,
},
},
},
})
}
controllerRule.Mutation = &kyverno.Mutation{
ForEachMutation: newForeachMutation,
}
controllerRule.Mutation = newForeachMutation.DeepCopy()
return *controllerRule
}
@ -769,19 +772,14 @@ func generateRuleForControllers(rule kyverno.Rule, controllers string, log logr.
return *controllerRule
}
if rule.Validation.ForEachValidation != nil && rule.Validation.ForEachValidation.Pattern != nil {
newForeachValidate := &kyverno.Validation{
Message: variables.FindAndShiftReferences(log, rule.Validation.Message, "spec/template", "pattern"),
ForEachValidation: rule.Validation.ForEachValidation,
if len(rule.Validation.ForEachValidation) > 0 && rule.Validation.ForEachValidation != nil {
newForeachValidate := make([]*kyverno.ForEachValidation, len(rule.Validation.ForEachValidation))
for i, foreach := range rule.Validation.ForEachValidation {
newForeachValidate[i] = foreach
}
controllerRule.Validation = newForeachValidate.DeepCopy()
return *controllerRule
}
if rule.Validation.ForEachValidation != nil && rule.Validation.ForEachValidation.AnyPattern != nil {
controllerRule.Validation = &kyverno.Validation{
Message: variables.FindAndShiftReferences(log, rule.Validation.Message, "spec/template", "pattern"),
ForEachValidation: rule.Validation.ForEachValidation,
ForEachValidation: newForeachValidate,
}
return *controllerRule
}