mirror of
https://github.com/kyverno/kyverno.git
synced 2024-12-14 11:57:48 +00:00
Nested foreach (#5589)
* updated foreach logic and added tests Signed-off-by: Jim Bugwadia <jim@nirmata.com> * uncomment tests Signed-off-by: Jim Bugwadia <jim@nirmata.com> * fix vars and unit tests Signed-off-by: Jim Bugwadia <jim@nirmata.com> * fix vars and unit tests Signed-off-by: Jim Bugwadia <jim@nirmata.com> * fix some tests Signed-off-by: Jim Bugwadia <jim@nirmata.com> * fix more tests Signed-off-by: Jim Bugwadia <jim@nirmata.com> * format Signed-off-by: Jim Bugwadia <jim@nirmata.com> * make codegen Signed-off-by: Jim Bugwadia <jim@nirmata.com> * linter Signed-off-by: Jim Bugwadia <jim@nirmata.com> * cleanup Signed-off-by: Jim Bugwadia <jim@nirmata.com> * fix linter issue Signed-off-by: Jim Bugwadia <jim@nirmata.com> * revert local launch Signed-off-by: Jim Bugwadia <jim@nirmata.com> * propagate context Signed-off-by: Jim Bugwadia <jim@nirmata.com> * uncomment tests Signed-off-by: Jim Bugwadia <jim@nirmata.com> * fix propagation of registry client Signed-off-by: Jim Bugwadia <jim@nirmata.com> Signed-off-by: Jim Bugwadia <jim@nirmata.com> Co-authored-by: shuting <shuting@nirmata.com>
This commit is contained in:
parent
d36a42b815
commit
9d3b176def
38 changed files with 923 additions and 284 deletions
11
.vscode/launch.json
vendored
11
.vscode/launch.json
vendored
|
@ -11,6 +11,17 @@
|
||||||
"--kubeconfig=${userHome}/.kube/config",
|
"--kubeconfig=${userHome}/.kube/config",
|
||||||
"--serverIP=<local ip>:9443",
|
"--serverIP=<local ip>:9443",
|
||||||
],
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Launch CLI",
|
||||||
|
"type": "go",
|
||||||
|
"request": "launch",
|
||||||
|
"mode": "auto",
|
||||||
|
"program": "${workspaceFolder}/cmd/cli/kubectl-kyverno",
|
||||||
|
"args": [
|
||||||
|
"test",
|
||||||
|
"${workspaceFolder}/test/cli/",
|
||||||
|
],
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
|
@ -283,6 +283,10 @@ type ForEachMutation struct {
|
||||||
// See https://tools.ietf.org/html/rfc6902 and https://kubectl.docs.kubernetes.io/references/kustomize/patchesjson6902/.
|
// See https://tools.ietf.org/html/rfc6902 and https://kubectl.docs.kubernetes.io/references/kustomize/patchesjson6902/.
|
||||||
// +optional
|
// +optional
|
||||||
PatchesJSON6902 string `json:"patchesJson6902,omitempty" yaml:"patchesJson6902,omitempty"`
|
PatchesJSON6902 string `json:"patchesJson6902,omitempty" yaml:"patchesJson6902,omitempty"`
|
||||||
|
|
||||||
|
// Foreach declares a nested foreach iterator
|
||||||
|
// +optional
|
||||||
|
ForEachMutation *apiextv1.JSON `json:"foreach,omitempty" yaml:"foreach,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *ForEachMutation) GetPatchStrategicMerge() apiextensions.JSON {
|
func (m *ForEachMutation) GetPatchStrategicMerge() apiextensions.JSON {
|
||||||
|
@ -398,6 +402,14 @@ func (v *Validation) SetAnyPattern(in apiextensions.JSON) {
|
||||||
v.RawAnyPattern = ToJSON(in)
|
v.RawAnyPattern = ToJSON(in)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v *Validation) GetForeach() apiextensions.JSON {
|
||||||
|
return FromJSON(v.RawPattern)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Validation) SetForeach(in apiextensions.JSON) {
|
||||||
|
v.RawPattern = ToJSON(in)
|
||||||
|
}
|
||||||
|
|
||||||
// Deny specifies a list of conditions used to pass or fail a validation rule.
|
// Deny specifies a list of conditions used to pass or fail a validation rule.
|
||||||
type Deny struct {
|
type Deny struct {
|
||||||
// Multiple conditions can be declared under an `any` or `all` statement. A direct list
|
// Multiple conditions can be declared under an `any` or `all` statement. A direct list
|
||||||
|
@ -450,6 +462,10 @@ type ForEachValidation struct {
|
||||||
// Deny defines conditions used to pass or fail a validation rule.
|
// Deny defines conditions used to pass or fail a validation rule.
|
||||||
// +optional
|
// +optional
|
||||||
Deny *Deny `json:"deny,omitempty" yaml:"deny,omitempty"`
|
Deny *Deny `json:"deny,omitempty" yaml:"deny,omitempty"`
|
||||||
|
|
||||||
|
// Foreach declares a nested foreach iterator
|
||||||
|
// +optional
|
||||||
|
ForEachValidation *apiextv1.JSON `json:"foreach,omitempty" yaml:"foreach,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *ForEachValidation) GetPattern() apiextensions.JSON {
|
func (v *ForEachValidation) GetPattern() apiextensions.JSON {
|
||||||
|
|
|
@ -472,6 +472,11 @@ func (in *ForEachMutation) DeepCopyInto(out *ForEachMutation) {
|
||||||
*out = new(apiextensionsv1.JSON)
|
*out = new(apiextensionsv1.JSON)
|
||||||
(*in).DeepCopyInto(*out)
|
(*in).DeepCopyInto(*out)
|
||||||
}
|
}
|
||||||
|
if in.ForEachMutation != nil {
|
||||||
|
in, out := &in.ForEachMutation, &out.ForEachMutation
|
||||||
|
*out = new(apiextensionsv1.JSON)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ForEachMutation.
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ForEachMutation.
|
||||||
|
@ -519,6 +524,11 @@ func (in *ForEachValidation) DeepCopyInto(out *ForEachValidation) {
|
||||||
*out = new(Deny)
|
*out = new(Deny)
|
||||||
(*in).DeepCopyInto(*out)
|
(*in).DeepCopyInto(*out)
|
||||||
}
|
}
|
||||||
|
if in.ForEachValidation != nil {
|
||||||
|
in, out := &in.ForEachValidation, &out.ForEachValidation
|
||||||
|
*out = new(apiextensionsv1.JSON)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ForEachValidation.
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ForEachValidation.
|
||||||
|
|
|
@ -3489,6 +3489,9 @@ spec:
|
||||||
type: object
|
type: object
|
||||||
type: object
|
type: object
|
||||||
type: array
|
type: array
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied.
|
description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied.
|
||||||
type: string
|
type: string
|
||||||
|
@ -3693,6 +3696,9 @@ spec:
|
||||||
elementScope:
|
elementScope:
|
||||||
description: ElementScope specifies whether to use the current list element as the scope for validation. Defaults to "true" if not specified. When set to "false", "request.object" is used as the validation scope within the foreach block to allow referencing other elements in the subtree.
|
description: ElementScope specifies whether to use the current list element as the scope for validation. Defaults to "true" if not specified. When set to "false", "request.object" is used as the validation scope within the foreach block to allow referencing other elements in the subtree.
|
||||||
type: boolean
|
type: boolean
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied.
|
description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied.
|
||||||
type: string
|
type: string
|
||||||
|
@ -5377,6 +5383,9 @@ spec:
|
||||||
type: object
|
type: object
|
||||||
type: object
|
type: object
|
||||||
type: array
|
type: array
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied.
|
description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied.
|
||||||
type: string
|
type: string
|
||||||
|
@ -5581,6 +5590,9 @@ spec:
|
||||||
elementScope:
|
elementScope:
|
||||||
description: ElementScope specifies whether to use the current list element as the scope for validation. Defaults to "true" if not specified. When set to "false", "request.object" is used as the validation scope within the foreach block to allow referencing other elements in the subtree.
|
description: ElementScope specifies whether to use the current list element as the scope for validation. Defaults to "true" if not specified. When set to "false", "request.object" is used as the validation scope within the foreach block to allow referencing other elements in the subtree.
|
||||||
type: boolean
|
type: boolean
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied.
|
description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied.
|
||||||
type: string
|
type: string
|
||||||
|
@ -7132,6 +7144,9 @@ spec:
|
||||||
type: object
|
type: object
|
||||||
type: object
|
type: object
|
||||||
type: array
|
type: array
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied.
|
description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied.
|
||||||
type: string
|
type: string
|
||||||
|
@ -7458,6 +7473,9 @@ spec:
|
||||||
elementScope:
|
elementScope:
|
||||||
description: ElementScope specifies whether to use the current list element as the scope for validation. Defaults to "true" if not specified. When set to "false", "request.object" is used as the validation scope within the foreach block to allow referencing other elements in the subtree.
|
description: ElementScope specifies whether to use the current list element as the scope for validation. Defaults to "true" if not specified. When set to "false", "request.object" is used as the validation scope within the foreach block to allow referencing other elements in the subtree.
|
||||||
type: boolean
|
type: boolean
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied.
|
description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied.
|
||||||
type: string
|
type: string
|
||||||
|
@ -9117,6 +9135,9 @@ spec:
|
||||||
type: object
|
type: object
|
||||||
type: object
|
type: object
|
||||||
type: array
|
type: array
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied.
|
description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied.
|
||||||
type: string
|
type: string
|
||||||
|
@ -9321,6 +9342,9 @@ spec:
|
||||||
elementScope:
|
elementScope:
|
||||||
description: ElementScope specifies whether to use the current list element as the scope for validation. Defaults to "true" if not specified. When set to "false", "request.object" is used as the validation scope within the foreach block to allow referencing other elements in the subtree.
|
description: ElementScope specifies whether to use the current list element as the scope for validation. Defaults to "true" if not specified. When set to "false", "request.object" is used as the validation scope within the foreach block to allow referencing other elements in the subtree.
|
||||||
type: boolean
|
type: boolean
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied.
|
description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied.
|
||||||
type: string
|
type: string
|
||||||
|
@ -11596,6 +11620,9 @@ spec:
|
||||||
type: object
|
type: object
|
||||||
type: object
|
type: object
|
||||||
type: array
|
type: array
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied.
|
description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied.
|
||||||
type: string
|
type: string
|
||||||
|
@ -11800,6 +11827,9 @@ spec:
|
||||||
elementScope:
|
elementScope:
|
||||||
description: ElementScope specifies whether to use the current list element as the scope for validation. Defaults to "true" if not specified. When set to "false", "request.object" is used as the validation scope within the foreach block to allow referencing other elements in the subtree.
|
description: ElementScope specifies whether to use the current list element as the scope for validation. Defaults to "true" if not specified. When set to "false", "request.object" is used as the validation scope within the foreach block to allow referencing other elements in the subtree.
|
||||||
type: boolean
|
type: boolean
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied.
|
description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied.
|
||||||
type: string
|
type: string
|
||||||
|
@ -13484,6 +13514,9 @@ spec:
|
||||||
type: object
|
type: object
|
||||||
type: object
|
type: object
|
||||||
type: array
|
type: array
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied.
|
description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied.
|
||||||
type: string
|
type: string
|
||||||
|
@ -13688,6 +13721,9 @@ spec:
|
||||||
elementScope:
|
elementScope:
|
||||||
description: ElementScope specifies whether to use the current list element as the scope for validation. Defaults to "true" if not specified. When set to "false", "request.object" is used as the validation scope within the foreach block to allow referencing other elements in the subtree.
|
description: ElementScope specifies whether to use the current list element as the scope for validation. Defaults to "true" if not specified. When set to "false", "request.object" is used as the validation scope within the foreach block to allow referencing other elements in the subtree.
|
||||||
type: boolean
|
type: boolean
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied.
|
description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied.
|
||||||
type: string
|
type: string
|
||||||
|
@ -15239,6 +15275,9 @@ spec:
|
||||||
type: object
|
type: object
|
||||||
type: object
|
type: object
|
||||||
type: array
|
type: array
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied.
|
description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied.
|
||||||
type: string
|
type: string
|
||||||
|
@ -15565,6 +15604,9 @@ spec:
|
||||||
elementScope:
|
elementScope:
|
||||||
description: ElementScope specifies whether to use the current list element as the scope for validation. Defaults to "true" if not specified. When set to "false", "request.object" is used as the validation scope within the foreach block to allow referencing other elements in the subtree.
|
description: ElementScope specifies whether to use the current list element as the scope for validation. Defaults to "true" if not specified. When set to "false", "request.object" is used as the validation scope within the foreach block to allow referencing other elements in the subtree.
|
||||||
type: boolean
|
type: boolean
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied.
|
description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied.
|
||||||
type: string
|
type: string
|
||||||
|
@ -17224,6 +17266,9 @@ spec:
|
||||||
type: object
|
type: object
|
||||||
type: object
|
type: object
|
||||||
type: array
|
type: array
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied.
|
description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied.
|
||||||
type: string
|
type: string
|
||||||
|
@ -17428,6 +17473,9 @@ spec:
|
||||||
elementScope:
|
elementScope:
|
||||||
description: ElementScope specifies whether to use the current list element as the scope for validation. Defaults to "true" if not specified. When set to "false", "request.object" is used as the validation scope within the foreach block to allow referencing other elements in the subtree.
|
description: ElementScope specifies whether to use the current list element as the scope for validation. Defaults to "true" if not specified. When set to "false", "request.object" is used as the validation scope within the foreach block to allow referencing other elements in the subtree.
|
||||||
type: boolean
|
type: boolean
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied.
|
description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied.
|
||||||
type: string
|
type: string
|
||||||
|
|
|
@ -82,7 +82,7 @@ func Test_Apply(t *testing.T) {
|
||||||
expectedPolicyReports: []preport.PolicyReport{
|
expectedPolicyReports: []preport.PolicyReport{
|
||||||
{
|
{
|
||||||
Summary: preport.PolicyReportSummary{
|
Summary: preport.PolicyReportSummary{
|
||||||
Pass: 9,
|
Pass: 6,
|
||||||
Fail: 0,
|
Fail: 0,
|
||||||
Skip: 0,
|
Skip: 0,
|
||||||
Error: 0,
|
Error: 0,
|
||||||
|
|
|
@ -1729,6 +1729,9 @@ spec:
|
||||||
type: object
|
type: object
|
||||||
type: object
|
type: object
|
||||||
type: array
|
type: array
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression
|
description: List specifies a JMESPath expression
|
||||||
that results in one or more elements to which the
|
that results in one or more elements to which the
|
||||||
|
@ -2042,6 +2045,9 @@ spec:
|
||||||
scope within the foreach block to allow referencing
|
scope within the foreach block to allow referencing
|
||||||
other elements in the subtree.
|
other elements in the subtree.
|
||||||
type: boolean
|
type: boolean
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression
|
description: List specifies a JMESPath expression
|
||||||
that results in one or more elements to which the
|
that results in one or more elements to which the
|
||||||
|
@ -4815,6 +4821,10 @@ spec:
|
||||||
type: object
|
type: object
|
||||||
type: object
|
type: object
|
||||||
type: array
|
type: array
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach
|
||||||
|
iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression
|
description: List specifies a JMESPath expression
|
||||||
that results in one or more elements to which
|
that results in one or more elements to which
|
||||||
|
@ -5140,6 +5150,10 @@ spec:
|
||||||
as the validation scope within the foreach block
|
as the validation scope within the foreach block
|
||||||
to allow referencing other elements in the subtree.
|
to allow referencing other elements in the subtree.
|
||||||
type: boolean
|
type: boolean
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach
|
||||||
|
iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression
|
description: List specifies a JMESPath expression
|
||||||
that results in one or more elements to which
|
that results in one or more elements to which
|
||||||
|
@ -7631,6 +7645,9 @@ spec:
|
||||||
type: object
|
type: object
|
||||||
type: object
|
type: object
|
||||||
type: array
|
type: array
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression
|
description: List specifies a JMESPath expression
|
||||||
that results in one or more elements to which the
|
that results in one or more elements to which the
|
||||||
|
@ -8112,6 +8129,9 @@ spec:
|
||||||
scope within the foreach block to allow referencing
|
scope within the foreach block to allow referencing
|
||||||
other elements in the subtree.
|
other elements in the subtree.
|
||||||
type: boolean
|
type: boolean
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression
|
description: List specifies a JMESPath expression
|
||||||
that results in one or more elements to which the
|
that results in one or more elements to which the
|
||||||
|
@ -10845,6 +10865,10 @@ spec:
|
||||||
type: object
|
type: object
|
||||||
type: object
|
type: object
|
||||||
type: array
|
type: array
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach
|
||||||
|
iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression
|
description: List specifies a JMESPath expression
|
||||||
that results in one or more elements to which
|
that results in one or more elements to which
|
||||||
|
@ -11170,6 +11194,10 @@ spec:
|
||||||
as the validation scope within the foreach block
|
as the validation scope within the foreach block
|
||||||
to allow referencing other elements in the subtree.
|
to allow referencing other elements in the subtree.
|
||||||
type: boolean
|
type: boolean
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach
|
||||||
|
iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression
|
description: List specifies a JMESPath expression
|
||||||
that results in one or more elements to which
|
that results in one or more elements to which
|
||||||
|
|
|
@ -1731,6 +1731,9 @@ spec:
|
||||||
type: object
|
type: object
|
||||||
type: object
|
type: object
|
||||||
type: array
|
type: array
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression
|
description: List specifies a JMESPath expression
|
||||||
that results in one or more elements to which the
|
that results in one or more elements to which the
|
||||||
|
@ -2044,6 +2047,9 @@ spec:
|
||||||
scope within the foreach block to allow referencing
|
scope within the foreach block to allow referencing
|
||||||
other elements in the subtree.
|
other elements in the subtree.
|
||||||
type: boolean
|
type: boolean
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression
|
description: List specifies a JMESPath expression
|
||||||
that results in one or more elements to which the
|
that results in one or more elements to which the
|
||||||
|
@ -4818,6 +4824,10 @@ spec:
|
||||||
type: object
|
type: object
|
||||||
type: object
|
type: object
|
||||||
type: array
|
type: array
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach
|
||||||
|
iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression
|
description: List specifies a JMESPath expression
|
||||||
that results in one or more elements to which
|
that results in one or more elements to which
|
||||||
|
@ -5143,6 +5153,10 @@ spec:
|
||||||
as the validation scope within the foreach block
|
as the validation scope within the foreach block
|
||||||
to allow referencing other elements in the subtree.
|
to allow referencing other elements in the subtree.
|
||||||
type: boolean
|
type: boolean
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach
|
||||||
|
iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression
|
description: List specifies a JMESPath expression
|
||||||
that results in one or more elements to which
|
that results in one or more elements to which
|
||||||
|
@ -7635,6 +7649,9 @@ spec:
|
||||||
type: object
|
type: object
|
||||||
type: object
|
type: object
|
||||||
type: array
|
type: array
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression
|
description: List specifies a JMESPath expression
|
||||||
that results in one or more elements to which the
|
that results in one or more elements to which the
|
||||||
|
@ -8116,6 +8133,9 @@ spec:
|
||||||
scope within the foreach block to allow referencing
|
scope within the foreach block to allow referencing
|
||||||
other elements in the subtree.
|
other elements in the subtree.
|
||||||
type: boolean
|
type: boolean
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression
|
description: List specifies a JMESPath expression
|
||||||
that results in one or more elements to which the
|
that results in one or more elements to which the
|
||||||
|
@ -10849,6 +10869,10 @@ spec:
|
||||||
type: object
|
type: object
|
||||||
type: object
|
type: object
|
||||||
type: array
|
type: array
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach
|
||||||
|
iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression
|
description: List specifies a JMESPath expression
|
||||||
that results in one or more elements to which
|
that results in one or more elements to which
|
||||||
|
@ -11174,6 +11198,10 @@ spec:
|
||||||
as the validation scope within the foreach block
|
as the validation scope within the foreach block
|
||||||
to allow referencing other elements in the subtree.
|
to allow referencing other elements in the subtree.
|
||||||
type: boolean
|
type: boolean
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach
|
||||||
|
iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression
|
description: List specifies a JMESPath expression
|
||||||
that results in one or more elements to which
|
that results in one or more elements to which
|
||||||
|
|
|
@ -5155,6 +5155,9 @@ spec:
|
||||||
type: object
|
type: object
|
||||||
type: object
|
type: object
|
||||||
type: array
|
type: array
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression
|
description: List specifies a JMESPath expression
|
||||||
that results in one or more elements to which the
|
that results in one or more elements to which the
|
||||||
|
@ -5468,6 +5471,9 @@ spec:
|
||||||
scope within the foreach block to allow referencing
|
scope within the foreach block to allow referencing
|
||||||
other elements in the subtree.
|
other elements in the subtree.
|
||||||
type: boolean
|
type: boolean
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression
|
description: List specifies a JMESPath expression
|
||||||
that results in one or more elements to which the
|
that results in one or more elements to which the
|
||||||
|
@ -8241,6 +8247,10 @@ spec:
|
||||||
type: object
|
type: object
|
||||||
type: object
|
type: object
|
||||||
type: array
|
type: array
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach
|
||||||
|
iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression
|
description: List specifies a JMESPath expression
|
||||||
that results in one or more elements to which
|
that results in one or more elements to which
|
||||||
|
@ -8566,6 +8576,10 @@ spec:
|
||||||
as the validation scope within the foreach block
|
as the validation scope within the foreach block
|
||||||
to allow referencing other elements in the subtree.
|
to allow referencing other elements in the subtree.
|
||||||
type: boolean
|
type: boolean
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach
|
||||||
|
iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression
|
description: List specifies a JMESPath expression
|
||||||
that results in one or more elements to which
|
that results in one or more elements to which
|
||||||
|
@ -11057,6 +11071,9 @@ spec:
|
||||||
type: object
|
type: object
|
||||||
type: object
|
type: object
|
||||||
type: array
|
type: array
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression
|
description: List specifies a JMESPath expression
|
||||||
that results in one or more elements to which the
|
that results in one or more elements to which the
|
||||||
|
@ -11538,6 +11555,9 @@ spec:
|
||||||
scope within the foreach block to allow referencing
|
scope within the foreach block to allow referencing
|
||||||
other elements in the subtree.
|
other elements in the subtree.
|
||||||
type: boolean
|
type: boolean
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression
|
description: List specifies a JMESPath expression
|
||||||
that results in one or more elements to which the
|
that results in one or more elements to which the
|
||||||
|
@ -14271,6 +14291,10 @@ spec:
|
||||||
type: object
|
type: object
|
||||||
type: object
|
type: object
|
||||||
type: array
|
type: array
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach
|
||||||
|
iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression
|
description: List specifies a JMESPath expression
|
||||||
that results in one or more elements to which
|
that results in one or more elements to which
|
||||||
|
@ -14596,6 +14620,10 @@ spec:
|
||||||
as the validation scope within the foreach block
|
as the validation scope within the foreach block
|
||||||
to allow referencing other elements in the subtree.
|
to allow referencing other elements in the subtree.
|
||||||
type: boolean
|
type: boolean
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach
|
||||||
|
iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression
|
description: List specifies a JMESPath expression
|
||||||
that results in one or more elements to which
|
that results in one or more elements to which
|
||||||
|
@ -18072,6 +18100,9 @@ spec:
|
||||||
type: object
|
type: object
|
||||||
type: object
|
type: object
|
||||||
type: array
|
type: array
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression
|
description: List specifies a JMESPath expression
|
||||||
that results in one or more elements to which the
|
that results in one or more elements to which the
|
||||||
|
@ -18385,6 +18416,9 @@ spec:
|
||||||
scope within the foreach block to allow referencing
|
scope within the foreach block to allow referencing
|
||||||
other elements in the subtree.
|
other elements in the subtree.
|
||||||
type: boolean
|
type: boolean
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression
|
description: List specifies a JMESPath expression
|
||||||
that results in one or more elements to which the
|
that results in one or more elements to which the
|
||||||
|
@ -21159,6 +21193,10 @@ spec:
|
||||||
type: object
|
type: object
|
||||||
type: object
|
type: object
|
||||||
type: array
|
type: array
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach
|
||||||
|
iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression
|
description: List specifies a JMESPath expression
|
||||||
that results in one or more elements to which
|
that results in one or more elements to which
|
||||||
|
@ -21484,6 +21522,10 @@ spec:
|
||||||
as the validation scope within the foreach block
|
as the validation scope within the foreach block
|
||||||
to allow referencing other elements in the subtree.
|
to allow referencing other elements in the subtree.
|
||||||
type: boolean
|
type: boolean
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach
|
||||||
|
iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression
|
description: List specifies a JMESPath expression
|
||||||
that results in one or more elements to which
|
that results in one or more elements to which
|
||||||
|
@ -23976,6 +24018,9 @@ spec:
|
||||||
type: object
|
type: object
|
||||||
type: object
|
type: object
|
||||||
type: array
|
type: array
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression
|
description: List specifies a JMESPath expression
|
||||||
that results in one or more elements to which the
|
that results in one or more elements to which the
|
||||||
|
@ -24457,6 +24502,9 @@ spec:
|
||||||
scope within the foreach block to allow referencing
|
scope within the foreach block to allow referencing
|
||||||
other elements in the subtree.
|
other elements in the subtree.
|
||||||
type: boolean
|
type: boolean
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression
|
description: List specifies a JMESPath expression
|
||||||
that results in one or more elements to which the
|
that results in one or more elements to which the
|
||||||
|
@ -27190,6 +27238,10 @@ spec:
|
||||||
type: object
|
type: object
|
||||||
type: object
|
type: object
|
||||||
type: array
|
type: array
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach
|
||||||
|
iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression
|
description: List specifies a JMESPath expression
|
||||||
that results in one or more elements to which
|
that results in one or more elements to which
|
||||||
|
@ -27515,6 +27567,10 @@ spec:
|
||||||
as the validation scope within the foreach block
|
as the validation scope within the foreach block
|
||||||
to allow referencing other elements in the subtree.
|
to allow referencing other elements in the subtree.
|
||||||
type: boolean
|
type: boolean
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach
|
||||||
|
iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression
|
description: List specifies a JMESPath expression
|
||||||
that results in one or more elements to which
|
that results in one or more elements to which
|
||||||
|
|
|
@ -5147,6 +5147,9 @@ spec:
|
||||||
type: object
|
type: object
|
||||||
type: object
|
type: object
|
||||||
type: array
|
type: array
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression
|
description: List specifies a JMESPath expression
|
||||||
that results in one or more elements to which the
|
that results in one or more elements to which the
|
||||||
|
@ -5460,6 +5463,9 @@ spec:
|
||||||
scope within the foreach block to allow referencing
|
scope within the foreach block to allow referencing
|
||||||
other elements in the subtree.
|
other elements in the subtree.
|
||||||
type: boolean
|
type: boolean
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression
|
description: List specifies a JMESPath expression
|
||||||
that results in one or more elements to which the
|
that results in one or more elements to which the
|
||||||
|
@ -8233,6 +8239,10 @@ spec:
|
||||||
type: object
|
type: object
|
||||||
type: object
|
type: object
|
||||||
type: array
|
type: array
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach
|
||||||
|
iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression
|
description: List specifies a JMESPath expression
|
||||||
that results in one or more elements to which
|
that results in one or more elements to which
|
||||||
|
@ -8558,6 +8568,10 @@ spec:
|
||||||
as the validation scope within the foreach block
|
as the validation scope within the foreach block
|
||||||
to allow referencing other elements in the subtree.
|
to allow referencing other elements in the subtree.
|
||||||
type: boolean
|
type: boolean
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach
|
||||||
|
iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression
|
description: List specifies a JMESPath expression
|
||||||
that results in one or more elements to which
|
that results in one or more elements to which
|
||||||
|
@ -11049,6 +11063,9 @@ spec:
|
||||||
type: object
|
type: object
|
||||||
type: object
|
type: object
|
||||||
type: array
|
type: array
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression
|
description: List specifies a JMESPath expression
|
||||||
that results in one or more elements to which the
|
that results in one or more elements to which the
|
||||||
|
@ -11530,6 +11547,9 @@ spec:
|
||||||
scope within the foreach block to allow referencing
|
scope within the foreach block to allow referencing
|
||||||
other elements in the subtree.
|
other elements in the subtree.
|
||||||
type: boolean
|
type: boolean
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression
|
description: List specifies a JMESPath expression
|
||||||
that results in one or more elements to which the
|
that results in one or more elements to which the
|
||||||
|
@ -14263,6 +14283,10 @@ spec:
|
||||||
type: object
|
type: object
|
||||||
type: object
|
type: object
|
||||||
type: array
|
type: array
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach
|
||||||
|
iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression
|
description: List specifies a JMESPath expression
|
||||||
that results in one or more elements to which
|
that results in one or more elements to which
|
||||||
|
@ -14588,6 +14612,10 @@ spec:
|
||||||
as the validation scope within the foreach block
|
as the validation scope within the foreach block
|
||||||
to allow referencing other elements in the subtree.
|
to allow referencing other elements in the subtree.
|
||||||
type: boolean
|
type: boolean
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach
|
||||||
|
iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression
|
description: List specifies a JMESPath expression
|
||||||
that results in one or more elements to which
|
that results in one or more elements to which
|
||||||
|
@ -18061,6 +18089,9 @@ spec:
|
||||||
type: object
|
type: object
|
||||||
type: object
|
type: object
|
||||||
type: array
|
type: array
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression
|
description: List specifies a JMESPath expression
|
||||||
that results in one or more elements to which the
|
that results in one or more elements to which the
|
||||||
|
@ -18374,6 +18405,9 @@ spec:
|
||||||
scope within the foreach block to allow referencing
|
scope within the foreach block to allow referencing
|
||||||
other elements in the subtree.
|
other elements in the subtree.
|
||||||
type: boolean
|
type: boolean
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression
|
description: List specifies a JMESPath expression
|
||||||
that results in one or more elements to which the
|
that results in one or more elements to which the
|
||||||
|
@ -21148,6 +21182,10 @@ spec:
|
||||||
type: object
|
type: object
|
||||||
type: object
|
type: object
|
||||||
type: array
|
type: array
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach
|
||||||
|
iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression
|
description: List specifies a JMESPath expression
|
||||||
that results in one or more elements to which
|
that results in one or more elements to which
|
||||||
|
@ -21473,6 +21511,10 @@ spec:
|
||||||
as the validation scope within the foreach block
|
as the validation scope within the foreach block
|
||||||
to allow referencing other elements in the subtree.
|
to allow referencing other elements in the subtree.
|
||||||
type: boolean
|
type: boolean
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach
|
||||||
|
iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression
|
description: List specifies a JMESPath expression
|
||||||
that results in one or more elements to which
|
that results in one or more elements to which
|
||||||
|
@ -23965,6 +24007,9 @@ spec:
|
||||||
type: object
|
type: object
|
||||||
type: object
|
type: object
|
||||||
type: array
|
type: array
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression
|
description: List specifies a JMESPath expression
|
||||||
that results in one or more elements to which the
|
that results in one or more elements to which the
|
||||||
|
@ -24446,6 +24491,9 @@ spec:
|
||||||
scope within the foreach block to allow referencing
|
scope within the foreach block to allow referencing
|
||||||
other elements in the subtree.
|
other elements in the subtree.
|
||||||
type: boolean
|
type: boolean
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression
|
description: List specifies a JMESPath expression
|
||||||
that results in one or more elements to which the
|
that results in one or more elements to which the
|
||||||
|
@ -27179,6 +27227,10 @@ spec:
|
||||||
type: object
|
type: object
|
||||||
type: object
|
type: object
|
||||||
type: array
|
type: array
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach
|
||||||
|
iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression
|
description: List specifies a JMESPath expression
|
||||||
that results in one or more elements to which
|
that results in one or more elements to which
|
||||||
|
@ -27504,6 +27556,10 @@ spec:
|
||||||
as the validation scope within the foreach block
|
as the validation scope within the foreach block
|
||||||
to allow referencing other elements in the subtree.
|
to allow referencing other elements in the subtree.
|
||||||
type: boolean
|
type: boolean
|
||||||
|
foreach:
|
||||||
|
description: Foreach declares a nested foreach
|
||||||
|
iterator
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
list:
|
list:
|
||||||
description: List specifies a JMESPath expression
|
description: List specifies a JMESPath expression
|
||||||
that results in one or more elements to which
|
that results in one or more elements to which
|
||||||
|
|
|
@ -1533,6 +1533,20 @@ string
|
||||||
See <a href="https://tools.ietf.org/html/rfc6902">https://tools.ietf.org/html/rfc6902</a> and <a href="https://kubectl.docs.kubernetes.io/references/kustomize/patchesjson6902/">https://kubectl.docs.kubernetes.io/references/kustomize/patchesjson6902/</a>.</p>
|
See <a href="https://tools.ietf.org/html/rfc6902">https://tools.ietf.org/html/rfc6902</a> and <a href="https://kubectl.docs.kubernetes.io/references/kustomize/patchesjson6902/">https://kubectl.docs.kubernetes.io/references/kustomize/patchesjson6902/</a>.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<code>foreach</code><br/>
|
||||||
|
<em>
|
||||||
|
<a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#json-v1-apiextensions">
|
||||||
|
Kubernetes apiextensions/v1.JSON
|
||||||
|
</a>
|
||||||
|
</em>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<em>(Optional)</em>
|
||||||
|
<p>Foreach declares a nested foreach iterator</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<hr />
|
<hr />
|
||||||
|
@ -1653,6 +1667,20 @@ Deny
|
||||||
<p>Deny defines conditions used to pass or fail a validation rule.</p>
|
<p>Deny defines conditions used to pass or fail a validation rule.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<code>foreach</code><br/>
|
||||||
|
<em>
|
||||||
|
<a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#json-v1-apiextensions">
|
||||||
|
Kubernetes apiextensions/v1.JSON
|
||||||
|
</a>
|
||||||
|
</em>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<em>(Optional)</em>
|
||||||
|
<p>Foreach declares a nested foreach iterator</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<hr />
|
<hr />
|
||||||
|
|
|
@ -45,14 +45,14 @@ var localSchemeBuilder = runtime.SchemeBuilder{
|
||||||
// AddToScheme adds all types of this clientset into the given scheme. This allows composition
|
// AddToScheme adds all types of this clientset into the given scheme. This allows composition
|
||||||
// of clientsets, like in:
|
// of clientsets, like in:
|
||||||
//
|
//
|
||||||
// import (
|
// import (
|
||||||
// "k8s.io/client-go/kubernetes"
|
// "k8s.io/client-go/kubernetes"
|
||||||
// clientsetscheme "k8s.io/client-go/kubernetes/scheme"
|
// clientsetscheme "k8s.io/client-go/kubernetes/scheme"
|
||||||
// aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme"
|
// aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme"
|
||||||
// )
|
// )
|
||||||
//
|
//
|
||||||
// kclientset, _ := kubernetes.NewForConfig(c)
|
// kclientset, _ := kubernetes.NewForConfig(c)
|
||||||
// _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme)
|
// _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme)
|
||||||
//
|
//
|
||||||
// After this, RawExtensions in Kubernetes types will serialize kube-aggregator types
|
// After this, RawExtensions in Kubernetes types will serialize kube-aggregator types
|
||||||
// correctly.
|
// correctly.
|
||||||
|
|
|
@ -45,14 +45,14 @@ var localSchemeBuilder = runtime.SchemeBuilder{
|
||||||
// AddToScheme adds all types of this clientset into the given scheme. This allows composition
|
// AddToScheme adds all types of this clientset into the given scheme. This allows composition
|
||||||
// of clientsets, like in:
|
// of clientsets, like in:
|
||||||
//
|
//
|
||||||
// import (
|
// import (
|
||||||
// "k8s.io/client-go/kubernetes"
|
// "k8s.io/client-go/kubernetes"
|
||||||
// clientsetscheme "k8s.io/client-go/kubernetes/scheme"
|
// clientsetscheme "k8s.io/client-go/kubernetes/scheme"
|
||||||
// aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme"
|
// aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme"
|
||||||
// )
|
// )
|
||||||
//
|
//
|
||||||
// kclientset, _ := kubernetes.NewForConfig(c)
|
// kclientset, _ := kubernetes.NewForConfig(c)
|
||||||
// _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme)
|
// _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme)
|
||||||
//
|
//
|
||||||
// After this, RawExtensions in Kubernetes types will serialize kube-aggregator types
|
// After this, RawExtensions in Kubernetes types will serialize kube-aggregator types
|
||||||
// correctly.
|
// correctly.
|
||||||
|
|
|
@ -227,7 +227,6 @@ var scanPredicate = `
|
||||||
`
|
`
|
||||||
|
|
||||||
func Test_Conditions(t *testing.T) {
|
func Test_Conditions(t *testing.T) {
|
||||||
|
|
||||||
conditions := []v1.AnyAllConditions{
|
conditions := []v1.AnyAllConditions{
|
||||||
{
|
{
|
||||||
AnyConditions: []v1.Condition{
|
AnyConditions: []v1.Condition{
|
||||||
|
|
|
@ -2,6 +2,7 @@ package context
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
@ -65,7 +66,7 @@ type Interface interface {
|
||||||
AddNamespace(namespace string) error
|
AddNamespace(namespace string) error
|
||||||
|
|
||||||
// AddElement adds element info to the context
|
// AddElement adds element info to the context
|
||||||
AddElement(data interface{}, index int) error
|
AddElement(data interface{}, index, nesting int) error
|
||||||
|
|
||||||
// AddImageInfo adds image info to the context
|
// AddImageInfo adds image info to the context
|
||||||
AddImageInfo(info apiutils.ImageInfo) error
|
AddImageInfo(info apiutils.ImageInfo) error
|
||||||
|
@ -239,10 +240,14 @@ func (ctx *context) AddNamespace(namespace string) error {
|
||||||
return addToContext(ctx, namespace, "request", "namespace")
|
return addToContext(ctx, namespace, "request", "namespace")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx *context) AddElement(data interface{}, index int) error {
|
func (ctx *context) AddElement(data interface{}, index, nesting int) error {
|
||||||
|
nestedElement := fmt.Sprintf("element%d", nesting)
|
||||||
|
nestedElementIndex := fmt.Sprintf("elementIndex%d", nesting)
|
||||||
data = map[string]interface{}{
|
data = map[string]interface{}{
|
||||||
"element": data,
|
"element": data,
|
||||||
"elementIndex": index,
|
nestedElement: data,
|
||||||
|
"elementIndex": index,
|
||||||
|
nestedElementIndex: index,
|
||||||
}
|
}
|
||||||
return addToContext(ctx, data)
|
return addToContext(ctx, data)
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,7 +51,8 @@ func Test_addResourceAndUserContext(t *testing.T) {
|
||||||
userRequestInfo := urkyverno.RequestInfo{
|
userRequestInfo := urkyverno.RequestInfo{
|
||||||
Roles: nil,
|
Roles: nil,
|
||||||
ClusterRoles: nil,
|
ClusterRoles: nil,
|
||||||
AdmissionUserInfo: userInfo}
|
AdmissionUserInfo: userInfo,
|
||||||
|
}
|
||||||
|
|
||||||
var expectedResult string
|
var expectedResult string
|
||||||
ctx := NewContext()
|
ctx := NewContext()
|
||||||
|
|
|
@ -3,12 +3,16 @@ package engine
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/go-logr/logr"
|
||||||
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
|
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
|
||||||
"github.com/kyverno/kyverno/pkg/engine/context"
|
"github.com/kyverno/kyverno/pkg/engine/context"
|
||||||
"github.com/kyverno/kyverno/pkg/engine/mutate"
|
"github.com/kyverno/kyverno/pkg/engine/mutate"
|
||||||
"github.com/kyverno/kyverno/pkg/engine/response"
|
"github.com/kyverno/kyverno/pkg/engine/response"
|
||||||
"github.com/kyverno/kyverno/pkg/engine/variables"
|
"github.com/kyverno/kyverno/pkg/engine/variables"
|
||||||
"github.com/kyverno/kyverno/pkg/logging"
|
"github.com/kyverno/kyverno/pkg/logging"
|
||||||
|
"github.com/kyverno/kyverno/pkg/utils/api"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -34,30 +38,53 @@ func ForceMutate(ctx context.Interface, policy kyvernov1.PolicyInterface, resour
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.Mutation.ForEachMutation != nil {
|
if r.Mutation.ForEachMutation != nil {
|
||||||
for i, foreach := range r.Mutation.ForEachMutation {
|
patchedResource, err = applyForeachMutate(r.Name, r.Mutation.ForEachMutation, patchedResource, ctx, logger)
|
||||||
patcher := mutate.NewPatcher(r.Name, foreach.GetPatchStrategicMerge(), foreach.PatchesJSON6902, patchedResource, ctx, logger)
|
if err != nil {
|
||||||
resp, mutatedResource := patcher.Patch()
|
return patchedResource, err
|
||||||
if resp.Status != response.RuleStatusPass {
|
|
||||||
return patchedResource, fmt.Errorf("foreach mutate result %q at index %d: %s", resp.Status.String(), i, resp.Message)
|
|
||||||
}
|
|
||||||
|
|
||||||
patchedResource = mutatedResource
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
m := r.Mutation
|
m := r.Mutation
|
||||||
patcher := mutate.NewPatcher(r.Name, m.GetPatchStrategicMerge(), m.PatchesJSON6902, patchedResource, ctx, logger)
|
patchedResource, err = applyPatches(r.Name, m.GetPatchStrategicMerge(), m.PatchesJSON6902, patchedResource, ctx, logger)
|
||||||
resp, mutatedResource := patcher.Patch()
|
if err != nil {
|
||||||
if resp.Status != response.RuleStatusPass {
|
return patchedResource, err
|
||||||
return patchedResource, fmt.Errorf("mutate result %q: %s", resp.Status.String(), resp.Message)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
patchedResource = mutatedResource
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return patchedResource, nil
|
return patchedResource, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func applyForeachMutate(name string, foreach []kyvernov1.ForEachMutation, resource unstructured.Unstructured, ctx context.Interface, logger logr.Logger) (patchedResource unstructured.Unstructured, err error) {
|
||||||
|
patchedResource = resource
|
||||||
|
for _, fe := range foreach {
|
||||||
|
if fe.ForEachMutation != nil {
|
||||||
|
nestedForeach, err := api.DeserializeJSONArray[kyvernov1.ForEachMutation](fe.ForEachMutation)
|
||||||
|
if err != nil {
|
||||||
|
return patchedResource, errors.Wrapf(err, "failed to deserialize foreach")
|
||||||
|
}
|
||||||
|
|
||||||
|
return applyForeachMutate(name, nestedForeach, patchedResource, ctx, logger)
|
||||||
|
}
|
||||||
|
|
||||||
|
patchedResource, err = applyPatches(name, fe.GetPatchStrategicMerge(), fe.PatchesJSON6902, patchedResource, ctx, logger)
|
||||||
|
if err != nil {
|
||||||
|
return resource, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return patchedResource, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyPatches(name string, mergePatch apiextensions.JSON, jsonPatch string, resource unstructured.Unstructured, ctx context.Interface, logger logr.Logger) (unstructured.Unstructured, error) {
|
||||||
|
patcher := mutate.NewPatcher(name, mergePatch, jsonPatch, resource, ctx, logger)
|
||||||
|
resp, mutatedResource := patcher.Patch()
|
||||||
|
if resp.Status != response.RuleStatusPass {
|
||||||
|
return mutatedResource, fmt.Errorf("mutate status %q: %s", resp.Status.String(), resp.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
return mutatedResource, nil
|
||||||
|
}
|
||||||
|
|
||||||
// removeConditions mutates the rule to remove AnyAllConditions
|
// removeConditions mutates the rule to remove AnyAllConditions
|
||||||
func removeConditions(rule *kyvernov1.Rule) {
|
func removeConditions(rule *kyvernov1.Rule) {
|
||||||
if rule.GetAnyAllConditions() != nil {
|
if rule.GetAnyAllConditions() != nil {
|
||||||
|
|
|
@ -171,7 +171,6 @@ func Test_CosignMockAttest_fail(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildContext(t *testing.T, policy, resource string, oldResource string) *PolicyContext {
|
func buildContext(t *testing.T, policy, resource string, oldResource string) *PolicyContext {
|
||||||
|
|
||||||
var cpol kyverno.ClusterPolicy
|
var cpol kyverno.ClusterPolicy
|
||||||
err := json.Unmarshal([]byte(policy), &cpol)
|
err := json.Unmarshal([]byte(policy), &cpol)
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
|
@ -407,8 +406,10 @@ var testConfigMapMissingResource = `{
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
|
|
||||||
var testVerifyImageKey = `-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8nXRh950IZbRj8Ra/N9sbqOPZrfM5/KAQN0/KjHcorm/J5yctVd7iEcnessRQjU917hmKO6JWVGHpDguIyakZA==\n-----END PUBLIC KEY-----\n`
|
var (
|
||||||
var testOtherKey = `-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEpNlOGZ323zMlhs4bcKSpAKQvbcWi5ZLRmijm6SqXDy0Fp0z0Eal+BekFnLzs8rUXUaXlhZ3hNudlgFJH+nFNMw==\n-----END PUBLIC KEY-----\n`
|
testVerifyImageKey = `-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8nXRh950IZbRj8Ra/N9sbqOPZrfM5/KAQN0/KjHcorm/J5yctVd7iEcnessRQjU917hmKO6JWVGHpDguIyakZA==\n-----END PUBLIC KEY-----\n`
|
||||||
|
testOtherKey = `-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEpNlOGZ323zMlhs4bcKSpAKQvbcWi5ZLRmijm6SqXDy0Fp0z0Eal+BekFnLzs8rUXUaXlhZ3hNudlgFJH+nFNMw==\n-----END PUBLIC KEY-----\n`
|
||||||
|
)
|
||||||
|
|
||||||
func Test_ConfigMapMissingSuccess(t *testing.T) {
|
func Test_ConfigMapMissingSuccess(t *testing.T) {
|
||||||
policyContext := buildContext(t, testConfigMapMissing, testConfigMapMissingResource, "")
|
policyContext := buildContext(t, testConfigMapMissing, testConfigMapMissingResource, "")
|
||||||
|
@ -744,7 +745,7 @@ func Test_MarkImageVerified(t *testing.T) {
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
assert.Equal(t, len(patches), 2)
|
assert.Equal(t, len(patches), 2)
|
||||||
|
|
||||||
resource := applyPatches(t, patches)
|
resource := testApplyPatches(t, patches)
|
||||||
patchedAnnotations := resource.GetAnnotations()
|
patchedAnnotations := resource.GetAnnotations()
|
||||||
assert.Equal(t, len(patchedAnnotations), 1)
|
assert.Equal(t, len(patchedAnnotations), 1)
|
||||||
|
|
||||||
|
@ -756,7 +757,7 @@ func Test_MarkImageVerified(t *testing.T) {
|
||||||
assert.Equal(t, verified, true)
|
assert.Equal(t, verified, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyPatches(t *testing.T, patches [][]byte) unstructured.Unstructured {
|
func testApplyPatches(t *testing.T, patches [][]byte) unstructured.Unstructured {
|
||||||
patchedResource, err := utils.ApplyPatches([]byte(testResource), patches)
|
patchedResource, err := utils.ApplyPatches([]byte(testResource), patches)
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
assert.Assert(t, patchedResource != nil)
|
assert.Assert(t, patchedResource != nil)
|
||||||
|
|
|
@ -1382,7 +1382,6 @@ func Test_Items(t *testing.T) {
|
||||||
}
|
}
|
||||||
for i, tc := range testCases {
|
for i, tc := range testCases {
|
||||||
t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) {
|
t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) {
|
||||||
|
|
||||||
query, err := New("items(`" + tc.object + "`,`" + tc.keyName + "`,`" + tc.valName + "`)")
|
query, err := New("items(`" + tc.object + "`,`" + tc.keyName + "`,`" + tc.valName + "`)")
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
|
|
||||||
|
|
|
@ -22,11 +22,11 @@ type Response struct {
|
||||||
Message string
|
Message string
|
||||||
}
|
}
|
||||||
|
|
||||||
func newErrorResponse(msg string, err error) *Response {
|
func NewErrorResponse(msg string, err error) *Response {
|
||||||
return newResponse(response.RuleStatusError, unstructured.Unstructured{}, nil, fmt.Sprintf("%s: %v", msg, err))
|
return NewResponse(response.RuleStatusError, unstructured.Unstructured{}, nil, fmt.Sprintf("%s: %v", msg, err))
|
||||||
}
|
}
|
||||||
|
|
||||||
func newResponse(status response.RuleStatus, resource unstructured.Unstructured, patches [][]byte, msg string) *Response {
|
func NewResponse(status response.RuleStatus, resource unstructured.Unstructured, patches [][]byte, msg string) *Response {
|
||||||
return &Response{
|
return &Response{
|
||||||
Status: status,
|
Status: status,
|
||||||
PatchedResource: resource,
|
PatchedResource: resource,
|
||||||
|
@ -38,56 +38,56 @@ func newResponse(status response.RuleStatus, resource unstructured.Unstructured,
|
||||||
func Mutate(rule *kyvernov1.Rule, ctx context.Interface, resource unstructured.Unstructured, logger logr.Logger) *Response {
|
func Mutate(rule *kyvernov1.Rule, ctx context.Interface, resource unstructured.Unstructured, logger logr.Logger) *Response {
|
||||||
updatedRule, err := variables.SubstituteAllInRule(logger, ctx, *rule)
|
updatedRule, err := variables.SubstituteAllInRule(logger, ctx, *rule)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return newErrorResponse("variable substitution failed", err)
|
return NewErrorResponse("variable substitution failed", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
m := updatedRule.Mutation
|
m := updatedRule.Mutation
|
||||||
patcher := NewPatcher(updatedRule.Name, m.GetPatchStrategicMerge(), m.PatchesJSON6902, resource, ctx, logger)
|
patcher := NewPatcher(updatedRule.Name, m.GetPatchStrategicMerge(), m.PatchesJSON6902, resource, ctx, logger)
|
||||||
if patcher == nil {
|
if patcher == nil {
|
||||||
return newResponse(response.RuleStatusError, resource, nil, "empty mutate rule")
|
return NewResponse(response.RuleStatusError, resource, nil, "empty mutate rule")
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, patchedResource := patcher.Patch()
|
resp, patchedResource := patcher.Patch()
|
||||||
if resp.Status != response.RuleStatusPass {
|
if resp.Status != response.RuleStatusPass {
|
||||||
return newResponse(resp.Status, resource, nil, resp.Message)
|
return NewResponse(resp.Status, resource, nil, resp.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
if resp.Patches == nil {
|
if resp.Patches == nil {
|
||||||
return newResponse(response.RuleStatusSkip, resource, nil, "no patches applied")
|
return NewResponse(response.RuleStatusSkip, resource, nil, "no patches applied")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := ctx.AddResource(patchedResource.Object); err != nil {
|
if err := ctx.AddResource(patchedResource.Object); err != nil {
|
||||||
return newErrorResponse("failed to update patched resource in the JSON context", err)
|
return NewErrorResponse("failed to update patched resource in the JSON context", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return newResponse(response.RuleStatusPass, patchedResource, resp.Patches, resp.Message)
|
return NewResponse(response.RuleStatusPass, patchedResource, resp.Patches, resp.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ForEach(name string, foreach kyvernov1.ForEachMutation, ctx context.Interface, resource unstructured.Unstructured, logger logr.Logger) *Response {
|
func ForEach(name string, foreach kyvernov1.ForEachMutation, ctx context.Interface, resource unstructured.Unstructured, logger logr.Logger) *Response {
|
||||||
fe, err := substituteAllInForEach(foreach, ctx, logger)
|
fe, err := substituteAllInForEach(foreach, ctx, logger)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return newErrorResponse("variable substitution failed", err)
|
return NewErrorResponse("variable substitution failed", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
patcher := NewPatcher(name, fe.GetPatchStrategicMerge(), fe.PatchesJSON6902, resource, ctx, logger)
|
patcher := NewPatcher(name, fe.GetPatchStrategicMerge(), fe.PatchesJSON6902, resource, ctx, logger)
|
||||||
if patcher == nil {
|
if patcher == nil {
|
||||||
return newResponse(response.RuleStatusError, unstructured.Unstructured{}, nil, "no patches found")
|
return NewResponse(response.RuleStatusError, unstructured.Unstructured{}, nil, "no patches found")
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, patchedResource := patcher.Patch()
|
resp, patchedResource := patcher.Patch()
|
||||||
if resp.Status != response.RuleStatusPass {
|
if resp.Status != response.RuleStatusPass {
|
||||||
return newResponse(resp.Status, unstructured.Unstructured{}, nil, resp.Message)
|
return NewResponse(resp.Status, unstructured.Unstructured{}, nil, resp.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
if resp.Patches == nil {
|
if resp.Patches == nil {
|
||||||
return newResponse(response.RuleStatusSkip, unstructured.Unstructured{}, nil, "no patches applied")
|
return NewResponse(response.RuleStatusSkip, unstructured.Unstructured{}, nil, "no patches applied")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := ctx.AddResource(patchedResource.Object); err != nil {
|
if err := ctx.AddResource(patchedResource.Object); err != nil {
|
||||||
return newErrorResponse("failed to update patched resource in the JSON context", err)
|
return NewErrorResponse("failed to update patched resource in the JSON context", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return newResponse(response.RuleStatusPass, patchedResource, resp.Patches, resp.Message)
|
return NewResponse(response.RuleStatusPass, patchedResource, resp.Patches, resp.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func substituteAllInForEach(fe kyvernov1.ForEachMutation, ctx context.Interface, logger logr.Logger) (*kyvernov1.ForEachMutation, error) {
|
func substituteAllInForEach(fe kyvernov1.ForEachMutation, ctx context.Interface, logger logr.Logger) (*kyvernov1.ForEachMutation, error) {
|
||||||
|
|
|
@ -68,7 +68,7 @@ func applyPatches(rule *types.Rule, resource unstructured.Unstructured) (*respon
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProcessPatches_EmptyPatches(t *testing.T) {
|
func TestProcessPatches_EmptyPatches(t *testing.T) {
|
||||||
var emptyRule = &types.Rule{Name: "emptyRule"}
|
emptyRule := &types.Rule{Name: "emptyRule"}
|
||||||
resourceUnstructured, err := utils.ConvertToUnstructured([]byte(endpointsDocument))
|
resourceUnstructured, err := utils.ConvertToUnstructured([]byte(endpointsDocument))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
|
|
|
@ -182,7 +182,6 @@ spec:
|
||||||
|
|
||||||
for _, testCase := range testCases {
|
for _, testCase := range testCases {
|
||||||
t.Run(testCase.testName, func(t *testing.T) {
|
t.Run(testCase.testName, func(t *testing.T) {
|
||||||
|
|
||||||
expectedBytes, err := yaml.YAMLToJSON(testCase.expected)
|
expectedBytes, err := yaml.YAMLToJSON(testCase.expected)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,6 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_GeneratePatches(t *testing.T) {
|
func Test_GeneratePatches(t *testing.T) {
|
||||||
|
|
||||||
out, err := strategicMergePatch(logging.GlobalLogger(), string(baseBytes), string(overlayBytes))
|
out, err := strategicMergePatch(logging.GlobalLogger(), string(baseBytes), string(overlayBytes))
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
|
|
||||||
|
@ -225,7 +224,6 @@ func Test_GeneratePatches_sortRemovalPatches(t *testing.T) {
|
||||||
fmt.Println(patches)
|
fmt.Println(patches)
|
||||||
assertnew.Nil(t, err)
|
assertnew.Nil(t, err)
|
||||||
assertnew.Equal(t, expectedPatches, patches)
|
assertnew.Equal(t, expectedPatches, patches)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_sortRemovalPatches(t *testing.T) {
|
func Test_sortRemovalPatches(t *testing.T) {
|
||||||
|
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"github.com/kyverno/kyverno/pkg/engine/response"
|
"github.com/kyverno/kyverno/pkg/engine/response"
|
||||||
"github.com/kyverno/kyverno/pkg/logging"
|
"github.com/kyverno/kyverno/pkg/logging"
|
||||||
"github.com/kyverno/kyverno/pkg/registryclient"
|
"github.com/kyverno/kyverno/pkg/registryclient"
|
||||||
|
"github.com/kyverno/kyverno/pkg/utils/api"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
)
|
)
|
||||||
|
@ -99,7 +100,9 @@ func Mutate(ctx context.Context, rclient registryclient.Client, policyContext *P
|
||||||
parentResourceGVR = policyContext.requestResource
|
parentResourceGVR = policyContext.requestResource
|
||||||
}
|
}
|
||||||
patchedResources = append(patchedResources, resourceInfo{
|
patchedResources = append(patchedResources, resourceInfo{
|
||||||
unstructured: matchedResource, subresource: policyContext.subresource, parentResourceGVR: parentResourceGVR,
|
unstructured: matchedResource,
|
||||||
|
subresource: policyContext.subresource,
|
||||||
|
parentResourceGVR: parentResourceGVR,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,18 +120,29 @@ func Mutate(ctx context.Context, rclient registryclient.Client, policyContext *P
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.V(4).Info("apply rule to resource", "rule", rule.Name, "resource namespace", patchedResource.unstructured.GetNamespace(), "resource name", patchedResource.unstructured.GetName())
|
logger.V(4).Info("apply rule to resource", "rule", rule.Name, "resource namespace", patchedResource.unstructured.GetNamespace(), "resource name", patchedResource.unstructured.GetName())
|
||||||
var ruleResp *response.RuleResponse
|
var mutateResp *mutate.Response
|
||||||
if rule.Mutation.ForEachMutation != nil {
|
if rule.Mutation.ForEachMutation != nil {
|
||||||
ruleResp, patchedResource.unstructured = mutateForEach(ctx, rclient, ruleCopy, policyContext, patchedResource.unstructured, patchedResource.subresource, patchedResource.parentResourceGVR, logger)
|
m := &forEachMutator{
|
||||||
|
rule: ruleCopy,
|
||||||
|
foreach: rule.Mutation.ForEachMutation,
|
||||||
|
policyContext: policyContext,
|
||||||
|
resource: patchedResource,
|
||||||
|
log: logger,
|
||||||
|
rclient: rclient,
|
||||||
|
nesting: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
mutateResp = m.mutateForEach(ctx)
|
||||||
} else {
|
} else {
|
||||||
ruleResp, patchedResource.unstructured = mutateResource(ruleCopy, policyContext, patchedResource.unstructured, patchedResource.subresource, patchedResource.parentResourceGVR, logger)
|
mutateResp = mutateResource(ruleCopy, policyContext, patchedResource.unstructured, logger)
|
||||||
}
|
}
|
||||||
|
|
||||||
matchedResource = patchedResource.unstructured
|
matchedResource = mutateResp.PatchedResource
|
||||||
|
ruleResponse := buildRuleResponse(ruleCopy, mutateResp, patchedResource)
|
||||||
|
|
||||||
if ruleResp != nil {
|
if ruleResponse != nil {
|
||||||
resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, *ruleResp)
|
resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, *ruleResponse)
|
||||||
if ruleResp.Status == response.RuleStatusError {
|
if ruleResponse.Status == response.RuleStatusError {
|
||||||
incrementErrorCount(resp)
|
incrementErrorCount(resp)
|
||||||
} else {
|
} else {
|
||||||
incrementAppliedCount(resp)
|
incrementAppliedCount(resp)
|
||||||
|
@ -154,81 +168,81 @@ func Mutate(ctx context.Context, rclient registryclient.Client, policyContext *P
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
func mutateResource(rule *kyvernov1.Rule, ctx *PolicyContext, resource unstructured.Unstructured, subresourceName string, parentResourceGVR metav1.GroupVersionResource, logger logr.Logger) (*response.RuleResponse, unstructured.Unstructured) {
|
func mutateResource(rule *kyvernov1.Rule, ctx *PolicyContext, resource unstructured.Unstructured, logger logr.Logger) *mutate.Response {
|
||||||
preconditionsPassed, err := checkPreconditions(logger, ctx, rule.GetAnyAllConditions())
|
preconditionsPassed, err := checkPreconditions(logger, ctx, rule.GetAnyAllConditions())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ruleError(rule, response.Mutation, "failed to evaluate preconditions", err), resource
|
return mutate.NewErrorResponse("failed to evaluate preconditions", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !preconditionsPassed {
|
if !preconditionsPassed {
|
||||||
return ruleResponseWithPatchedTarget(*rule, response.Mutation, "preconditions not met", response.RuleStatusSkip, &resource, subresourceName, parentResourceGVR), resource
|
return mutate.NewResponse(response.RuleStatusSkip, resource, nil, "preconditions not met")
|
||||||
}
|
}
|
||||||
|
|
||||||
mutateResp := mutate.Mutate(rule, ctx.jsonContext, resource, logger)
|
return mutate.Mutate(rule, ctx.JSONContext(), resource, logger)
|
||||||
ruleResp := buildRuleResponse(rule, mutateResp, &mutateResp.PatchedResource, subresourceName, parentResourceGVR)
|
|
||||||
return ruleResp, mutateResp.PatchedResource
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func mutateForEach(ctx context.Context, rclient registryclient.Client, rule *kyvernov1.Rule, enginectx *PolicyContext, resource unstructured.Unstructured, subresourceName string, parentResourceGVR metav1.GroupVersionResource, logger logr.Logger) (*response.RuleResponse, unstructured.Unstructured) {
|
type forEachMutator struct {
|
||||||
foreachList := rule.Mutation.ForEachMutation
|
rule *kyvernov1.Rule
|
||||||
if foreachList == nil {
|
policyContext *PolicyContext
|
||||||
return nil, resource
|
foreach []kyvernov1.ForEachMutation
|
||||||
}
|
resource resourceInfo
|
||||||
|
nesting int
|
||||||
|
rclient registryclient.Client
|
||||||
|
log logr.Logger
|
||||||
|
}
|
||||||
|
|
||||||
patchedResource := resource
|
func (f *forEachMutator) mutateForEach(ctx context.Context) *mutate.Response {
|
||||||
var applyCount int
|
var applyCount int
|
||||||
allPatches := make([][]byte, 0)
|
allPatches := make([][]byte, 0)
|
||||||
|
|
||||||
for _, foreach := range foreachList {
|
for _, foreach := range f.foreach {
|
||||||
if err := LoadContext(ctx, logger, rclient, rule.Context, enginectx, rule.Name); err != nil {
|
if err := LoadContext(ctx, f.log, f.rclient, f.rule.Context, f.policyContext, f.rule.Name); err != nil {
|
||||||
logger.Error(err, "failed to load context")
|
f.log.Error(err, "failed to load context")
|
||||||
return ruleError(rule, response.Mutation, "failed to load context", err), resource
|
return mutate.NewErrorResponse("failed to load context", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
preconditionsPassed, err := checkPreconditions(logger, enginectx, rule.GetAnyAllConditions())
|
preconditionsPassed, err := checkPreconditions(f.log, f.policyContext, f.rule.GetAnyAllConditions())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ruleError(rule, response.Mutation, "failed to evaluate preconditions", err), resource
|
return mutate.NewErrorResponse("failed to evaluate preconditions", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !preconditionsPassed {
|
if !preconditionsPassed {
|
||||||
return ruleResponseWithPatchedTarget(*rule, response.Mutation, "preconditions not met", response.RuleStatusSkip, &patchedResource, subresourceName, parentResourceGVR), resource
|
return mutate.NewResponse(response.RuleStatusSkip, f.resource.unstructured, nil, "preconditions not met")
|
||||||
}
|
}
|
||||||
|
|
||||||
elements, err := evaluateList(foreach.List, enginectx.jsonContext)
|
elements, err := evaluateList(foreach.List, f.policyContext.JSONContext())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
msg := fmt.Sprintf("failed to evaluate list %s", foreach.List)
|
msg := fmt.Sprintf("failed to evaluate list %s: %v", foreach.List, err)
|
||||||
return ruleError(rule, response.Mutation, msg, err), resource
|
return mutate.NewErrorResponse(msg, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
mutateResp := mutateElements(ctx, rclient, rule.Name, foreach, enginectx, elements, patchedResource, logger)
|
mutateResp := f.mutateElements(ctx, foreach, elements)
|
||||||
if mutateResp.Status == response.RuleStatusError {
|
if mutateResp.Status == response.RuleStatusError {
|
||||||
logger.Error(err, "failed to mutate elements")
|
return mutate.NewErrorResponse("failed to mutate elements", err)
|
||||||
return buildRuleResponse(rule, mutateResp, nil, "", metav1.GroupVersionResource{}), resource
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if mutateResp.Status != response.RuleStatusSkip {
|
if mutateResp.Status != response.RuleStatusSkip {
|
||||||
applyCount++
|
applyCount++
|
||||||
if len(mutateResp.Patches) > 0 {
|
if len(mutateResp.Patches) > 0 {
|
||||||
patchedResource = mutateResp.PatchedResource
|
f.resource.unstructured = mutateResp.PatchedResource
|
||||||
allPatches = append(allPatches, mutateResp.Patches...)
|
allPatches = append(allPatches, mutateResp.Patches...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
msg := fmt.Sprintf("%d elements processed", applyCount)
|
||||||
if applyCount == 0 {
|
if applyCount == 0 {
|
||||||
return ruleResponseWithPatchedTarget(*rule, response.Mutation, "0 elements processed", response.RuleStatusSkip, &resource, subresourceName, parentResourceGVR), resource
|
return mutate.NewResponse(response.RuleStatusSkip, f.resource.unstructured, allPatches, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
r := ruleResponseWithPatchedTarget(*rule, response.Mutation, fmt.Sprintf("%d elements processed", applyCount), response.RuleStatusPass, &patchedResource, subresourceName, parentResourceGVR)
|
return mutate.NewResponse(response.RuleStatusPass, f.resource.unstructured, allPatches, msg)
|
||||||
r.Patches = allPatches
|
|
||||||
return r, patchedResource
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func mutateElements(ctx context.Context, rclient registryclient.Client, name string, foreach kyvernov1.ForEachMutation, enginectx *PolicyContext, elements []interface{}, resource unstructured.Unstructured, logger logr.Logger) *mutate.Response {
|
func (f *forEachMutator) mutateElements(ctx context.Context, foreach kyvernov1.ForEachMutation, elements []interface{}) *mutate.Response {
|
||||||
enginectx.jsonContext.Checkpoint()
|
f.policyContext.JSONContext().Checkpoint()
|
||||||
defer enginectx.jsonContext.Restore()
|
defer f.policyContext.JSONContext().Restore()
|
||||||
|
|
||||||
patchedResource := resource
|
patchedResource := f.resource
|
||||||
var allPatches [][]byte
|
var allPatches [][]byte
|
||||||
if foreach.RawPatchStrategicMerge != nil {
|
if foreach.RawPatchStrategicMerge != nil {
|
||||||
invertedElement(elements)
|
invertedElement(elements)
|
||||||
|
@ -238,63 +252,79 @@ func mutateElements(ctx context.Context, rclient registryclient.Client, name str
|
||||||
if e == nil {
|
if e == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
enginectx.jsonContext.Reset()
|
|
||||||
enginectx := enginectx.Copy()
|
f.policyContext.JSONContext().Reset()
|
||||||
|
policyContext := f.policyContext.Copy()
|
||||||
|
|
||||||
|
// TODO - this needs to be refactored. The engine should not have a dependency to the CLI code
|
||||||
store.SetForeachElement(i)
|
store.SetForeachElement(i)
|
||||||
|
|
||||||
falseVar := false
|
falseVar := false
|
||||||
if err := addElementToContext(enginectx, e, i, &falseVar); err != nil {
|
if err := addElementToContext(policyContext, e, i, f.nesting, &falseVar); err != nil {
|
||||||
return mutateError(err, fmt.Sprintf("failed to add element to mutate.foreach[%d].context", i))
|
return mutate.NewErrorResponse(fmt.Sprintf("failed to add element to mutate.foreach[%d].context", i), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := LoadContext(ctx, logger, rclient, foreach.Context, enginectx, name); err != nil {
|
if err := LoadContext(ctx, f.log, f.rclient, foreach.Context, policyContext, f.rule.Name); err != nil {
|
||||||
return mutateError(err, fmt.Sprintf("failed to load to mutate.foreach[%d].context", i))
|
return mutate.NewErrorResponse(fmt.Sprintf("failed to load to mutate.foreach[%d].context", i), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
preconditionsPassed, err := checkPreconditions(logger, enginectx, foreach.AnyAllConditions)
|
preconditionsPassed, err := checkPreconditions(f.log, policyContext, foreach.AnyAllConditions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return mutateError(err, fmt.Sprintf("failed to evaluate mutate.foreach[%d].preconditions", i))
|
return mutate.NewErrorResponse(fmt.Sprintf("failed to evaluate mutate.foreach[%d].preconditions", i), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !preconditionsPassed {
|
if !preconditionsPassed {
|
||||||
logger.Info("mutate.foreach.preconditions not met", "elementIndex", i)
|
f.log.Info("mutate.foreach.preconditions not met", "elementIndex", i)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
mutateResp := mutate.ForEach(name, foreach, enginectx.jsonContext, patchedResource, logger)
|
var mutateResp *mutate.Response
|
||||||
|
if foreach.ForEachMutation != nil {
|
||||||
|
nestedForeach, err := api.DeserializeJSONArray[kyvernov1.ForEachMutation](foreach.ForEachMutation)
|
||||||
|
if err != nil {
|
||||||
|
return mutate.NewErrorResponse("failed to deserialize foreach", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
m := &forEachMutator{
|
||||||
|
rule: f.rule,
|
||||||
|
policyContext: f.policyContext,
|
||||||
|
resource: patchedResource,
|
||||||
|
log: f.log,
|
||||||
|
foreach: nestedForeach,
|
||||||
|
nesting: f.nesting + 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
mutateResp = m.mutateForEach(ctx)
|
||||||
|
} else {
|
||||||
|
mutateResp = mutate.ForEach(f.rule.Name, foreach, policyContext.JSONContext(), patchedResource.unstructured, f.log)
|
||||||
|
}
|
||||||
|
|
||||||
if mutateResp.Status == response.RuleStatusFail || mutateResp.Status == response.RuleStatusError {
|
if mutateResp.Status == response.RuleStatusFail || mutateResp.Status == response.RuleStatusError {
|
||||||
return mutateResp
|
return mutateResp
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(mutateResp.Patches) > 0 {
|
if len(mutateResp.Patches) > 0 {
|
||||||
patchedResource = mutateResp.PatchedResource
|
patchedResource.unstructured = mutateResp.PatchedResource
|
||||||
allPatches = append(allPatches, mutateResp.Patches...)
|
allPatches = append(allPatches, mutateResp.Patches...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &mutate.Response{
|
return mutate.NewResponse(response.RuleStatusPass, patchedResource.unstructured, allPatches, "")
|
||||||
Status: response.RuleStatusPass,
|
|
||||||
PatchedResource: patchedResource,
|
|
||||||
Patches: allPatches,
|
|
||||||
Message: "foreach mutation applied",
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func mutateError(err error, message string) *mutate.Response {
|
func buildRuleResponse(rule *kyvernov1.Rule, mutateResp *mutate.Response, info resourceInfo) *response.RuleResponse {
|
||||||
return &mutate.Response{
|
resp := ruleResponse(*rule, response.Mutation, mutateResp.Message, mutateResp.Status)
|
||||||
Status: response.RuleStatusFail,
|
|
||||||
PatchedResource: unstructured.Unstructured{},
|
|
||||||
Patches: nil,
|
|
||||||
Message: fmt.Sprintf("failed to add element to context: %v", err),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildRuleResponse(rule *kyvernov1.Rule, mutateResp *mutate.Response, patchedResource *unstructured.Unstructured, patchedSubresourceName string, parentResourceGVR metav1.GroupVersionResource) *response.RuleResponse {
|
|
||||||
resp := ruleResponseWithPatchedTarget(*rule, response.Mutation, mutateResp.Message, mutateResp.Status, patchedResource, patchedSubresourceName, parentResourceGVR)
|
|
||||||
if resp.Status == response.RuleStatusPass {
|
if resp.Status == response.RuleStatusPass {
|
||||||
resp.Patches = mutateResp.Patches
|
resp.Patches = mutateResp.Patches
|
||||||
resp.Message = buildSuccessMessage(mutateResp.PatchedResource)
|
resp.Message = buildSuccessMessage(mutateResp.PatchedResource)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(rule.Mutation.Targets) != 0 {
|
||||||
|
resp.PatchedTarget = &mutateResp.PatchedResource
|
||||||
|
resp.PatchedTargetSubresourceName = info.subresource
|
||||||
|
resp.PatchedTargetParentResourceGVR = info.parentResourceGVR
|
||||||
|
}
|
||||||
|
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -93,7 +93,8 @@ func Test_VariableSubstitutionPatchStrategicMerge(t *testing.T) {
|
||||||
policyContext := &PolicyContext{
|
policyContext := &PolicyContext{
|
||||||
policy: &policy,
|
policy: &policy,
|
||||||
jsonContext: ctx,
|
jsonContext: ctx,
|
||||||
newResource: *resourceUnstructured}
|
newResource: *resourceUnstructured,
|
||||||
|
}
|
||||||
er := Mutate(context.TODO(), registryclient.NewOrDie(), policyContext)
|
er := Mutate(context.TODO(), registryclient.NewOrDie(), policyContext)
|
||||||
t.Log(string(expectedPatch))
|
t.Log(string(expectedPatch))
|
||||||
|
|
||||||
|
@ -166,7 +167,8 @@ func Test_variableSubstitutionPathNotExist(t *testing.T) {
|
||||||
policyContext := &PolicyContext{
|
policyContext := &PolicyContext{
|
||||||
policy: &policy,
|
policy: &policy,
|
||||||
jsonContext: ctx,
|
jsonContext: ctx,
|
||||||
newResource: *resourceUnstructured}
|
newResource: *resourceUnstructured,
|
||||||
|
}
|
||||||
er := Mutate(context.TODO(), registryclient.NewOrDie(), policyContext)
|
er := Mutate(context.TODO(), registryclient.NewOrDie(), policyContext)
|
||||||
assert.Equal(t, len(er.PolicyResponse.Rules), 1)
|
assert.Equal(t, len(er.PolicyResponse.Rules), 1)
|
||||||
assert.Assert(t, strings.Contains(er.PolicyResponse.Rules[0].Message, "Unknown key \"name1\" in path"))
|
assert.Assert(t, strings.Contains(er.PolicyResponse.Rules[0].Message, "Unknown key \"name1\" in path"))
|
||||||
|
@ -989,6 +991,29 @@ func Test_foreach_order_mutation_(t *testing.T) {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}`)
|
}`)
|
||||||
|
|
||||||
|
er := testApplyPolicyToResource(t, policyRaw, resourceRaw)
|
||||||
|
|
||||||
|
assert.Equal(t, len(er.PolicyResponse.Rules), 1)
|
||||||
|
assert.Equal(t, er.PolicyResponse.Rules[0].Status, response.RuleStatusPass)
|
||||||
|
|
||||||
|
containers, _, err := unstructured.NestedSlice(er.PatchedResource.Object, "spec", "containers")
|
||||||
|
assert.NilError(t, err)
|
||||||
|
|
||||||
|
for i, c := range containers {
|
||||||
|
ctnr := c.(map[string]interface{})
|
||||||
|
switch i {
|
||||||
|
case 0:
|
||||||
|
assert.Equal(t, ctnr["name"], "mongod")
|
||||||
|
case 1:
|
||||||
|
assert.Equal(t, ctnr["name"], "nginx")
|
||||||
|
case 3:
|
||||||
|
assert.Equal(t, ctnr["name"], "mongodb-agent")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testApplyPolicyToResource(t *testing.T, policyRaw, resourceRaw []byte) *response.EngineResponse {
|
||||||
var policy kyverno.ClusterPolicy
|
var policy kyverno.ClusterPolicy
|
||||||
err := json.Unmarshal(policyRaw, &policy)
|
err := json.Unmarshal(policyRaw, &policy)
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
|
@ -1013,22 +1038,127 @@ func Test_foreach_order_mutation_(t *testing.T) {
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
|
|
||||||
er := Mutate(context.TODO(), registryclient.NewOrDie(), policyContext)
|
er := Mutate(context.TODO(), registryclient.NewOrDie(), policyContext)
|
||||||
|
return er
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_mutate_nested_foreach(t *testing.T) {
|
||||||
|
policyRaw := []byte(`{
|
||||||
|
"apiVersion": "kyverno.io/v1",
|
||||||
|
"kind": "ClusterPolicy",
|
||||||
|
"metadata": {
|
||||||
|
"name": "replace-image-registry"
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"background": false,
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"name": "replace-dns-suffix",
|
||||||
|
"match": {
|
||||||
|
"any": [
|
||||||
|
{
|
||||||
|
"resources": {
|
||||||
|
"kinds": [
|
||||||
|
"Ingress"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"mutate": {
|
||||||
|
"foreach": [
|
||||||
|
{
|
||||||
|
"list": "request.object.spec.tls",
|
||||||
|
"foreach": [
|
||||||
|
{
|
||||||
|
"list": "element.hosts",
|
||||||
|
"patchesJson6902": "- path: /spec/tls/{{elementIndex0}}/hosts/{{elementIndex1}}\n op: replace\n value: {{replace_all('{{element}}', '.foo.com', '.newfoo.com')}}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
|
||||||
|
resourceRaw := []byte(`{
|
||||||
|
"apiVersion": "networking.k8s.io/v1",
|
||||||
|
"kind": "Ingress",
|
||||||
|
"metadata": {
|
||||||
|
"name": "tls-example-ingress"
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"tls": [
|
||||||
|
{
|
||||||
|
"hosts": [
|
||||||
|
"https-example.foo.com"
|
||||||
|
],
|
||||||
|
"secretName": "testsecret-tls"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hosts": [
|
||||||
|
"https-example2.foo.com"
|
||||||
|
],
|
||||||
|
"secretName": "testsecret-tls-2"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"host": "https-example.foo.com",
|
||||||
|
"http": {
|
||||||
|
"paths": [
|
||||||
|
{
|
||||||
|
"path": "/",
|
||||||
|
"pathType": "Prefix",
|
||||||
|
"backend": {
|
||||||
|
"service": {
|
||||||
|
"name": "service1",
|
||||||
|
"port": {
|
||||||
|
"number": 80
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"host": "https-example2.foo.com",
|
||||||
|
"http": {
|
||||||
|
"paths": [
|
||||||
|
{
|
||||||
|
"path": "/",
|
||||||
|
"pathType": "Prefix",
|
||||||
|
"backend": {
|
||||||
|
"service": {
|
||||||
|
"name": "service2",
|
||||||
|
"port": {
|
||||||
|
"number": 80
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
|
||||||
|
er := testApplyPolicyToResource(t, policyRaw, resourceRaw)
|
||||||
assert.Equal(t, len(er.PolicyResponse.Rules), 1)
|
assert.Equal(t, len(er.PolicyResponse.Rules), 1)
|
||||||
assert.Equal(t, er.PolicyResponse.Rules[0].Status, response.RuleStatusPass)
|
assert.Equal(t, er.PolicyResponse.Rules[0].Status, response.RuleStatusPass)
|
||||||
|
assert.Equal(t, len(er.PolicyResponse.Rules[0].Patches), 2)
|
||||||
|
|
||||||
containers, _, err := unstructured.NestedSlice(er.PatchedResource.Object, "spec", "containers")
|
tlsArr, _, err := unstructured.NestedSlice(er.PatchedResource.Object, "spec", "tls")
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
|
for _, e := range tlsArr {
|
||||||
for i, c := range containers {
|
tls := e.(map[string]interface{})
|
||||||
ctnr := c.(map[string]interface{})
|
hosts := tls["hosts"].([]interface{})
|
||||||
switch i {
|
for _, h := range hosts {
|
||||||
case 0:
|
s := h.(string)
|
||||||
assert.Equal(t, ctnr["name"], "mongod")
|
assert.Assert(t, strings.HasSuffix(s, ".newfoo.com"))
|
||||||
case 1:
|
|
||||||
assert.Equal(t, ctnr["name"], "nginx")
|
|
||||||
case 3:
|
|
||||||
assert.Equal(t, ctnr["name"], "mongodb-agent")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,6 @@ import (
|
||||||
kyvernov1beta1 "github.com/kyverno/kyverno/api/kyverno/v1beta1"
|
kyvernov1beta1 "github.com/kyverno/kyverno/api/kyverno/v1beta1"
|
||||||
"github.com/kyverno/kyverno/pkg/clients/dclient"
|
"github.com/kyverno/kyverno/pkg/clients/dclient"
|
||||||
"github.com/kyverno/kyverno/pkg/config"
|
"github.com/kyverno/kyverno/pkg/config"
|
||||||
"github.com/kyverno/kyverno/pkg/engine/context"
|
|
||||||
enginectx "github.com/kyverno/kyverno/pkg/engine/context"
|
enginectx "github.com/kyverno/kyverno/pkg/engine/context"
|
||||||
"github.com/kyverno/kyverno/pkg/engine/context/resolvers"
|
"github.com/kyverno/kyverno/pkg/engine/context/resolvers"
|
||||||
"github.com/kyverno/kyverno/pkg/utils"
|
"github.com/kyverno/kyverno/pkg/utils"
|
||||||
|
@ -54,7 +53,7 @@ type PolicyContext struct {
|
||||||
excludeResourceFunc ExcludeFunc
|
excludeResourceFunc ExcludeFunc
|
||||||
|
|
||||||
// jsonContext is the variable context
|
// jsonContext is the variable context
|
||||||
jsonContext context.Interface
|
jsonContext enginectx.Interface
|
||||||
|
|
||||||
// namespaceLabels stores the label of namespace to be processed by namespace selector
|
// namespaceLabels stores the label of namespace to be processed by namespace selector
|
||||||
namespaceLabels map[string]string
|
namespaceLabels map[string]string
|
||||||
|
@ -95,7 +94,7 @@ func (c *PolicyContext) AdmissionInfo() kyvernov1beta1.RequestInfo {
|
||||||
return c.admissionInfo
|
return c.admissionInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *PolicyContext) JSONContext() context.Interface {
|
func (c *PolicyContext) JSONContext() enginectx.Interface {
|
||||||
return c.jsonContext
|
return c.jsonContext
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -193,7 +192,7 @@ func (c *PolicyContext) WithSubresourcesInPolicy(subresourcesInPolicy []struct {
|
||||||
|
|
||||||
// Constructors
|
// Constructors
|
||||||
|
|
||||||
func NewPolicyContextWithJsonContext(jsonContext context.Interface) *PolicyContext {
|
func NewPolicyContextWithJsonContext(jsonContext enginectx.Interface) *PolicyContext {
|
||||||
return &PolicyContext{
|
return &PolicyContext{
|
||||||
jsonContext: jsonContext,
|
jsonContext: jsonContext,
|
||||||
excludeGroupRole: []string{},
|
excludeGroupRole: []string{},
|
||||||
|
@ -204,7 +203,7 @@ func NewPolicyContextWithJsonContext(jsonContext context.Interface) *PolicyConte
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPolicyContext() *PolicyContext {
|
func NewPolicyContext() *PolicyContext {
|
||||||
return NewPolicyContextWithJsonContext(context.NewContext())
|
return NewPolicyContextWithJsonContext(enginectx.NewContext())
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPolicyContextFromAdmissionRequest(
|
func NewPolicyContextFromAdmissionRequest(
|
||||||
|
|
|
@ -468,22 +468,6 @@ func ruleResponse(rule kyvernov1.Rule, ruleType response.RuleType, msg string, s
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
func ruleResponseWithPatchedTarget(rule kyvernov1.Rule, ruleType response.RuleType, msg string, status response.RuleStatus, patchedResource *unstructured.Unstructured, patchedSubresourceName string, parentResourceGVR metav1.GroupVersionResource) *response.RuleResponse {
|
|
||||||
resp := &response.RuleResponse{
|
|
||||||
Name: rule.Name,
|
|
||||||
Type: ruleType,
|
|
||||||
Message: msg,
|
|
||||||
Status: status,
|
|
||||||
}
|
|
||||||
|
|
||||||
if rule.Mutation.Targets != nil {
|
|
||||||
resp.PatchedTarget = patchedResource
|
|
||||||
resp.PatchedTargetSubresourceName = patchedSubresourceName
|
|
||||||
resp.PatchedTargetParentResourceGVR = parentResourceGVR
|
|
||||||
}
|
|
||||||
return resp
|
|
||||||
}
|
|
||||||
|
|
||||||
func incrementAppliedCount(resp *response.EngineResponse) {
|
func incrementAppliedCount(resp *response.EngineResponse) {
|
||||||
resp.PolicyResponse.RulesAppliedCount++
|
resp.PolicyResponse.RulesAppliedCount++
|
||||||
}
|
}
|
||||||
|
|
|
@ -1867,7 +1867,6 @@ func TestResourceDescriptionMatch_MultipleKind(t *testing.T) {
|
||||||
resource, err := utils.ConvertToUnstructured(rawResource)
|
resource, err := utils.ConvertToUnstructured(rawResource)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("unable to convert raw resource to unstructured: %v", err)
|
t.Errorf("unable to convert raw resource to unstructured: %v", err)
|
||||||
|
|
||||||
}
|
}
|
||||||
resourceDescription := v1.ResourceDescription{
|
resourceDescription := v1.ResourceDescription{
|
||||||
Kinds: []string{"Deployment", "Pods"},
|
Kinds: []string{"Deployment", "Pods"},
|
||||||
|
@ -1881,7 +1880,6 @@ func TestResourceDescriptionMatch_MultipleKind(t *testing.T) {
|
||||||
if err := MatchesResourceDescription(make(map[string]*metav1.APIResource), *resource, rule, v1beta1.RequestInfo{}, []string{}, nil, "", ""); err != nil {
|
if err := MatchesResourceDescription(make(map[string]*metav1.APIResource), *resource, rule, v1beta1.RequestInfo{}, []string{}, nil, "", ""); err != nil {
|
||||||
t.Errorf("Testcase has failed due to the following:%v", err)
|
t.Errorf("Testcase has failed due to the following:%v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Match resource name
|
// Match resource name
|
||||||
|
@ -1927,7 +1925,6 @@ func TestResourceDescriptionMatch_Name(t *testing.T) {
|
||||||
resource, err := utils.ConvertToUnstructured(rawResource)
|
resource, err := utils.ConvertToUnstructured(rawResource)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("unable to convert raw resource to unstructured: %v", err)
|
t.Errorf("unable to convert raw resource to unstructured: %v", err)
|
||||||
|
|
||||||
}
|
}
|
||||||
resourceDescription := v1.ResourceDescription{
|
resourceDescription := v1.ResourceDescription{
|
||||||
Kinds: []string{"Deployment"},
|
Kinds: []string{"Deployment"},
|
||||||
|
@ -1986,7 +1983,6 @@ func TestResourceDescriptionMatch_GenerateName(t *testing.T) {
|
||||||
resource, err := utils.ConvertToUnstructured(rawResource)
|
resource, err := utils.ConvertToUnstructured(rawResource)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("unable to convert raw resource to unstructured: %v", err)
|
t.Errorf("unable to convert raw resource to unstructured: %v", err)
|
||||||
|
|
||||||
}
|
}
|
||||||
resourceDescription := v1.ResourceDescription{
|
resourceDescription := v1.ResourceDescription{
|
||||||
Kinds: []string{"Deployment"},
|
Kinds: []string{"Deployment"},
|
||||||
|
@ -2046,7 +2042,6 @@ func TestResourceDescriptionMatch_Name_Regex(t *testing.T) {
|
||||||
resource, err := utils.ConvertToUnstructured(rawResource)
|
resource, err := utils.ConvertToUnstructured(rawResource)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("unable to convert raw resource to unstructured: %v", err)
|
t.Errorf("unable to convert raw resource to unstructured: %v", err)
|
||||||
|
|
||||||
}
|
}
|
||||||
resourceDescription := v1.ResourceDescription{
|
resourceDescription := v1.ResourceDescription{
|
||||||
Kinds: []string{"Deployment"},
|
Kinds: []string{"Deployment"},
|
||||||
|
@ -2105,7 +2100,6 @@ func TestResourceDescriptionMatch_GenerateName_Regex(t *testing.T) {
|
||||||
resource, err := utils.ConvertToUnstructured(rawResource)
|
resource, err := utils.ConvertToUnstructured(rawResource)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("unable to convert raw resource to unstructured: %v", err)
|
t.Errorf("unable to convert raw resource to unstructured: %v", err)
|
||||||
|
|
||||||
}
|
}
|
||||||
resourceDescription := v1.ResourceDescription{
|
resourceDescription := v1.ResourceDescription{
|
||||||
Kinds: []string{"Deployment"},
|
Kinds: []string{"Deployment"},
|
||||||
|
@ -2165,7 +2159,6 @@ func TestResourceDescriptionMatch_Label_Expression_NotMatch(t *testing.T) {
|
||||||
resource, err := utils.ConvertToUnstructured(rawResource)
|
resource, err := utils.ConvertToUnstructured(rawResource)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("unable to convert raw resource to unstructured: %v", err)
|
t.Errorf("unable to convert raw resource to unstructured: %v", err)
|
||||||
|
|
||||||
}
|
}
|
||||||
resourceDescription := v1.ResourceDescription{
|
resourceDescription := v1.ResourceDescription{
|
||||||
Kinds: []string{"Deployment"},
|
Kinds: []string{"Deployment"},
|
||||||
|
@ -2233,7 +2226,6 @@ func TestResourceDescriptionMatch_Label_Expression_Match(t *testing.T) {
|
||||||
resource, err := utils.ConvertToUnstructured(rawResource)
|
resource, err := utils.ConvertToUnstructured(rawResource)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("unable to convert raw resource to unstructured: %v", err)
|
t.Errorf("unable to convert raw resource to unstructured: %v", err)
|
||||||
|
|
||||||
}
|
}
|
||||||
resourceDescription := v1.ResourceDescription{
|
resourceDescription := v1.ResourceDescription{
|
||||||
Kinds: []string{"Deployment"},
|
Kinds: []string{"Deployment"},
|
||||||
|
@ -2303,7 +2295,6 @@ func TestResourceDescriptionExclude_Label_Expression_Match(t *testing.T) {
|
||||||
resource, err := utils.ConvertToUnstructured(rawResource)
|
resource, err := utils.ConvertToUnstructured(rawResource)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("unable to convert raw resource to unstructured: %v", err)
|
t.Errorf("unable to convert raw resource to unstructured: %v", err)
|
||||||
|
|
||||||
}
|
}
|
||||||
resourceDescription := v1.ResourceDescription{
|
resourceDescription := v1.ResourceDescription{
|
||||||
Kinds: []string{"Deployment"},
|
Kinds: []string{"Deployment"},
|
||||||
|
@ -2331,8 +2322,10 @@ func TestResourceDescriptionExclude_Label_Expression_Match(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
rule := v1.Rule{MatchResources: v1.MatchResources{ResourceDescription: resourceDescription},
|
rule := v1.Rule{
|
||||||
ExcludeResources: v1.MatchResources{ResourceDescription: resourceDescriptionExclude}}
|
MatchResources: v1.MatchResources{ResourceDescription: resourceDescription},
|
||||||
|
ExcludeResources: v1.MatchResources{ResourceDescription: resourceDescriptionExclude},
|
||||||
|
}
|
||||||
|
|
||||||
if err := MatchesResourceDescription(make(map[string]*metav1.APIResource), *resource, rule, v1beta1.RequestInfo{}, []string{}, nil, "", ""); err == nil {
|
if err := MatchesResourceDescription(make(map[string]*metav1.APIResource), *resource, rule, v1beta1.RequestInfo{}, []string{}, nil, "", ""); err == nil {
|
||||||
t.Errorf("Testcase has failed due to the following:\n Function has returned no error, even though it was supposed to fail")
|
t.Errorf("Testcase has failed due to the following:\n Function has returned no error, even though it was supposed to fail")
|
||||||
|
@ -2340,7 +2333,6 @@ func TestResourceDescriptionExclude_Label_Expression_Match(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWildCardLabels(t *testing.T) {
|
func TestWildCardLabels(t *testing.T) {
|
||||||
|
|
||||||
testSelector(t, &metav1.LabelSelector{}, map[string]string{}, true)
|
testSelector(t, &metav1.LabelSelector{}, map[string]string{}, true)
|
||||||
|
|
||||||
testSelector(t, &metav1.LabelSelector{}, map[string]string{"foo": "bar"}, true)
|
testSelector(t, &metav1.LabelSelector{}, map[string]string{"foo": "bar"}, true)
|
||||||
|
@ -2386,7 +2378,6 @@ func testSelector(t *testing.T, s *metav1.LabelSelector, l map[string]string, ma
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWildCardAnnotation(t *testing.T) {
|
func TestWildCardAnnotation(t *testing.T) {
|
||||||
|
|
||||||
// test single annotation values
|
// test single annotation values
|
||||||
testAnnotationMatch(t, map[string]string{}, map[string]string{}, true)
|
testAnnotationMatch(t, map[string]string{}, map[string]string{}, true)
|
||||||
testAnnotationMatch(t, map[string]string{"test/*": "*"}, map[string]string{}, false)
|
testAnnotationMatch(t, map[string]string{"test/*": "*"}, map[string]string{}, false)
|
||||||
|
|
|
@ -1667,7 +1667,8 @@ func testMatchPattern(t *testing.T, testCase struct {
|
||||||
pattern []byte
|
pattern []byte
|
||||||
resource []byte
|
resource []byte
|
||||||
status response.RuleStatus
|
status response.RuleStatus
|
||||||
}) {
|
},
|
||||||
|
) {
|
||||||
var pattern, resource interface{}
|
var pattern, resource interface{}
|
||||||
err := json.Unmarshal(testCase.pattern, &pattern)
|
err := json.Unmarshal(testCase.pattern, &pattern)
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
|
@ -1688,6 +1689,5 @@ func testMatchPattern(t *testing.T, testCase struct {
|
||||||
assert.Assert(t, pe.Skip, fmt.Sprintf("\nexpected skip == true - test: %s\npattern: %s\nresource: %s\n", testCase.name, pattern, resource))
|
assert.Assert(t, pe.Skip, fmt.Sprintf("\nexpected skip == true - test: %s\npattern: %s\nresource: %s\n", testCase.name, pattern, resource))
|
||||||
} else if testCase.status == response.RuleStatusError {
|
} else if testCase.status == response.RuleStatusError {
|
||||||
assert.Assert(t, err == nil, fmt.Sprintf("\nexpected error - test: %s\npattern: %s\nresource: %s\n", testCase.name, pattern, resource))
|
assert.Assert(t, err == nil, fmt.Sprintf("\nexpected error - test: %s\npattern: %s\nresource: %s\n", testCase.name, pattern, resource))
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"github.com/kyverno/kyverno/pkg/pss"
|
"github.com/kyverno/kyverno/pkg/pss"
|
||||||
"github.com/kyverno/kyverno/pkg/registryclient"
|
"github.com/kyverno/kyverno/pkg/registryclient"
|
||||||
"github.com/kyverno/kyverno/pkg/utils"
|
"github.com/kyverno/kyverno/pkg/utils"
|
||||||
|
"github.com/kyverno/kyverno/pkg/utils/api"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
appsv1 "k8s.io/api/apps/v1"
|
appsv1 "k8s.io/api/apps/v1"
|
||||||
batchv1 "k8s.io/api/batch/v1"
|
batchv1 "k8s.io/api/batch/v1"
|
||||||
|
@ -147,12 +148,8 @@ func validateResource(ctx context.Context, log logr.Logger, rclient registryclie
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
func processValidationRule(ctx context.Context, log logr.Logger, rclient registryclient.Client, enginectx *PolicyContext, rule *kyvernov1.Rule) *response.RuleResponse {
|
func processValidationRule(ctx context.Context, log logr.Logger, rclient registryclient.Client, policyContext *PolicyContext, rule *kyvernov1.Rule) *response.RuleResponse {
|
||||||
v := newValidator(log, rclient, enginectx, rule)
|
v := newValidator(log, rclient, policyContext, rule)
|
||||||
if rule.Validation.ForEachValidation != nil {
|
|
||||||
return v.validateForEach(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
return v.validate(ctx)
|
return v.validate(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,7 +169,7 @@ func addRuleResponse(log logr.Logger, resp *response.EngineResponse, ruleResp *r
|
||||||
|
|
||||||
type validator struct {
|
type validator struct {
|
||||||
log logr.Logger
|
log logr.Logger
|
||||||
ctx *PolicyContext
|
policyContext *PolicyContext
|
||||||
rule *kyvernov1.Rule
|
rule *kyvernov1.Rule
|
||||||
contextEntries []kyvernov1.ContextEntry
|
contextEntries []kyvernov1.ContextEntry
|
||||||
anyAllConditions apiextensions.JSON
|
anyAllConditions apiextensions.JSON
|
||||||
|
@ -180,7 +177,9 @@ type validator struct {
|
||||||
anyPattern apiextensions.JSON
|
anyPattern apiextensions.JSON
|
||||||
deny *kyvernov1.Deny
|
deny *kyvernov1.Deny
|
||||||
podSecurity *kyvernov1.PodSecurity
|
podSecurity *kyvernov1.PodSecurity
|
||||||
|
foreach []kyvernov1.ForEachValidation
|
||||||
rclient registryclient.Client
|
rclient registryclient.Client
|
||||||
|
nesting int
|
||||||
}
|
}
|
||||||
|
|
||||||
func newValidator(log logr.Logger, rclient registryclient.Client, ctx *PolicyContext, rule *kyvernov1.Rule) *validator {
|
func newValidator(log logr.Logger, rclient registryclient.Client, ctx *PolicyContext, rule *kyvernov1.Rule) *validator {
|
||||||
|
@ -188,35 +187,43 @@ func newValidator(log logr.Logger, rclient registryclient.Client, ctx *PolicyCon
|
||||||
return &validator{
|
return &validator{
|
||||||
log: log,
|
log: log,
|
||||||
rule: ruleCopy,
|
rule: ruleCopy,
|
||||||
ctx: ctx,
|
policyContext: ctx,
|
||||||
|
rclient: rclient,
|
||||||
contextEntries: ruleCopy.Context,
|
contextEntries: ruleCopy.Context,
|
||||||
anyAllConditions: ruleCopy.GetAnyAllConditions(),
|
anyAllConditions: ruleCopy.GetAnyAllConditions(),
|
||||||
pattern: ruleCopy.Validation.GetPattern(),
|
pattern: ruleCopy.Validation.GetPattern(),
|
||||||
anyPattern: ruleCopy.Validation.GetAnyPattern(),
|
anyPattern: ruleCopy.Validation.GetAnyPattern(),
|
||||||
deny: ruleCopy.Validation.Deny,
|
deny: ruleCopy.Validation.Deny,
|
||||||
podSecurity: ruleCopy.Validation.PodSecurity,
|
podSecurity: ruleCopy.Validation.PodSecurity,
|
||||||
rclient: rclient,
|
foreach: ruleCopy.Validation.ForEachValidation,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newForeachValidator(log logr.Logger, rclient registryclient.Client, foreach kyvernov1.ForEachValidation, rule *kyvernov1.Rule, ctx *PolicyContext) *validator {
|
func newForeachValidator(foreach kyvernov1.ForEachValidation, rclient registryclient.Client, nesting int, rule *kyvernov1.Rule, ctx *PolicyContext, log logr.Logger) (*validator, error) {
|
||||||
ruleCopy := rule.DeepCopy()
|
ruleCopy := rule.DeepCopy()
|
||||||
anyAllConditions, err := utils.ToMap(foreach.AnyAllConditions)
|
anyAllConditions, err := utils.ToMap(foreach.AnyAllConditions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err, "failed to convert ruleCopy.Validation.ForEachValidation.AnyAllConditions")
|
return nil, errors.Wrap(err, "failed to convert ruleCopy.Validation.ForEachValidation.AnyAllConditions")
|
||||||
|
}
|
||||||
|
|
||||||
|
nestedForeach, err := api.DeserializeJSONArray[kyvernov1.ForEachValidation](foreach.ForEachValidation)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to convert ruleCopy.Validation.ForEachValidation.AnyAllConditions")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &validator{
|
return &validator{
|
||||||
log: log,
|
log: log,
|
||||||
ctx: ctx,
|
policyContext: ctx,
|
||||||
rule: ruleCopy,
|
rule: ruleCopy,
|
||||||
|
rclient: rclient,
|
||||||
contextEntries: foreach.Context,
|
contextEntries: foreach.Context,
|
||||||
anyAllConditions: anyAllConditions,
|
anyAllConditions: anyAllConditions,
|
||||||
pattern: foreach.GetPattern(),
|
pattern: foreach.GetPattern(),
|
||||||
anyPattern: foreach.GetAnyPattern(),
|
anyPattern: foreach.GetAnyPattern(),
|
||||||
deny: foreach.Deny,
|
deny: foreach.Deny,
|
||||||
rclient: rclient,
|
foreach: nestedForeach,
|
||||||
}
|
nesting: nesting,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *validator) validate(ctx context.Context) *response.RuleResponse {
|
func (v *validator) validate(ctx context.Context) *response.RuleResponse {
|
||||||
|
@ -224,7 +231,7 @@ func (v *validator) validate(ctx context.Context) *response.RuleResponse {
|
||||||
return ruleError(v.rule, response.Validation, "failed to load context", err)
|
return ruleError(v.rule, response.Validation, "failed to load context", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
preconditionsPassed, err := checkPreconditions(v.log, v.ctx, v.anyAllConditions)
|
preconditionsPassed, err := checkPreconditions(v.log, v.policyContext, v.anyAllConditions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ruleError(v.rule, response.Validation, "failed to evaluate preconditions", err)
|
return ruleError(v.rule, response.Validation, "failed to evaluate preconditions", err)
|
||||||
}
|
}
|
||||||
|
@ -243,46 +250,35 @@ func (v *validator) validate(ctx context.Context) *response.RuleResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
ruleResponse := v.validateResourceWithRule()
|
ruleResponse := v.validateResourceWithRule()
|
||||||
|
|
||||||
return ruleResponse
|
return ruleResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
if v.podSecurity != nil {
|
if v.podSecurity != nil {
|
||||||
if !isDeleteRequest(v.ctx) {
|
if !isDeleteRequest(v.policyContext) {
|
||||||
ruleResponse := v.validatePodSecurity()
|
ruleResponse := v.validatePodSecurity()
|
||||||
return ruleResponse
|
return ruleResponse
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if v.foreach != nil {
|
||||||
|
ruleResponse := v.validateForEach(ctx)
|
||||||
|
return ruleResponse
|
||||||
|
}
|
||||||
|
|
||||||
v.log.V(2).Info("invalid validation rule: podSecurity, patterns, or deny expected")
|
v.log.V(2).Info("invalid validation rule: podSecurity, patterns, or deny expected")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *validator) validateForEach(ctx context.Context) *response.RuleResponse {
|
func (v *validator) validateForEach(ctx context.Context) *response.RuleResponse {
|
||||||
if err := v.loadContext(ctx); err != nil {
|
|
||||||
return ruleError(v.rule, response.Validation, "failed to load context", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
preconditionsPassed, err := checkPreconditions(v.log, v.ctx, v.anyAllConditions)
|
|
||||||
if err != nil {
|
|
||||||
return ruleError(v.rule, response.Validation, "failed to evaluate preconditions", err)
|
|
||||||
} else if !preconditionsPassed {
|
|
||||||
return ruleResponse(*v.rule, response.Validation, "preconditions not met", response.RuleStatusSkip)
|
|
||||||
}
|
|
||||||
|
|
||||||
foreachList := v.rule.Validation.ForEachValidation
|
|
||||||
applyCount := 0
|
applyCount := 0
|
||||||
if foreachList == nil {
|
for _, foreach := range v.foreach {
|
||||||
return nil
|
elements, err := evaluateList(foreach.List, (v.policyContext.JSONContext()))
|
||||||
}
|
|
||||||
|
|
||||||
for _, foreach := range foreachList {
|
|
||||||
elements, err := evaluateList(foreach.List, v.ctx.jsonContext)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
v.log.V(2).Info("failed to evaluate list", "list", foreach.List, "error", err.Error())
|
v.log.V(2).Info("failed to evaluate list", "list", foreach.List, "error", err.Error())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
resp, count := v.validateElements(ctx, foreach, elements, foreach.ElementScope)
|
|
||||||
|
resp, count := v.validateElements(ctx, v.rclient, foreach, elements, foreach.ElementScope)
|
||||||
if resp.Status != response.RuleStatusPass {
|
if resp.Status != response.RuleStatusPass {
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
@ -291,31 +287,42 @@ func (v *validator) validateForEach(ctx context.Context) *response.RuleResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
if applyCount == 0 {
|
if applyCount == 0 {
|
||||||
|
if v.foreach == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
return ruleResponse(*v.rule, response.Validation, "rule skipped", response.RuleStatusSkip)
|
return ruleResponse(*v.rule, response.Validation, "rule skipped", response.RuleStatusSkip)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ruleResponse(*v.rule, response.Validation, "rule passed", response.RuleStatusPass)
|
return ruleResponse(*v.rule, response.Validation, "rule passed", response.RuleStatusPass)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *validator) validateElements(ctx context.Context, foreach kyvernov1.ForEachValidation, elements []interface{}, elementScope *bool) (*response.RuleResponse, int) {
|
func (v *validator) validateElements(ctx context.Context, rclient registryclient.Client, foreach kyvernov1.ForEachValidation, elements []interface{}, elementScope *bool) (*response.RuleResponse, int) {
|
||||||
v.ctx.jsonContext.Checkpoint()
|
v.policyContext.jsonContext.Checkpoint()
|
||||||
defer v.ctx.jsonContext.Restore()
|
defer v.policyContext.jsonContext.Restore()
|
||||||
applyCount := 0
|
applyCount := 0
|
||||||
|
|
||||||
for i, e := range elements {
|
for i, e := range elements {
|
||||||
if e == nil {
|
if e == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
store.SetForeachElement(i)
|
|
||||||
v.ctx.jsonContext.Reset()
|
|
||||||
|
|
||||||
enginectx := v.ctx.Copy()
|
// TODO - this needs to be refactored. The engine should not have a dependency to the CLI code
|
||||||
if err := addElementToContext(enginectx, e, i, elementScope); err != nil {
|
store.SetForeachElement(i)
|
||||||
|
|
||||||
|
v.policyContext.JSONContext().Reset()
|
||||||
|
policyContext := v.policyContext.Copy()
|
||||||
|
if err := addElementToContext(policyContext, e, i, v.nesting, elementScope); err != nil {
|
||||||
v.log.Error(err, "failed to add element to context")
|
v.log.Error(err, "failed to add element to context")
|
||||||
return ruleError(v.rule, response.Validation, "failed to process foreach", err), applyCount
|
return ruleError(v.rule, response.Validation, "failed to process foreach", err), applyCount
|
||||||
}
|
}
|
||||||
|
|
||||||
foreachValidator := newForeachValidator(v.log, v.rclient, foreach, v.rule, enginectx)
|
foreachValidator, err := newForeachValidator(foreach, rclient, v.nesting+1, v.rule, policyContext, v.log)
|
||||||
|
if err != nil {
|
||||||
|
v.log.Error(err, "failed to create foreach validator")
|
||||||
|
return ruleError(v.rule, response.Validation, "failed to create foreach validator", err), applyCount
|
||||||
|
}
|
||||||
|
|
||||||
r := foreachValidator.validate(ctx)
|
r := foreachValidator.validate(ctx)
|
||||||
if r == nil {
|
if r == nil {
|
||||||
v.log.V(2).Info("skip rule due to empty result")
|
v.log.V(2).Info("skip rule due to empty result")
|
||||||
|
@ -341,12 +348,12 @@ func (v *validator) validateElements(ctx context.Context, foreach kyvernov1.ForE
|
||||||
return ruleResponse(*v.rule, response.Validation, "", response.RuleStatusPass), applyCount
|
return ruleResponse(*v.rule, response.Validation, "", response.RuleStatusPass), applyCount
|
||||||
}
|
}
|
||||||
|
|
||||||
func addElementToContext(ctx *PolicyContext, e interface{}, elementIndex int, elementScope *bool) error {
|
func addElementToContext(ctx *PolicyContext, e interface{}, elementIndex, nesting int, elementScope *bool) error {
|
||||||
data, err := variables.DocumentToUntyped(e)
|
data, err := variables.DocumentToUntyped(e)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := ctx.jsonContext.AddElement(data, elementIndex); err != nil {
|
if err := ctx.JSONContext().AddElement(data, elementIndex, nesting); err != nil {
|
||||||
return errors.Wrapf(err, "failed to add element (%v) to JSON context", e)
|
return errors.Wrapf(err, "failed to add element (%v) to JSON context", e)
|
||||||
}
|
}
|
||||||
dataMap, ok := data.(map[string]interface{})
|
dataMap, ok := data.(map[string]interface{})
|
||||||
|
@ -375,7 +382,7 @@ func addElementToContext(ctx *PolicyContext, e interface{}, elementIndex int, el
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *validator) loadContext(ctx context.Context) error {
|
func (v *validator) loadContext(ctx context.Context) error {
|
||||||
if err := LoadContext(ctx, v.log, v.rclient, v.contextEntries, v.ctx, v.rule.Name); err != nil {
|
if err := LoadContext(ctx, v.log, v.rclient, v.contextEntries, v.policyContext, v.rule.Name); err != nil {
|
||||||
if _, ok := err.(gojmespath.NotFoundError); ok {
|
if _, ok := err.(gojmespath.NotFoundError); ok {
|
||||||
v.log.V(3).Info("failed to load context", "reason", err.Error())
|
v.log.V(3).Info("failed to load context", "reason", err.Error())
|
||||||
} else {
|
} else {
|
||||||
|
@ -390,7 +397,7 @@ func (v *validator) loadContext(ctx context.Context) error {
|
||||||
|
|
||||||
func (v *validator) validateDeny() *response.RuleResponse {
|
func (v *validator) validateDeny() *response.RuleResponse {
|
||||||
anyAllCond := v.deny.GetAnyAllConditions()
|
anyAllCond := v.deny.GetAnyAllConditions()
|
||||||
anyAllCond, err := variables.SubstituteAll(v.log, v.ctx.jsonContext, anyAllCond)
|
anyAllCond, err := variables.SubstituteAll(v.log, v.policyContext.jsonContext, anyAllCond)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ruleError(v.rule, response.Validation, "failed to substitute variables in deny conditions", err)
|
return ruleError(v.rule, response.Validation, "failed to substitute variables in deny conditions", err)
|
||||||
}
|
}
|
||||||
|
@ -404,7 +411,7 @@ func (v *validator) validateDeny() *response.RuleResponse {
|
||||||
return ruleError(v.rule, response.Validation, "invalid deny conditions", err)
|
return ruleError(v.rule, response.Validation, "invalid deny conditions", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
deny := variables.EvaluateConditions(v.log, v.ctx.jsonContext, denyConditions)
|
deny := variables.EvaluateConditions(v.log, v.policyContext.jsonContext, denyConditions)
|
||||||
if deny {
|
if deny {
|
||||||
return ruleResponse(*v.rule, response.Validation, v.getDenyMessage(deny), response.RuleStatusFail)
|
return ruleResponse(*v.rule, response.Validation, v.getDenyMessage(deny), response.RuleStatusFail)
|
||||||
}
|
}
|
||||||
|
@ -422,7 +429,7 @@ func (v *validator) getDenyMessage(deny bool) string {
|
||||||
return fmt.Sprintf("validation error: rule %s failed", v.rule.Name)
|
return fmt.Sprintf("validation error: rule %s failed", v.rule.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
raw, err := variables.SubstituteAll(v.log, v.ctx.jsonContext, msg)
|
raw, err := variables.SubstituteAll(v.log, v.policyContext.jsonContext, msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return msg
|
return msg
|
||||||
}
|
}
|
||||||
|
@ -431,12 +438,12 @@ func (v *validator) getDenyMessage(deny bool) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSpec(v *validator) (podSpec *corev1.PodSpec, metadata *metav1.ObjectMeta, err error) {
|
func getSpec(v *validator) (podSpec *corev1.PodSpec, metadata *metav1.ObjectMeta, err error) {
|
||||||
kind := v.ctx.newResource.GetKind()
|
kind := v.policyContext.newResource.GetKind()
|
||||||
|
|
||||||
if kind == "DaemonSet" || kind == "Deployment" || kind == "Job" || kind == "StatefulSet" || kind == "ReplicaSet" || kind == "ReplicationController" {
|
if kind == "DaemonSet" || kind == "Deployment" || kind == "Job" || kind == "StatefulSet" || kind == "ReplicaSet" || kind == "ReplicationController" {
|
||||||
var deployment appsv1.Deployment
|
var deployment appsv1.Deployment
|
||||||
|
|
||||||
resourceBytes, err := v.ctx.newResource.MarshalJSON()
|
resourceBytes, err := v.policyContext.newResource.MarshalJSON()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
@ -450,7 +457,7 @@ func getSpec(v *validator) (podSpec *corev1.PodSpec, metadata *metav1.ObjectMeta
|
||||||
} else if kind == "CronJob" {
|
} else if kind == "CronJob" {
|
||||||
var cronJob batchv1.CronJob
|
var cronJob batchv1.CronJob
|
||||||
|
|
||||||
resourceBytes, err := v.ctx.newResource.MarshalJSON()
|
resourceBytes, err := v.policyContext.newResource.MarshalJSON()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
@ -463,7 +470,7 @@ func getSpec(v *validator) (podSpec *corev1.PodSpec, metadata *metav1.ObjectMeta
|
||||||
} else if kind == "Pod" {
|
} else if kind == "Pod" {
|
||||||
var pod corev1.Pod
|
var pod corev1.Pod
|
||||||
|
|
||||||
resourceBytes, err := v.ctx.newResource.MarshalJSON()
|
resourceBytes, err := v.policyContext.newResource.MarshalJSON()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
@ -508,16 +515,16 @@ func (v *validator) validatePodSecurity() *response.RuleResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *validator) validateResourceWithRule() *response.RuleResponse {
|
func (v *validator) validateResourceWithRule() *response.RuleResponse {
|
||||||
if !isEmptyUnstructured(&v.ctx.element) {
|
if !isEmptyUnstructured(&v.policyContext.element) {
|
||||||
return v.validatePatterns(v.ctx.element)
|
return v.validatePatterns(v.policyContext.element)
|
||||||
}
|
}
|
||||||
|
|
||||||
if isDeleteRequest(v.ctx) {
|
if isDeleteRequest(v.policyContext) {
|
||||||
v.log.V(3).Info("skipping validation on deleted resource")
|
v.log.V(3).Info("skipping validation on deleted resource")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
resp := v.validatePatterns(v.ctx.newResource)
|
resp := v.validatePatterns(v.policyContext.newResource)
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -673,7 +680,7 @@ func (v *validator) buildErrorMessage(err error, path string) string {
|
||||||
return fmt.Sprintf("validation error: rule %s execution error: %s", v.rule.Name, err.Error())
|
return fmt.Sprintf("validation error: rule %s execution error: %s", v.rule.Name, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
msgRaw, sErr := variables.SubstituteAll(v.log, v.ctx.jsonContext, v.rule.Validation.Message)
|
msgRaw, sErr := variables.SubstituteAll(v.log, v.policyContext.jsonContext, v.rule.Validation.Message)
|
||||||
if sErr != nil {
|
if sErr != nil {
|
||||||
v.log.V(2).Info("failed to substitute variables in message", "error", sErr)
|
v.log.V(2).Info("failed to substitute variables in message", "error", sErr)
|
||||||
return fmt.Sprintf("validation error: variables substitution error in rule %s execution error: %s", v.rule.Name, err.Error())
|
return fmt.Sprintf("validation error: variables substitution error in rule %s execution error: %s", v.rule.Name, err.Error())
|
||||||
|
@ -704,7 +711,7 @@ func buildAnyPatternErrorMessage(rule *kyvernov1.Rule, errors []string) string {
|
||||||
|
|
||||||
func (v *validator) substitutePatterns() error {
|
func (v *validator) substitutePatterns() error {
|
||||||
if v.pattern != nil {
|
if v.pattern != nil {
|
||||||
i, err := variables.SubstituteAll(v.log, v.ctx.jsonContext, v.pattern)
|
i, err := variables.SubstituteAll(v.log, v.policyContext.jsonContext, v.pattern)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -714,7 +721,7 @@ func (v *validator) substitutePatterns() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if v.anyPattern != nil {
|
if v.anyPattern != nil {
|
||||||
i, err := variables.SubstituteAll(v.log, v.ctx.jsonContext, v.anyPattern)
|
i, err := variables.SubstituteAll(v.log, v.policyContext.jsonContext, v.anyPattern)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -731,7 +738,7 @@ func (v *validator) substituteDeny() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
i, err := variables.SubstituteAll(v.log, v.ctx.jsonContext, v.deny)
|
i, err := variables.SubstituteAll(v.log, v.policyContext.jsonContext, v.deny)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -1479,7 +1479,8 @@ func Test_VariableSubstitutionPathNotExistInPattern(t *testing.T) {
|
||||||
policyContext := &PolicyContext{
|
policyContext := &PolicyContext{
|
||||||
policy: &policy,
|
policy: &policy,
|
||||||
jsonContext: ctx,
|
jsonContext: ctx,
|
||||||
newResource: *resourceUnstructured}
|
newResource: *resourceUnstructured,
|
||||||
|
}
|
||||||
er := Validate(context.TODO(), registryclient.NewOrDie(), policyContext)
|
er := Validate(context.TODO(), registryclient.NewOrDie(), policyContext)
|
||||||
|
|
||||||
assert.Equal(t, len(er.PolicyResponse.Rules), 1)
|
assert.Equal(t, len(er.PolicyResponse.Rules), 1)
|
||||||
|
@ -1572,7 +1573,8 @@ func Test_VariableSubstitutionPathNotExistInAnyPattern_OnePatternStatisfiesButSu
|
||||||
policyContext := &PolicyContext{
|
policyContext := &PolicyContext{
|
||||||
policy: &policy,
|
policy: &policy,
|
||||||
jsonContext: ctx,
|
jsonContext: ctx,
|
||||||
newResource: *resourceUnstructured}
|
newResource: *resourceUnstructured,
|
||||||
|
}
|
||||||
er := Validate(context.TODO(), registryclient.NewOrDie(), policyContext)
|
er := Validate(context.TODO(), registryclient.NewOrDie(), policyContext)
|
||||||
|
|
||||||
assert.Equal(t, len(er.PolicyResponse.Rules), 1)
|
assert.Equal(t, len(er.PolicyResponse.Rules), 1)
|
||||||
|
@ -1633,7 +1635,8 @@ func Test_VariableSubstitution_NotOperatorWithStringVariable(t *testing.T) {
|
||||||
policyContext := &PolicyContext{
|
policyContext := &PolicyContext{
|
||||||
policy: &policy,
|
policy: &policy,
|
||||||
jsonContext: ctx,
|
jsonContext: ctx,
|
||||||
newResource: *resourceUnstructured}
|
newResource: *resourceUnstructured,
|
||||||
|
}
|
||||||
er := Validate(context.TODO(), registryclient.NewOrDie(), policyContext)
|
er := Validate(context.TODO(), registryclient.NewOrDie(), policyContext)
|
||||||
assert.Equal(t, er.PolicyResponse.Rules[0].Status, response.RuleStatusFail)
|
assert.Equal(t, er.PolicyResponse.Rules[0].Status, response.RuleStatusFail)
|
||||||
assert.Equal(t, er.PolicyResponse.Rules[0].Message, "validation error: rule not-operator-with-variable-should-alway-fail-validation failed at path /spec/content/")
|
assert.Equal(t, er.PolicyResponse.Rules[0].Message, "validation error: rule not-operator-with-variable-should-alway-fail-validation failed at path /spec/content/")
|
||||||
|
@ -1724,7 +1727,8 @@ func Test_VariableSubstitutionPathNotExistInAnyPattern_AllPathNotPresent(t *test
|
||||||
policyContext := &PolicyContext{
|
policyContext := &PolicyContext{
|
||||||
policy: &policy,
|
policy: &policy,
|
||||||
jsonContext: ctx,
|
jsonContext: ctx,
|
||||||
newResource: *resourceUnstructured}
|
newResource: *resourceUnstructured,
|
||||||
|
}
|
||||||
er := Validate(context.TODO(), registryclient.NewOrDie(), policyContext)
|
er := Validate(context.TODO(), registryclient.NewOrDie(), policyContext)
|
||||||
|
|
||||||
assert.Equal(t, len(er.PolicyResponse.Rules), 1)
|
assert.Equal(t, len(er.PolicyResponse.Rules), 1)
|
||||||
|
@ -1817,7 +1821,8 @@ func Test_VariableSubstitutionPathNotExistInAnyPattern_AllPathPresent_NonePatter
|
||||||
policyContext := &PolicyContext{
|
policyContext := &PolicyContext{
|
||||||
policy: &policy,
|
policy: &policy,
|
||||||
jsonContext: ctx,
|
jsonContext: ctx,
|
||||||
newResource: *resourceUnstructured}
|
newResource: *resourceUnstructured,
|
||||||
|
}
|
||||||
er := Validate(context.TODO(), registryclient.NewOrDie(), policyContext)
|
er := Validate(context.TODO(), registryclient.NewOrDie(), policyContext)
|
||||||
|
|
||||||
assert.Equal(t, er.PolicyResponse.Rules[0].Status, response.RuleStatusFail)
|
assert.Equal(t, er.PolicyResponse.Rules[0].Status, response.RuleStatusFail)
|
||||||
|
@ -1922,7 +1927,8 @@ func Test_VariableSubstitutionValidate_VariablesInMessageAreResolved(t *testing.
|
||||||
policyContext := &PolicyContext{
|
policyContext := &PolicyContext{
|
||||||
policy: &policy,
|
policy: &policy,
|
||||||
jsonContext: ctx,
|
jsonContext: ctx,
|
||||||
newResource: *resourceUnstructured}
|
newResource: *resourceUnstructured,
|
||||||
|
}
|
||||||
er := Validate(context.TODO(), registryclient.NewOrDie(), policyContext)
|
er := Validate(context.TODO(), registryclient.NewOrDie(), policyContext)
|
||||||
assert.Equal(t, er.PolicyResponse.Rules[0].Status, response.RuleStatusFail)
|
assert.Equal(t, er.PolicyResponse.Rules[0].Status, response.RuleStatusFail)
|
||||||
assert.Equal(t, er.PolicyResponse.Rules[0].Message, "The animal cow is not in the allowed list of animals.")
|
assert.Equal(t, er.PolicyResponse.Rules[0].Message, "The animal cow is not in the allowed list of animals.")
|
||||||
|
@ -1975,7 +1981,8 @@ func Test_Flux_Kustomization_PathNotPresent(t *testing.T) {
|
||||||
policyContext := &PolicyContext{
|
policyContext := &PolicyContext{
|
||||||
policy: &policy,
|
policy: &policy,
|
||||||
jsonContext: ctx,
|
jsonContext: ctx,
|
||||||
newResource: *resourceUnstructured}
|
newResource: *resourceUnstructured,
|
||||||
|
}
|
||||||
er := Validate(context.TODO(), registryclient.NewOrDie(), policyContext)
|
er := Validate(context.TODO(), registryclient.NewOrDie(), policyContext)
|
||||||
|
|
||||||
for i, rule := range er.PolicyResponse.Rules {
|
for i, rule := range er.PolicyResponse.Rules {
|
||||||
|
@ -2658,7 +2665,6 @@ func Test_foreach_container_deny_error(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_foreach_context_preconditions(t *testing.T) {
|
func Test_foreach_context_preconditions(t *testing.T) {
|
||||||
|
|
||||||
resourceRaw := []byte(`{
|
resourceRaw := []byte(`{
|
||||||
"apiVersion": "v1",
|
"apiVersion": "v1",
|
||||||
"kind": "Deployment",
|
"kind": "Deployment",
|
||||||
|
@ -2752,7 +2758,6 @@ func Test_foreach_context_preconditions(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_foreach_context_preconditions_fail(t *testing.T) {
|
func Test_foreach_context_preconditions_fail(t *testing.T) {
|
||||||
|
|
||||||
resourceRaw := []byte(`{
|
resourceRaw := []byte(`{
|
||||||
"apiVersion": "v1",
|
"apiVersion": "v1",
|
||||||
"kind": "Deployment",
|
"kind": "Deployment",
|
||||||
|
@ -2847,7 +2852,6 @@ func Test_foreach_context_preconditions_fail(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_foreach_element_validation(t *testing.T) {
|
func Test_foreach_element_validation(t *testing.T) {
|
||||||
|
|
||||||
resourceRaw := []byte(`{
|
resourceRaw := []byte(`{
|
||||||
"apiVersion": "v1",
|
"apiVersion": "v1",
|
||||||
"kind": "Pod",
|
"kind": "Pod",
|
||||||
|
@ -2895,7 +2899,6 @@ func Test_foreach_element_validation(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_outof_foreach_element_validation(t *testing.T) {
|
func Test_outof_foreach_element_validation(t *testing.T) {
|
||||||
|
|
||||||
resourceRaw := []byte(`{
|
resourceRaw := []byte(`{
|
||||||
"apiVersion": "v1",
|
"apiVersion": "v1",
|
||||||
"kind": "Pod",
|
"kind": "Pod",
|
||||||
|
@ -2938,7 +2941,6 @@ func Test_outof_foreach_element_validation(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_foreach_skip_initContainer_pass(t *testing.T) {
|
func Test_foreach_skip_initContainer_pass(t *testing.T) {
|
||||||
|
|
||||||
resourceRaw := []byte(`{"apiVersion": "v1",
|
resourceRaw := []byte(`{"apiVersion": "v1",
|
||||||
"kind": "Deployment",
|
"kind": "Deployment",
|
||||||
"metadata": {"name": "test"},
|
"metadata": {"name": "test"},
|
||||||
|
@ -2977,7 +2979,7 @@ func Test_foreach_skip_initContainer_pass(t *testing.T) {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"list": "request.object.spec.template.spec..initContainers",
|
"list": "request.object.spec.template.spec.initContainers",
|
||||||
"pattern": {
|
"pattern": {
|
||||||
"image": "trusted-registry.io/*"
|
"image": "trusted-registry.io/*"
|
||||||
}
|
}
|
||||||
|
@ -2992,6 +2994,102 @@ func Test_foreach_skip_initContainer_pass(t *testing.T) {
|
||||||
testForEach(t, policyraw, resourceRaw, "", response.RuleStatusPass)
|
testForEach(t, policyraw, resourceRaw, "", response.RuleStatusPass)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_foreach_validate_nested(t *testing.T) {
|
||||||
|
resourceRaw := []byte(`{
|
||||||
|
"apiVersion": "networking.k8s.io/v1",
|
||||||
|
"kind": "Ingress",
|
||||||
|
"metadata": {
|
||||||
|
"name": "name-virtual-host-ingress"
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"host": "foo.bar.com",
|
||||||
|
"http": {
|
||||||
|
"paths": [
|
||||||
|
{
|
||||||
|
"pathType": "Prefix",
|
||||||
|
"path": "/",
|
||||||
|
"backend": {
|
||||||
|
"service": {
|
||||||
|
"name": "service1",
|
||||||
|
"port": {
|
||||||
|
"number": 80
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"host": "bar.foo.com",
|
||||||
|
"http": {
|
||||||
|
"paths": [
|
||||||
|
{
|
||||||
|
"pathType": "Prefix",
|
||||||
|
"path": "/",
|
||||||
|
"backend": {
|
||||||
|
"service": {
|
||||||
|
"name": "service2",
|
||||||
|
"port": {
|
||||||
|
"number": 80
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
|
||||||
|
policyraw := []byte(`{
|
||||||
|
"apiVersion": "kyverno.io/v1",
|
||||||
|
"kind": "ClusterPolicy",
|
||||||
|
"metadata": {
|
||||||
|
"name": "replace-image-registry"
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"background": false,
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"name": "replace-dns-suffix",
|
||||||
|
"match": {
|
||||||
|
"any": [
|
||||||
|
{
|
||||||
|
"resources": {
|
||||||
|
"kinds": [
|
||||||
|
"Ingress"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"validate": {
|
||||||
|
"foreach": [
|
||||||
|
{
|
||||||
|
"list": "request.object.spec.rules",
|
||||||
|
"foreach": [
|
||||||
|
{
|
||||||
|
"list": "element.http.paths",
|
||||||
|
"pattern": {
|
||||||
|
"path": "/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
|
||||||
|
testForEach(t, policyraw, resourceRaw, "", response.RuleStatusPass)
|
||||||
|
}
|
||||||
|
|
||||||
func testForEach(t *testing.T, policyraw []byte, resourceRaw []byte, msg string, status response.RuleStatus) {
|
func testForEach(t *testing.T, policyraw []byte, resourceRaw []byte, msg string, status response.RuleStatus) {
|
||||||
var policy kyverno.ClusterPolicy
|
var policy kyverno.ClusterPolicy
|
||||||
assert.NilError(t, json.Unmarshal(policyraw, &policy))
|
assert.NilError(t, json.Unmarshal(policyraw, &policy))
|
||||||
|
@ -3005,7 +3103,8 @@ func testForEach(t *testing.T, policyraw []byte, resourceRaw []byte, msg string,
|
||||||
policyContext := &PolicyContext{
|
policyContext := &PolicyContext{
|
||||||
policy: &policy,
|
policy: &policy,
|
||||||
jsonContext: ctx,
|
jsonContext: ctx,
|
||||||
newResource: *resourceUnstructured}
|
newResource: *resourceUnstructured,
|
||||||
|
}
|
||||||
er := Validate(context.TODO(), registryclient.NewOrDie(), policyContext)
|
er := Validate(context.TODO(), registryclient.NewOrDie(), policyContext)
|
||||||
|
|
||||||
assert.Equal(t, er.PolicyResponse.Rules[0].Status, status)
|
assert.Equal(t, er.PolicyResponse.Rules[0].Status, status)
|
||||||
|
@ -3015,7 +3114,6 @@ func testForEach(t *testing.T, policyraw []byte, resourceRaw []byte, msg string,
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_delete_ignore_pattern(t *testing.T) {
|
func Test_delete_ignore_pattern(t *testing.T) {
|
||||||
|
|
||||||
resourceRaw := []byte(`{
|
resourceRaw := []byte(`{
|
||||||
"apiVersion": "v1",
|
"apiVersion": "v1",
|
||||||
"kind": "Pod",
|
"kind": "Pod",
|
||||||
|
@ -3069,7 +3167,8 @@ func Test_delete_ignore_pattern(t *testing.T) {
|
||||||
policyContextCreate := &PolicyContext{
|
policyContextCreate := &PolicyContext{
|
||||||
policy: &policy,
|
policy: &policy,
|
||||||
jsonContext: ctx,
|
jsonContext: ctx,
|
||||||
newResource: *resourceUnstructured}
|
newResource: *resourceUnstructured,
|
||||||
|
}
|
||||||
engineResponseCreate := Validate(context.TODO(), registryclient.NewOrDie(), policyContextCreate)
|
engineResponseCreate := Validate(context.TODO(), registryclient.NewOrDie(), policyContextCreate)
|
||||||
assert.Equal(t, len(engineResponseCreate.PolicyResponse.Rules), 1)
|
assert.Equal(t, len(engineResponseCreate.PolicyResponse.Rules), 1)
|
||||||
assert.Equal(t, engineResponseCreate.PolicyResponse.Rules[0].Status, response.RuleStatusFail)
|
assert.Equal(t, engineResponseCreate.PolicyResponse.Rules[0].Status, response.RuleStatusFail)
|
||||||
|
@ -3077,7 +3176,8 @@ func Test_delete_ignore_pattern(t *testing.T) {
|
||||||
policyContextDelete := &PolicyContext{
|
policyContextDelete := &PolicyContext{
|
||||||
policy: &policy,
|
policy: &policy,
|
||||||
jsonContext: ctx,
|
jsonContext: ctx,
|
||||||
oldResource: *resourceUnstructured}
|
oldResource: *resourceUnstructured,
|
||||||
|
}
|
||||||
engineResponseDelete := Validate(context.TODO(), registryclient.NewOrDie(), policyContextDelete)
|
engineResponseDelete := Validate(context.TODO(), registryclient.NewOrDie(), policyContextDelete)
|
||||||
assert.Equal(t, len(engineResponseDelete.PolicyResponse.Rules), 0)
|
assert.Equal(t, len(engineResponseDelete.PolicyResponse.Rules), 0)
|
||||||
}
|
}
|
||||||
|
|
|
@ -189,6 +189,7 @@ func Test_variablesub_multiple(t *testing.T) {
|
||||||
t.Error("result does not match")
|
t.Error("result does not match")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_variablesubstitution(t *testing.T) {
|
func Test_variablesubstitution(t *testing.T) {
|
||||||
patternMap := []byte(`
|
patternMap := []byte(`
|
||||||
{
|
{
|
||||||
|
@ -277,7 +278,6 @@ func Test_variablesubstitution(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_variableSubstitutionValue(t *testing.T) {
|
func Test_variableSubstitutionValue(t *testing.T) {
|
||||||
|
|
||||||
resourceRaw := []byte(`
|
resourceRaw := []byte(`
|
||||||
{
|
{
|
||||||
"metadata": {
|
"metadata": {
|
||||||
|
@ -336,7 +336,6 @@ func Test_variableSubstitutionValue(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_variableSubstitutionValueOperatorNotEqual(t *testing.T) {
|
func Test_variableSubstitutionValueOperatorNotEqual(t *testing.T) {
|
||||||
|
|
||||||
resourceRaw := []byte(`
|
resourceRaw := []byte(`
|
||||||
{
|
{
|
||||||
"metadata": {
|
"metadata": {
|
||||||
|
@ -396,7 +395,6 @@ func Test_variableSubstitutionValueOperatorNotEqual(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_variableSubstitutionValueFail(t *testing.T) {
|
func Test_variableSubstitutionValueFail(t *testing.T) {
|
||||||
|
|
||||||
resourceRaw := []byte(`
|
resourceRaw := []byte(`
|
||||||
{
|
{
|
||||||
"metadata": {
|
"metadata": {
|
||||||
|
@ -444,7 +442,6 @@ func Test_variableSubstitutionValueFail(t *testing.T) {
|
||||||
t.Log("expected to fails")
|
t.Log("expected to fails")
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_variableSubstitutionObject(t *testing.T) {
|
func Test_variableSubstitutionObject(t *testing.T) {
|
||||||
|
|
|
@ -15,10 +15,11 @@ import (
|
||||||
"github.com/kyverno/kyverno/pkg/engine/context"
|
"github.com/kyverno/kyverno/pkg/engine/context"
|
||||||
jsonUtils "github.com/kyverno/kyverno/pkg/engine/jsonutils"
|
jsonUtils "github.com/kyverno/kyverno/pkg/engine/jsonutils"
|
||||||
"github.com/kyverno/kyverno/pkg/engine/operator"
|
"github.com/kyverno/kyverno/pkg/engine/operator"
|
||||||
|
"github.com/kyverno/kyverno/pkg/logging"
|
||||||
"github.com/kyverno/kyverno/pkg/utils/jsonpointer"
|
"github.com/kyverno/kyverno/pkg/utils/jsonpointer"
|
||||||
)
|
)
|
||||||
|
|
||||||
var RegexVariables = regexp.MustCompile(`(?:^|[^\\])(\{\{(?:\{[^{}]*\}|[^{}])*\}\})`)
|
var RegexVariables = regexp.MustCompile(`(^|[^\\])(\{\{(?:\{[^{}]*\}|[^{}])*\}\})`)
|
||||||
|
|
||||||
var RegexEscpVariables = regexp.MustCompile(`\\\{\{(\{[^{}]*\}|[^{}])*\}\}`)
|
var RegexEscpVariables = regexp.MustCompile(`\\\{\{(\{[^{}]*\}|[^{}])*\}\}`)
|
||||||
|
|
||||||
|
@ -30,7 +31,7 @@ var RegexEscpReferences = regexp.MustCompile(`\\\$\(.[^\ ]*\)`)
|
||||||
|
|
||||||
var regexVariableInit = regexp.MustCompile(`^\{\{(\{[^{}]*\}|[^{}])*\}\}`)
|
var regexVariableInit = regexp.MustCompile(`^\{\{(\{[^{}]*\}|[^{}])*\}\}`)
|
||||||
|
|
||||||
var regexElementIndex = regexp.MustCompile(`{{\s*elementIndex\s*}}`)
|
var regexElementIndex = regexp.MustCompile(`{{\s*elementIndex\d*\s*}}`)
|
||||||
|
|
||||||
// IsVariable returns true if the element contains a 'valid' variable {{}}
|
// IsVariable returns true if the element contains a 'valid' variable {{}}
|
||||||
func IsVariable(value string) bool {
|
func IsVariable(value string) bool {
|
||||||
|
@ -569,12 +570,13 @@ func replaceSubstituteVariables(document interface{}) interface{} {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
rawDocument = RegexVariables.ReplaceAll(rawDocument, []byte(`placeholderValue`))
|
rawDocument = RegexVariables.ReplaceAll(rawDocument, []byte(`${1}placeholderValue`))
|
||||||
}
|
}
|
||||||
|
|
||||||
var output interface{}
|
var output interface{}
|
||||||
err = json.Unmarshal(rawDocument, &output)
|
err = json.Unmarshal(rawDocument, &output)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
logging.Error(err, "failed to unmarshall JSON: %s", string(rawDocument))
|
||||||
return document
|
return document
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -365,7 +365,7 @@ func Test_subVars_withRegexReplaceAll(t *testing.T) {
|
||||||
func Test_ReplacingPathWhenDeleting(t *testing.T) {
|
func Test_ReplacingPathWhenDeleting(t *testing.T) {
|
||||||
patternRaw := []byte(`"{{request.object.metadata.annotations.target}}"`)
|
patternRaw := []byte(`"{{request.object.metadata.annotations.target}}"`)
|
||||||
|
|
||||||
var resourceRaw = []byte(`
|
resourceRaw := []byte(`
|
||||||
{
|
{
|
||||||
"request": {
|
"request": {
|
||||||
"operation": "DELETE",
|
"operation": "DELETE",
|
||||||
|
@ -408,7 +408,7 @@ func Test_ReplacingPathWhenDeleting(t *testing.T) {
|
||||||
func Test_ReplacingNestedVariableWhenDeleting(t *testing.T) {
|
func Test_ReplacingNestedVariableWhenDeleting(t *testing.T) {
|
||||||
patternRaw := []byte(`"{{request.object.metadata.annotations.{{request.object.metadata.annotations.targetnew}}}}"`)
|
patternRaw := []byte(`"{{request.object.metadata.annotations.{{request.object.metadata.annotations.targetnew}}}}"`)
|
||||||
|
|
||||||
var resourceRaw = []byte(`
|
resourceRaw := []byte(`
|
||||||
{
|
{
|
||||||
"request":{
|
"request":{
|
||||||
"operation":"DELETE",
|
"operation":"DELETE",
|
||||||
|
@ -468,8 +468,8 @@ func Test_SubstituteSuccess(t *testing.T) {
|
||||||
results, err := action(&ju.ActionData{
|
results, err := action(&ju.ActionData{
|
||||||
Document: nil,
|
Document: nil,
|
||||||
Element: string(patternRaw),
|
Element: string(patternRaw),
|
||||||
Path: "/"})
|
Path: "/",
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("substitution failed: %v", err.Error())
|
t.Errorf("substitution failed: %v", err.Error())
|
||||||
return
|
return
|
||||||
|
@ -492,7 +492,8 @@ func Test_SubstituteRecursiveErrors(t *testing.T) {
|
||||||
results, err := action(&ju.ActionData{
|
results, err := action(&ju.ActionData{
|
||||||
Document: nil,
|
Document: nil,
|
||||||
Element: string(patternRaw),
|
Element: string(patternRaw),
|
||||||
Path: "/"})
|
Path: "/",
|
||||||
|
})
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("expected error but received: %v", results)
|
t.Errorf("expected error but received: %v", results)
|
||||||
|
@ -505,7 +506,8 @@ func Test_SubstituteRecursiveErrors(t *testing.T) {
|
||||||
results, err = action(&ju.ActionData{
|
results, err = action(&ju.ActionData{
|
||||||
Document: nil,
|
Document: nil,
|
||||||
Element: string(patternRaw),
|
Element: string(patternRaw),
|
||||||
Path: "/"})
|
Path: "/",
|
||||||
|
})
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("expected error but received: %v", results)
|
t.Errorf("expected error but received: %v", results)
|
||||||
|
@ -524,8 +526,8 @@ func Test_SubstituteRecursive(t *testing.T) {
|
||||||
results, err := action(&ju.ActionData{
|
results, err := action(&ju.ActionData{
|
||||||
Document: nil,
|
Document: nil,
|
||||||
Element: string(patternRaw),
|
Element: string(patternRaw),
|
||||||
Path: "/"})
|
Path: "/",
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("substitution failed: %v", err.Error())
|
t.Errorf("substitution failed: %v", err.Error())
|
||||||
return
|
return
|
||||||
|
@ -1146,7 +1148,7 @@ func Test_EscpReferenceSubstitution(t *testing.T) {
|
||||||
func Test_ReplacingEscpNestedVariableWhenDeleting(t *testing.T) {
|
func Test_ReplacingEscpNestedVariableWhenDeleting(t *testing.T) {
|
||||||
patternRaw := []byte(`"\\{{request.object.metadata.annotations.{{request.object.metadata.annotations.targetnew}}}}"`)
|
patternRaw := []byte(`"\\{{request.object.metadata.annotations.{{request.object.metadata.annotations.targetnew}}}}"`)
|
||||||
|
|
||||||
var resourceRaw = []byte(`
|
resourceRaw := []byte(`
|
||||||
{
|
{
|
||||||
"request":{
|
"request":{
|
||||||
"operation":"DELETE",
|
"operation":"DELETE",
|
||||||
|
@ -1177,3 +1179,38 @@ func Test_ReplacingEscpNestedVariableWhenDeleting(t *testing.T) {
|
||||||
|
|
||||||
assert.Equal(t, fmt.Sprintf("%v", pattern), "{{request.object.metadata.annotations.target}}")
|
assert.Equal(t, fmt.Sprintf("%v", pattern), "{{request.object.metadata.annotations.target}}")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_RegexVariables(t *testing.T) {
|
||||||
|
vars := RegexVariables.FindAllString("tag: {{ value }}", -1)
|
||||||
|
assert.Equal(t, len(vars), 1)
|
||||||
|
assert.Equal(t, vars[0], " {{ value }}")
|
||||||
|
|
||||||
|
res := RegexVariables.ReplaceAllString("tag: {{ value }}", "${1}test")
|
||||||
|
assert.Equal(t, res, "tag: test")
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_IsVariable(t *testing.T) {
|
||||||
|
assert.Equal(t, IsVariable("{{ foo }}"), true)
|
||||||
|
assert.Equal(t, IsVariable("{{ foo {{foo2}} }}"), true)
|
||||||
|
assert.Equal(t, IsVariable("\\{{ foo }}"), false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_ReplaceAllVars(t *testing.T) {
|
||||||
|
result := ReplaceAllVars("{{ foo }}", func(s string) string { return "test" })
|
||||||
|
assert.Equal(t, result, "test")
|
||||||
|
|
||||||
|
result = ReplaceAllVars("\"{{ foo }}\"", func(s string) string { return "test" })
|
||||||
|
assert.Equal(t, result, "\"test\"")
|
||||||
|
|
||||||
|
result = ReplaceAllVars("/s/{{elementIndex}}/r", func(s string) string { return "test" })
|
||||||
|
assert.Equal(t, result, "/s/test/r")
|
||||||
|
|
||||||
|
result = ReplaceAllVars("{{ foo }} {{foo}} {{foo}}", func(s string) string { return "test" })
|
||||||
|
assert.Equal(t, result, "test test test")
|
||||||
|
|
||||||
|
result = ReplaceAllVars("{{ foo }} \\{{foo}} {{foo}}", func(s string) string { return "test" })
|
||||||
|
assert.Equal(t, result, "test \\{{foo}} test")
|
||||||
|
|
||||||
|
result = ReplaceAllVars("{{ foo {{foo}} }}", func(s string) string { return "test" })
|
||||||
|
assert.Equal(t, result, "{{ foo test }}")
|
||||||
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestExpandInMetadata(t *testing.T) {
|
func TestExpandInMetadata(t *testing.T) {
|
||||||
//testExpand(t, map[string]string{"test/*": "*"}, map[string]string{},
|
// testExpand(t, map[string]string{"test/*": "*"}, map[string]string{},
|
||||||
// map[string]string{"test/0": "0"})
|
// map[string]string{"test/0": "0"})
|
||||||
|
|
||||||
testExpand(t, map[string]string{"test/*": "*"}, map[string]string{"test/test": "test"},
|
testExpand(t, map[string]string{"test/*": "*"}, map[string]string{"test/test": "test"},
|
||||||
|
|
|
@ -65,6 +65,11 @@ func Test_ValidateMutationPolicy(t *testing.T) {
|
||||||
policy: []byte(`{"apiVersion":"kyverno.io\/v1","kind":"ClusterPolicy","metadata":{"name":"set-image-pull-policy"},"spec":{"rules":[{"name":"set-image-pull-policy","match":{"all":[{"resources":{"kinds":["Pod"]}}]},"mutate":{"patchStrategicMerge":{"spec":{"containers":[{"(image)":"*:latest","imagePullPolicy":"IfNotPresent"}]}}}}]}}`),
|
policy: []byte(`{"apiVersion":"kyverno.io\/v1","kind":"ClusterPolicy","metadata":{"name":"set-image-pull-policy"},"spec":{"rules":[{"name":"set-image-pull-policy","match":{"all":[{"resources":{"kinds":["Pod"]}}]},"mutate":{"patchStrategicMerge":{"spec":{"containers":[{"(image)":"*:latest","imagePullPolicy":"IfNotPresent"}]}}}}]}}`),
|
||||||
mustSucceed: true,
|
mustSucceed: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
description: "Policy with nested foreach and patchesJson6902",
|
||||||
|
policy: []byte(`{"apiVersion":"kyverno.io/v2beta1","kind":"ClusterPolicy","metadata":{"name":"replace-image-registry"},"spec":{"background":false,"validationFailureAction":"Enforce","rules":[{"name":"replace-dns-suffix","match":{"any":[{"resources":{"kinds":["Ingress"]}}]},"mutate":{"foreach":[{"list":"request.object.spec.tls","foreach":[{"list":"element.hosts","patchesJson6902":"- path: \"/spec/tls/{{elementIndex0}}/hosts/{{elementIndex1}}\"\n op: replace\n value: \"{{replace_all('{{element}}', '.foo.com', '.newfoo.com')}}\""}]}]}}]}}`),
|
||||||
|
mustSucceed: true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
o, _ := NewManager()
|
o, _ := NewManager()
|
||||||
|
|
|
@ -31,8 +31,8 @@ func containsUserVariables(policy kyvernov1.PolicyInterface, vars [][]string) er
|
||||||
}
|
}
|
||||||
for _, s := range vars {
|
for _, s := range vars {
|
||||||
for _, banned := range forbidden {
|
for _, banned := range forbidden {
|
||||||
if banned.Match([]byte(s[1])) {
|
if banned.Match([]byte(s[2])) {
|
||||||
return fmt.Errorf("variable %s is not allowed", s[1])
|
return fmt.Errorf("variable %s is not allowed", s[2])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,9 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
|
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
|
||||||
|
"github.com/kyverno/kyverno/pkg/utils/api"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Mutate provides implementation to validate 'mutate' rule
|
// Mutate provides implementation to validate 'mutate' rule
|
||||||
|
@ -21,7 +24,11 @@ func NewMutateFactory(m kyvernov1.Mutation) *Mutate {
|
||||||
// Validate validates the 'mutate' rule
|
// Validate validates the 'mutate' rule
|
||||||
func (m *Mutate) Validate() (string, error) {
|
func (m *Mutate) Validate() (string, error) {
|
||||||
if m.hasForEach() {
|
if m.hasForEach() {
|
||||||
return m.validateForEach()
|
if m.hasPatchStrategicMerge() || m.hasPatchesJSON6902() {
|
||||||
|
return "foreach", fmt.Errorf("only one of `foreach`, `patchStrategicMerge`, or `patchesJson6902` is allowed")
|
||||||
|
}
|
||||||
|
|
||||||
|
return m.validateForEach("", m.mutation.ForEachMutation)
|
||||||
}
|
}
|
||||||
|
|
||||||
if m.hasPatchesJSON6902() && m.hasPatchStrategicMerge() {
|
if m.hasPatchesJSON6902() && m.hasPatchStrategicMerge() {
|
||||||
|
@ -31,21 +38,35 @@ func (m *Mutate) Validate() (string, error) {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Mutate) validateForEach() (string, error) {
|
func (m *Mutate) validateForEach(tag string, foreach []kyvernov1.ForEachMutation) (string, error) {
|
||||||
if m.hasPatchStrategicMerge() || m.hasPatchesJSON6902() {
|
for i, fe := range foreach {
|
||||||
return "foreach", fmt.Errorf("only one of `foreach`, `patchStrategicMerge`, or `patchesJson6902` is allowed")
|
tag = tag + fmt.Sprintf("foreach[%d]", i)
|
||||||
}
|
if fe.ForEachMutation != nil {
|
||||||
|
if fe.Context != nil || fe.AnyAllConditions != nil || fe.PatchesJSON6902 != "" || fe.RawPatchStrategicMerge != nil {
|
||||||
|
return tag, fmt.Errorf("a nested foreach cannot contain other declarations")
|
||||||
|
}
|
||||||
|
|
||||||
|
return m.validateNestedForEach(tag, fe.ForEachMutation)
|
||||||
|
}
|
||||||
|
|
||||||
for i, fe := range m.mutation.ForEachMutation {
|
|
||||||
psm := fe.GetPatchStrategicMerge()
|
psm := fe.GetPatchStrategicMerge()
|
||||||
if (fe.PatchesJSON6902 == "" && psm == nil) || (fe.PatchesJSON6902 != "" && psm != nil) {
|
if (fe.PatchesJSON6902 == "" && psm == nil) || (fe.PatchesJSON6902 != "" && psm != nil) {
|
||||||
return fmt.Sprintf("foreach[%d]", i), fmt.Errorf("only one of `patchStrategicMerge` or `patchesJson6902` is allowed")
|
return tag, fmt.Errorf("only one of `patchStrategicMerge` or `patchesJson6902` is allowed")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Mutate) validateNestedForEach(tag string, j *v1.JSON) (string, error) {
|
||||||
|
nestedForeach, err := api.DeserializeJSONArray[kyvernov1.ForEachMutation](j)
|
||||||
|
if err != nil {
|
||||||
|
return tag, errors.Wrapf(err, "invalid foreach syntax")
|
||||||
|
}
|
||||||
|
|
||||||
|
return m.validateForEach(tag, nestedForeach)
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Mutate) hasForEach() bool {
|
func (m *Mutate) hasForEach() bool {
|
||||||
return len(m.mutation.ForEachMutation) > 0
|
return len(m.mutation.ForEachMutation) > 0
|
||||||
}
|
}
|
||||||
|
|
26
pkg/utils/api/json.go
Normal file
26
pkg/utils/api/json.go
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Deserialize "apiextensions.JSON" to a typed array
|
||||||
|
func DeserializeJSONArray[T any](j apiextensions.JSON) ([]T, error) {
|
||||||
|
if j == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := json.Marshal(j)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var res []T
|
||||||
|
if err := json.Unmarshal(data, &res); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
Loading…
Reference in a new issue