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",
|
||||
"--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/.
|
||||
// +optional
|
||||
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 {
|
||||
|
@ -398,6 +402,14 @@ func (v *Validation) SetAnyPattern(in apiextensions.JSON) {
|
|||
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.
|
||||
type Deny struct {
|
||||
// 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.
|
||||
// +optional
|
||||
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 {
|
||||
|
|
|
@ -472,6 +472,11 @@ func (in *ForEachMutation) DeepCopyInto(out *ForEachMutation) {
|
|||
*out = new(apiextensionsv1.JSON)
|
||||
(*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.
|
||||
|
@ -519,6 +524,11 @@ func (in *ForEachValidation) DeepCopyInto(out *ForEachValidation) {
|
|||
*out = new(Deny)
|
||||
(*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.
|
||||
|
|
|
@ -3489,6 +3489,9 @@ spec:
|
|||
type: object
|
||||
type: object
|
||||
type: array
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied.
|
||||
type: string
|
||||
|
@ -3693,6 +3696,9 @@ spec:
|
|||
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.
|
||||
type: boolean
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied.
|
||||
type: string
|
||||
|
@ -5377,6 +5383,9 @@ spec:
|
|||
type: object
|
||||
type: object
|
||||
type: array
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied.
|
||||
type: string
|
||||
|
@ -5581,6 +5590,9 @@ spec:
|
|||
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.
|
||||
type: boolean
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied.
|
||||
type: string
|
||||
|
@ -7132,6 +7144,9 @@ spec:
|
|||
type: object
|
||||
type: object
|
||||
type: array
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied.
|
||||
type: string
|
||||
|
@ -7458,6 +7473,9 @@ spec:
|
|||
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.
|
||||
type: boolean
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied.
|
||||
type: string
|
||||
|
@ -9117,6 +9135,9 @@ spec:
|
|||
type: object
|
||||
type: object
|
||||
type: array
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied.
|
||||
type: string
|
||||
|
@ -9321,6 +9342,9 @@ spec:
|
|||
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.
|
||||
type: boolean
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied.
|
||||
type: string
|
||||
|
@ -11596,6 +11620,9 @@ spec:
|
|||
type: object
|
||||
type: object
|
||||
type: array
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied.
|
||||
type: string
|
||||
|
@ -11800,6 +11827,9 @@ spec:
|
|||
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.
|
||||
type: boolean
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied.
|
||||
type: string
|
||||
|
@ -13484,6 +13514,9 @@ spec:
|
|||
type: object
|
||||
type: object
|
||||
type: array
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied.
|
||||
type: string
|
||||
|
@ -13688,6 +13721,9 @@ spec:
|
|||
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.
|
||||
type: boolean
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied.
|
||||
type: string
|
||||
|
@ -15239,6 +15275,9 @@ spec:
|
|||
type: object
|
||||
type: object
|
||||
type: array
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied.
|
||||
type: string
|
||||
|
@ -15565,6 +15604,9 @@ spec:
|
|||
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.
|
||||
type: boolean
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied.
|
||||
type: string
|
||||
|
@ -17224,6 +17266,9 @@ spec:
|
|||
type: object
|
||||
type: object
|
||||
type: array
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied.
|
||||
type: string
|
||||
|
@ -17428,6 +17473,9 @@ spec:
|
|||
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.
|
||||
type: boolean
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression that results in one or more elements to which the validation logic is applied.
|
||||
type: string
|
||||
|
|
|
@ -82,7 +82,7 @@ func Test_Apply(t *testing.T) {
|
|||
expectedPolicyReports: []preport.PolicyReport{
|
||||
{
|
||||
Summary: preport.PolicyReportSummary{
|
||||
Pass: 9,
|
||||
Pass: 6,
|
||||
Fail: 0,
|
||||
Skip: 0,
|
||||
Error: 0,
|
||||
|
|
|
@ -1729,6 +1729,9 @@ spec:
|
|||
type: object
|
||||
type: object
|
||||
type: array
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression
|
||||
that results in one or more elements to which the
|
||||
|
@ -2042,6 +2045,9 @@ spec:
|
|||
scope within the foreach block to allow referencing
|
||||
other elements in the subtree.
|
||||
type: boolean
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression
|
||||
that results in one or more elements to which the
|
||||
|
@ -4815,6 +4821,10 @@ spec:
|
|||
type: object
|
||||
type: object
|
||||
type: array
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach
|
||||
iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression
|
||||
that results in one or more elements to which
|
||||
|
@ -5140,6 +5150,10 @@ spec:
|
|||
as the validation scope within the foreach block
|
||||
to allow referencing other elements in the subtree.
|
||||
type: boolean
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach
|
||||
iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression
|
||||
that results in one or more elements to which
|
||||
|
@ -7631,6 +7645,9 @@ spec:
|
|||
type: object
|
||||
type: object
|
||||
type: array
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression
|
||||
that results in one or more elements to which the
|
||||
|
@ -8112,6 +8129,9 @@ spec:
|
|||
scope within the foreach block to allow referencing
|
||||
other elements in the subtree.
|
||||
type: boolean
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression
|
||||
that results in one or more elements to which the
|
||||
|
@ -10845,6 +10865,10 @@ spec:
|
|||
type: object
|
||||
type: object
|
||||
type: array
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach
|
||||
iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression
|
||||
that results in one or more elements to which
|
||||
|
@ -11170,6 +11194,10 @@ spec:
|
|||
as the validation scope within the foreach block
|
||||
to allow referencing other elements in the subtree.
|
||||
type: boolean
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach
|
||||
iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression
|
||||
that results in one or more elements to which
|
||||
|
|
|
@ -1731,6 +1731,9 @@ spec:
|
|||
type: object
|
||||
type: object
|
||||
type: array
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression
|
||||
that results in one or more elements to which the
|
||||
|
@ -2044,6 +2047,9 @@ spec:
|
|||
scope within the foreach block to allow referencing
|
||||
other elements in the subtree.
|
||||
type: boolean
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression
|
||||
that results in one or more elements to which the
|
||||
|
@ -4818,6 +4824,10 @@ spec:
|
|||
type: object
|
||||
type: object
|
||||
type: array
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach
|
||||
iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression
|
||||
that results in one or more elements to which
|
||||
|
@ -5143,6 +5153,10 @@ spec:
|
|||
as the validation scope within the foreach block
|
||||
to allow referencing other elements in the subtree.
|
||||
type: boolean
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach
|
||||
iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression
|
||||
that results in one or more elements to which
|
||||
|
@ -7635,6 +7649,9 @@ spec:
|
|||
type: object
|
||||
type: object
|
||||
type: array
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression
|
||||
that results in one or more elements to which the
|
||||
|
@ -8116,6 +8133,9 @@ spec:
|
|||
scope within the foreach block to allow referencing
|
||||
other elements in the subtree.
|
||||
type: boolean
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression
|
||||
that results in one or more elements to which the
|
||||
|
@ -10849,6 +10869,10 @@ spec:
|
|||
type: object
|
||||
type: object
|
||||
type: array
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach
|
||||
iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression
|
||||
that results in one or more elements to which
|
||||
|
@ -11174,6 +11198,10 @@ spec:
|
|||
as the validation scope within the foreach block
|
||||
to allow referencing other elements in the subtree.
|
||||
type: boolean
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach
|
||||
iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression
|
||||
that results in one or more elements to which
|
||||
|
|
|
@ -5155,6 +5155,9 @@ spec:
|
|||
type: object
|
||||
type: object
|
||||
type: array
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression
|
||||
that results in one or more elements to which the
|
||||
|
@ -5468,6 +5471,9 @@ spec:
|
|||
scope within the foreach block to allow referencing
|
||||
other elements in the subtree.
|
||||
type: boolean
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression
|
||||
that results in one or more elements to which the
|
||||
|
@ -8241,6 +8247,10 @@ spec:
|
|||
type: object
|
||||
type: object
|
||||
type: array
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach
|
||||
iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression
|
||||
that results in one or more elements to which
|
||||
|
@ -8566,6 +8576,10 @@ spec:
|
|||
as the validation scope within the foreach block
|
||||
to allow referencing other elements in the subtree.
|
||||
type: boolean
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach
|
||||
iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression
|
||||
that results in one or more elements to which
|
||||
|
@ -11057,6 +11071,9 @@ spec:
|
|||
type: object
|
||||
type: object
|
||||
type: array
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression
|
||||
that results in one or more elements to which the
|
||||
|
@ -11538,6 +11555,9 @@ spec:
|
|||
scope within the foreach block to allow referencing
|
||||
other elements in the subtree.
|
||||
type: boolean
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression
|
||||
that results in one or more elements to which the
|
||||
|
@ -14271,6 +14291,10 @@ spec:
|
|||
type: object
|
||||
type: object
|
||||
type: array
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach
|
||||
iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression
|
||||
that results in one or more elements to which
|
||||
|
@ -14596,6 +14620,10 @@ spec:
|
|||
as the validation scope within the foreach block
|
||||
to allow referencing other elements in the subtree.
|
||||
type: boolean
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach
|
||||
iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression
|
||||
that results in one or more elements to which
|
||||
|
@ -18072,6 +18100,9 @@ spec:
|
|||
type: object
|
||||
type: object
|
||||
type: array
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression
|
||||
that results in one or more elements to which the
|
||||
|
@ -18385,6 +18416,9 @@ spec:
|
|||
scope within the foreach block to allow referencing
|
||||
other elements in the subtree.
|
||||
type: boolean
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression
|
||||
that results in one or more elements to which the
|
||||
|
@ -21159,6 +21193,10 @@ spec:
|
|||
type: object
|
||||
type: object
|
||||
type: array
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach
|
||||
iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression
|
||||
that results in one or more elements to which
|
||||
|
@ -21484,6 +21522,10 @@ spec:
|
|||
as the validation scope within the foreach block
|
||||
to allow referencing other elements in the subtree.
|
||||
type: boolean
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach
|
||||
iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression
|
||||
that results in one or more elements to which
|
||||
|
@ -23976,6 +24018,9 @@ spec:
|
|||
type: object
|
||||
type: object
|
||||
type: array
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression
|
||||
that results in one or more elements to which the
|
||||
|
@ -24457,6 +24502,9 @@ spec:
|
|||
scope within the foreach block to allow referencing
|
||||
other elements in the subtree.
|
||||
type: boolean
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression
|
||||
that results in one or more elements to which the
|
||||
|
@ -27190,6 +27238,10 @@ spec:
|
|||
type: object
|
||||
type: object
|
||||
type: array
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach
|
||||
iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression
|
||||
that results in one or more elements to which
|
||||
|
@ -27515,6 +27567,10 @@ spec:
|
|||
as the validation scope within the foreach block
|
||||
to allow referencing other elements in the subtree.
|
||||
type: boolean
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach
|
||||
iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression
|
||||
that results in one or more elements to which
|
||||
|
|
|
@ -5147,6 +5147,9 @@ spec:
|
|||
type: object
|
||||
type: object
|
||||
type: array
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression
|
||||
that results in one or more elements to which the
|
||||
|
@ -5460,6 +5463,9 @@ spec:
|
|||
scope within the foreach block to allow referencing
|
||||
other elements in the subtree.
|
||||
type: boolean
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression
|
||||
that results in one or more elements to which the
|
||||
|
@ -8233,6 +8239,10 @@ spec:
|
|||
type: object
|
||||
type: object
|
||||
type: array
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach
|
||||
iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression
|
||||
that results in one or more elements to which
|
||||
|
@ -8558,6 +8568,10 @@ spec:
|
|||
as the validation scope within the foreach block
|
||||
to allow referencing other elements in the subtree.
|
||||
type: boolean
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach
|
||||
iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression
|
||||
that results in one or more elements to which
|
||||
|
@ -11049,6 +11063,9 @@ spec:
|
|||
type: object
|
||||
type: object
|
||||
type: array
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression
|
||||
that results in one or more elements to which the
|
||||
|
@ -11530,6 +11547,9 @@ spec:
|
|||
scope within the foreach block to allow referencing
|
||||
other elements in the subtree.
|
||||
type: boolean
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression
|
||||
that results in one or more elements to which the
|
||||
|
@ -14263,6 +14283,10 @@ spec:
|
|||
type: object
|
||||
type: object
|
||||
type: array
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach
|
||||
iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression
|
||||
that results in one or more elements to which
|
||||
|
@ -14588,6 +14612,10 @@ spec:
|
|||
as the validation scope within the foreach block
|
||||
to allow referencing other elements in the subtree.
|
||||
type: boolean
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach
|
||||
iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression
|
||||
that results in one or more elements to which
|
||||
|
@ -18061,6 +18089,9 @@ spec:
|
|||
type: object
|
||||
type: object
|
||||
type: array
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression
|
||||
that results in one or more elements to which the
|
||||
|
@ -18374,6 +18405,9 @@ spec:
|
|||
scope within the foreach block to allow referencing
|
||||
other elements in the subtree.
|
||||
type: boolean
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression
|
||||
that results in one or more elements to which the
|
||||
|
@ -21148,6 +21182,10 @@ spec:
|
|||
type: object
|
||||
type: object
|
||||
type: array
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach
|
||||
iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression
|
||||
that results in one or more elements to which
|
||||
|
@ -21473,6 +21511,10 @@ spec:
|
|||
as the validation scope within the foreach block
|
||||
to allow referencing other elements in the subtree.
|
||||
type: boolean
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach
|
||||
iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression
|
||||
that results in one or more elements to which
|
||||
|
@ -23965,6 +24007,9 @@ spec:
|
|||
type: object
|
||||
type: object
|
||||
type: array
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression
|
||||
that results in one or more elements to which the
|
||||
|
@ -24446,6 +24491,9 @@ spec:
|
|||
scope within the foreach block to allow referencing
|
||||
other elements in the subtree.
|
||||
type: boolean
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression
|
||||
that results in one or more elements to which the
|
||||
|
@ -27179,6 +27227,10 @@ spec:
|
|||
type: object
|
||||
type: object
|
||||
type: array
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach
|
||||
iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression
|
||||
that results in one or more elements to which
|
||||
|
@ -27504,6 +27556,10 @@ spec:
|
|||
as the validation scope within the foreach block
|
||||
to allow referencing other elements in the subtree.
|
||||
type: boolean
|
||||
foreach:
|
||||
description: Foreach declares a nested foreach
|
||||
iterator
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
list:
|
||||
description: List specifies a JMESPath expression
|
||||
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>
|
||||
</td>
|
||||
</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>
|
||||
</table>
|
||||
<hr />
|
||||
|
@ -1653,6 +1667,20 @@ Deny
|
|||
<p>Deny defines conditions used to pass or fail a validation rule.</p>
|
||||
</td>
|
||||
</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>
|
||||
</table>
|
||||
<hr />
|
||||
|
|
|
@ -45,14 +45,14 @@ var localSchemeBuilder = runtime.SchemeBuilder{
|
|||
// AddToScheme adds all types of this clientset into the given scheme. This allows composition
|
||||
// of clientsets, like in:
|
||||
//
|
||||
// import (
|
||||
// "k8s.io/client-go/kubernetes"
|
||||
// clientsetscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
// aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme"
|
||||
// )
|
||||
// import (
|
||||
// "k8s.io/client-go/kubernetes"
|
||||
// clientsetscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
// aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme"
|
||||
// )
|
||||
//
|
||||
// kclientset, _ := kubernetes.NewForConfig(c)
|
||||
// _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme)
|
||||
// kclientset, _ := kubernetes.NewForConfig(c)
|
||||
// _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme)
|
||||
//
|
||||
// After this, RawExtensions in Kubernetes types will serialize kube-aggregator types
|
||||
// correctly.
|
||||
|
|
|
@ -45,14 +45,14 @@ var localSchemeBuilder = runtime.SchemeBuilder{
|
|||
// AddToScheme adds all types of this clientset into the given scheme. This allows composition
|
||||
// of clientsets, like in:
|
||||
//
|
||||
// import (
|
||||
// "k8s.io/client-go/kubernetes"
|
||||
// clientsetscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
// aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme"
|
||||
// )
|
||||
// import (
|
||||
// "k8s.io/client-go/kubernetes"
|
||||
// clientsetscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
// aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme"
|
||||
// )
|
||||
//
|
||||
// kclientset, _ := kubernetes.NewForConfig(c)
|
||||
// _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme)
|
||||
// kclientset, _ := kubernetes.NewForConfig(c)
|
||||
// _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme)
|
||||
//
|
||||
// After this, RawExtensions in Kubernetes types will serialize kube-aggregator types
|
||||
// correctly.
|
||||
|
|
|
@ -227,7 +227,6 @@ var scanPredicate = `
|
|||
`
|
||||
|
||||
func Test_Conditions(t *testing.T) {
|
||||
|
||||
conditions := []v1.AnyAllConditions{
|
||||
{
|
||||
AnyConditions: []v1.Condition{
|
||||
|
|
|
@ -2,6 +2,7 @@ package context
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
|
@ -65,7 +66,7 @@ type Interface interface {
|
|||
AddNamespace(namespace string) error
|
||||
|
||||
// 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(info apiutils.ImageInfo) error
|
||||
|
@ -239,10 +240,14 @@ func (ctx *context) AddNamespace(namespace string) error {
|
|||
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{}{
|
||||
"element": data,
|
||||
"elementIndex": index,
|
||||
"element": data,
|
||||
nestedElement: data,
|
||||
"elementIndex": index,
|
||||
nestedElementIndex: index,
|
||||
}
|
||||
return addToContext(ctx, data)
|
||||
}
|
||||
|
|
|
@ -51,7 +51,8 @@ func Test_addResourceAndUserContext(t *testing.T) {
|
|||
userRequestInfo := urkyverno.RequestInfo{
|
||||
Roles: nil,
|
||||
ClusterRoles: nil,
|
||||
AdmissionUserInfo: userInfo}
|
||||
AdmissionUserInfo: userInfo,
|
||||
}
|
||||
|
||||
var expectedResult string
|
||||
ctx := NewContext()
|
||||
|
|
|
@ -3,12 +3,16 @@ package engine
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
|
||||
"github.com/kyverno/kyverno/pkg/engine/context"
|
||||
"github.com/kyverno/kyverno/pkg/engine/mutate"
|
||||
"github.com/kyverno/kyverno/pkg/engine/response"
|
||||
"github.com/kyverno/kyverno/pkg/engine/variables"
|
||||
"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"
|
||||
)
|
||||
|
||||
|
@ -34,30 +38,53 @@ func ForceMutate(ctx context.Interface, policy kyvernov1.PolicyInterface, resour
|
|||
}
|
||||
|
||||
if r.Mutation.ForEachMutation != nil {
|
||||
for i, foreach := range r.Mutation.ForEachMutation {
|
||||
patcher := mutate.NewPatcher(r.Name, foreach.GetPatchStrategicMerge(), foreach.PatchesJSON6902, patchedResource, ctx, logger)
|
||||
resp, mutatedResource := patcher.Patch()
|
||||
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
|
||||
patchedResource, err = applyForeachMutate(r.Name, r.Mutation.ForEachMutation, patchedResource, ctx, logger)
|
||||
if err != nil {
|
||||
return patchedResource, err
|
||||
}
|
||||
} else {
|
||||
m := r.Mutation
|
||||
patcher := mutate.NewPatcher(r.Name, m.GetPatchStrategicMerge(), m.PatchesJSON6902, patchedResource, ctx, logger)
|
||||
resp, mutatedResource := patcher.Patch()
|
||||
if resp.Status != response.RuleStatusPass {
|
||||
return patchedResource, fmt.Errorf("mutate result %q: %s", resp.Status.String(), resp.Message)
|
||||
patchedResource, err = applyPatches(r.Name, m.GetPatchStrategicMerge(), m.PatchesJSON6902, patchedResource, ctx, logger)
|
||||
if err != nil {
|
||||
return patchedResource, err
|
||||
}
|
||||
|
||||
patchedResource = mutatedResource
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
func removeConditions(rule *kyvernov1.Rule) {
|
||||
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 {
|
||||
|
||||
var cpol kyverno.ClusterPolicy
|
||||
err := json.Unmarshal([]byte(policy), &cpol)
|
||||
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 testOtherKey = `-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEpNlOGZ323zMlhs4bcKSpAKQvbcWi5ZLRmijm6SqXDy0Fp0z0Eal+BekFnLzs8rUXUaXlhZ3hNudlgFJH+nFNMw==\n-----END PUBLIC KEY-----\n`
|
||||
var (
|
||||
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) {
|
||||
policyContext := buildContext(t, testConfigMapMissing, testConfigMapMissingResource, "")
|
||||
|
@ -744,7 +745,7 @@ func Test_MarkImageVerified(t *testing.T) {
|
|||
assert.NilError(t, err)
|
||||
assert.Equal(t, len(patches), 2)
|
||||
|
||||
resource := applyPatches(t, patches)
|
||||
resource := testApplyPatches(t, patches)
|
||||
patchedAnnotations := resource.GetAnnotations()
|
||||
assert.Equal(t, len(patchedAnnotations), 1)
|
||||
|
||||
|
@ -756,7 +757,7 @@ func Test_MarkImageVerified(t *testing.T) {
|
|||
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)
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, patchedResource != nil)
|
||||
|
|
|
@ -1382,7 +1382,6 @@ func Test_Items(t *testing.T) {
|
|||
}
|
||||
for i, tc := range testCases {
|
||||
t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) {
|
||||
|
||||
query, err := New("items(`" + tc.object + "`,`" + tc.keyName + "`,`" + tc.valName + "`)")
|
||||
assert.NilError(t, err)
|
||||
|
||||
|
|
|
@ -22,11 +22,11 @@ type Response struct {
|
|||
Message string
|
||||
}
|
||||
|
||||
func newErrorResponse(msg string, err error) *Response {
|
||||
return newResponse(response.RuleStatusError, unstructured.Unstructured{}, nil, fmt.Sprintf("%s: %v", msg, err))
|
||||
func NewErrorResponse(msg string, err error) *Response {
|
||||
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{
|
||||
Status: status,
|
||||
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 {
|
||||
updatedRule, err := variables.SubstituteAllInRule(logger, ctx, *rule)
|
||||
if err != nil {
|
||||
return newErrorResponse("variable substitution failed", err)
|
||||
return NewErrorResponse("variable substitution failed", err)
|
||||
}
|
||||
|
||||
m := updatedRule.Mutation
|
||||
patcher := NewPatcher(updatedRule.Name, m.GetPatchStrategicMerge(), m.PatchesJSON6902, resource, ctx, logger)
|
||||
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()
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
fe, err := substituteAllInForEach(foreach, ctx, logger)
|
||||
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)
|
||||
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()
|
||||
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 {
|
||||
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 {
|
||||
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) {
|
||||
|
|
|
@ -68,7 +68,7 @@ func applyPatches(rule *types.Rule, resource unstructured.Unstructured) (*respon
|
|||
}
|
||||
|
||||
func TestProcessPatches_EmptyPatches(t *testing.T) {
|
||||
var emptyRule = &types.Rule{Name: "emptyRule"}
|
||||
emptyRule := &types.Rule{Name: "emptyRule"}
|
||||
resourceUnstructured, err := utils.ConvertToUnstructured([]byte(endpointsDocument))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
|
|
|
@ -182,7 +182,6 @@ spec:
|
|||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.testName, func(t *testing.T) {
|
||||
|
||||
expectedBytes, err := yaml.YAMLToJSON(testCase.expected)
|
||||
assert.Nil(t, err)
|
||||
|
||||
|
|
|
@ -11,7 +11,6 @@ import (
|
|||
)
|
||||
|
||||
func Test_GeneratePatches(t *testing.T) {
|
||||
|
||||
out, err := strategicMergePatch(logging.GlobalLogger(), string(baseBytes), string(overlayBytes))
|
||||
assert.NilError(t, err)
|
||||
|
||||
|
@ -225,7 +224,6 @@ func Test_GeneratePatches_sortRemovalPatches(t *testing.T) {
|
|||
fmt.Println(patches)
|
||||
assertnew.Nil(t, err)
|
||||
assertnew.Equal(t, expectedPatches, patches)
|
||||
|
||||
}
|
||||
|
||||
func Test_sortRemovalPatches(t *testing.T) {
|
||||
|
|
|
@ -15,6 +15,7 @@ import (
|
|||
"github.com/kyverno/kyverno/pkg/engine/response"
|
||||
"github.com/kyverno/kyverno/pkg/logging"
|
||||
"github.com/kyverno/kyverno/pkg/registryclient"
|
||||
"github.com/kyverno/kyverno/pkg/utils/api"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"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
|
||||
}
|
||||
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())
|
||||
var ruleResp *response.RuleResponse
|
||||
var mutateResp *mutate.Response
|
||||
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 {
|
||||
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 {
|
||||
resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, *ruleResp)
|
||||
if ruleResp.Status == response.RuleStatusError {
|
||||
if ruleResponse != nil {
|
||||
resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, *ruleResponse)
|
||||
if ruleResponse.Status == response.RuleStatusError {
|
||||
incrementErrorCount(resp)
|
||||
} else {
|
||||
incrementAppliedCount(resp)
|
||||
|
@ -154,81 +168,81 @@ func Mutate(ctx context.Context, rclient registryclient.Client, policyContext *P
|
|||
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())
|
||||
if err != nil {
|
||||
return ruleError(rule, response.Mutation, "failed to evaluate preconditions", err), resource
|
||||
return mutate.NewErrorResponse("failed to evaluate preconditions", err)
|
||||
}
|
||||
|
||||
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)
|
||||
ruleResp := buildRuleResponse(rule, mutateResp, &mutateResp.PatchedResource, subresourceName, parentResourceGVR)
|
||||
return ruleResp, mutateResp.PatchedResource
|
||||
return mutate.Mutate(rule, ctx.JSONContext(), resource, logger)
|
||||
}
|
||||
|
||||
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) {
|
||||
foreachList := rule.Mutation.ForEachMutation
|
||||
if foreachList == nil {
|
||||
return nil, resource
|
||||
}
|
||||
type forEachMutator struct {
|
||||
rule *kyvernov1.Rule
|
||||
policyContext *PolicyContext
|
||||
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
|
||||
allPatches := make([][]byte, 0)
|
||||
|
||||
for _, foreach := range foreachList {
|
||||
if err := LoadContext(ctx, logger, rclient, rule.Context, enginectx, rule.Name); err != nil {
|
||||
logger.Error(err, "failed to load context")
|
||||
return ruleError(rule, response.Mutation, "failed to load context", err), resource
|
||||
for _, foreach := range f.foreach {
|
||||
if err := LoadContext(ctx, f.log, f.rclient, f.rule.Context, f.policyContext, f.rule.Name); err != nil {
|
||||
f.log.Error(err, "failed to load context")
|
||||
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 {
|
||||
return ruleError(rule, response.Mutation, "failed to evaluate preconditions", err), resource
|
||||
return mutate.NewErrorResponse("failed to evaluate preconditions", err)
|
||||
}
|
||||
|
||||
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 {
|
||||
msg := fmt.Sprintf("failed to evaluate list %s", foreach.List)
|
||||
return ruleError(rule, response.Mutation, msg, err), resource
|
||||
msg := fmt.Sprintf("failed to evaluate list %s: %v", foreach.List, err)
|
||||
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 {
|
||||
logger.Error(err, "failed to mutate elements")
|
||||
return buildRuleResponse(rule, mutateResp, nil, "", metav1.GroupVersionResource{}), resource
|
||||
return mutate.NewErrorResponse("failed to mutate elements", err)
|
||||
}
|
||||
|
||||
if mutateResp.Status != response.RuleStatusSkip {
|
||||
applyCount++
|
||||
if len(mutateResp.Patches) > 0 {
|
||||
patchedResource = mutateResp.PatchedResource
|
||||
f.resource.unstructured = mutateResp.PatchedResource
|
||||
allPatches = append(allPatches, mutateResp.Patches...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
msg := fmt.Sprintf("%d elements processed", applyCount)
|
||||
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)
|
||||
r.Patches = allPatches
|
||||
return r, patchedResource
|
||||
return mutate.NewResponse(response.RuleStatusPass, f.resource.unstructured, allPatches, msg)
|
||||
}
|
||||
|
||||
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 {
|
||||
enginectx.jsonContext.Checkpoint()
|
||||
defer enginectx.jsonContext.Restore()
|
||||
func (f *forEachMutator) mutateElements(ctx context.Context, foreach kyvernov1.ForEachMutation, elements []interface{}) *mutate.Response {
|
||||
f.policyContext.JSONContext().Checkpoint()
|
||||
defer f.policyContext.JSONContext().Restore()
|
||||
|
||||
patchedResource := resource
|
||||
patchedResource := f.resource
|
||||
var allPatches [][]byte
|
||||
if foreach.RawPatchStrategicMerge != nil {
|
||||
invertedElement(elements)
|
||||
|
@ -238,63 +252,79 @@ func mutateElements(ctx context.Context, rclient registryclient.Client, name str
|
|||
if e == nil {
|
||||
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)
|
||||
|
||||
falseVar := false
|
||||
if err := addElementToContext(enginectx, e, i, &falseVar); err != nil {
|
||||
return mutateError(err, fmt.Sprintf("failed to add element to mutate.foreach[%d].context", i))
|
||||
if err := addElementToContext(policyContext, e, i, f.nesting, &falseVar); err != nil {
|
||||
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 {
|
||||
return mutateError(err, fmt.Sprintf("failed to load to mutate.foreach[%d].context", i))
|
||||
if err := LoadContext(ctx, f.log, f.rclient, foreach.Context, policyContext, f.rule.Name); err != nil {
|
||||
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 {
|
||||
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 {
|
||||
logger.Info("mutate.foreach.preconditions not met", "elementIndex", i)
|
||||
f.log.Info("mutate.foreach.preconditions not met", "elementIndex", i)
|
||||
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 {
|
||||
return mutateResp
|
||||
}
|
||||
|
||||
if len(mutateResp.Patches) > 0 {
|
||||
patchedResource = mutateResp.PatchedResource
|
||||
patchedResource.unstructured = mutateResp.PatchedResource
|
||||
allPatches = append(allPatches, mutateResp.Patches...)
|
||||
}
|
||||
}
|
||||
|
||||
return &mutate.Response{
|
||||
Status: response.RuleStatusPass,
|
||||
PatchedResource: patchedResource,
|
||||
Patches: allPatches,
|
||||
Message: "foreach mutation applied",
|
||||
}
|
||||
return mutate.NewResponse(response.RuleStatusPass, patchedResource.unstructured, allPatches, "")
|
||||
}
|
||||
|
||||
func mutateError(err error, message string) *mutate.Response {
|
||||
return &mutate.Response{
|
||||
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)
|
||||
func buildRuleResponse(rule *kyvernov1.Rule, mutateResp *mutate.Response, info resourceInfo) *response.RuleResponse {
|
||||
resp := ruleResponse(*rule, response.Mutation, mutateResp.Message, mutateResp.Status)
|
||||
if resp.Status == response.RuleStatusPass {
|
||||
resp.Patches = mutateResp.Patches
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
@ -93,7 +93,8 @@ func Test_VariableSubstitutionPatchStrategicMerge(t *testing.T) {
|
|||
policyContext := &PolicyContext{
|
||||
policy: &policy,
|
||||
jsonContext: ctx,
|
||||
newResource: *resourceUnstructured}
|
||||
newResource: *resourceUnstructured,
|
||||
}
|
||||
er := Mutate(context.TODO(), registryclient.NewOrDie(), policyContext)
|
||||
t.Log(string(expectedPatch))
|
||||
|
||||
|
@ -166,7 +167,8 @@ func Test_variableSubstitutionPathNotExist(t *testing.T) {
|
|||
policyContext := &PolicyContext{
|
||||
policy: &policy,
|
||||
jsonContext: ctx,
|
||||
newResource: *resourceUnstructured}
|
||||
newResource: *resourceUnstructured,
|
||||
}
|
||||
er := Mutate(context.TODO(), registryclient.NewOrDie(), policyContext)
|
||||
assert.Equal(t, len(er.PolicyResponse.Rules), 1)
|
||||
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
|
||||
err := json.Unmarshal(policyRaw, &policy)
|
||||
assert.NilError(t, err)
|
||||
|
@ -1013,22 +1038,127 @@ func Test_foreach_order_mutation_(t *testing.T) {
|
|||
assert.NilError(t, err)
|
||||
|
||||
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, 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)
|
||||
|
||||
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")
|
||||
for _, e := range tlsArr {
|
||||
tls := e.(map[string]interface{})
|
||||
hosts := tls["hosts"].([]interface{})
|
||||
for _, h := range hosts {
|
||||
s := h.(string)
|
||||
assert.Assert(t, strings.HasSuffix(s, ".newfoo.com"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ import (
|
|||
kyvernov1beta1 "github.com/kyverno/kyverno/api/kyverno/v1beta1"
|
||||
"github.com/kyverno/kyverno/pkg/clients/dclient"
|
||||
"github.com/kyverno/kyverno/pkg/config"
|
||||
"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/utils"
|
||||
|
@ -54,7 +53,7 @@ type PolicyContext struct {
|
|||
excludeResourceFunc ExcludeFunc
|
||||
|
||||
// jsonContext is the variable context
|
||||
jsonContext context.Interface
|
||||
jsonContext enginectx.Interface
|
||||
|
||||
// namespaceLabels stores the label of namespace to be processed by namespace selector
|
||||
namespaceLabels map[string]string
|
||||
|
@ -95,7 +94,7 @@ func (c *PolicyContext) AdmissionInfo() kyvernov1beta1.RequestInfo {
|
|||
return c.admissionInfo
|
||||
}
|
||||
|
||||
func (c *PolicyContext) JSONContext() context.Interface {
|
||||
func (c *PolicyContext) JSONContext() enginectx.Interface {
|
||||
return c.jsonContext
|
||||
}
|
||||
|
||||
|
@ -193,7 +192,7 @@ func (c *PolicyContext) WithSubresourcesInPolicy(subresourcesInPolicy []struct {
|
|||
|
||||
// Constructors
|
||||
|
||||
func NewPolicyContextWithJsonContext(jsonContext context.Interface) *PolicyContext {
|
||||
func NewPolicyContextWithJsonContext(jsonContext enginectx.Interface) *PolicyContext {
|
||||
return &PolicyContext{
|
||||
jsonContext: jsonContext,
|
||||
excludeGroupRole: []string{},
|
||||
|
@ -204,7 +203,7 @@ func NewPolicyContextWithJsonContext(jsonContext context.Interface) *PolicyConte
|
|||
}
|
||||
|
||||
func NewPolicyContext() *PolicyContext {
|
||||
return NewPolicyContextWithJsonContext(context.NewContext())
|
||||
return NewPolicyContextWithJsonContext(enginectx.NewContext())
|
||||
}
|
||||
|
||||
func NewPolicyContextFromAdmissionRequest(
|
||||
|
|
|
@ -468,22 +468,6 @@ func ruleResponse(rule kyvernov1.Rule, ruleType response.RuleType, msg string, s
|
|||
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) {
|
||||
resp.PolicyResponse.RulesAppliedCount++
|
||||
}
|
||||
|
|
|
@ -1867,7 +1867,6 @@ func TestResourceDescriptionMatch_MultipleKind(t *testing.T) {
|
|||
resource, err := utils.ConvertToUnstructured(rawResource)
|
||||
if err != nil {
|
||||
t.Errorf("unable to convert raw resource to unstructured: %v", err)
|
||||
|
||||
}
|
||||
resourceDescription := v1.ResourceDescription{
|
||||
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 {
|
||||
t.Errorf("Testcase has failed due to the following:%v", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Match resource name
|
||||
|
@ -1927,7 +1925,6 @@ func TestResourceDescriptionMatch_Name(t *testing.T) {
|
|||
resource, err := utils.ConvertToUnstructured(rawResource)
|
||||
if err != nil {
|
||||
t.Errorf("unable to convert raw resource to unstructured: %v", err)
|
||||
|
||||
}
|
||||
resourceDescription := v1.ResourceDescription{
|
||||
Kinds: []string{"Deployment"},
|
||||
|
@ -1986,7 +1983,6 @@ func TestResourceDescriptionMatch_GenerateName(t *testing.T) {
|
|||
resource, err := utils.ConvertToUnstructured(rawResource)
|
||||
if err != nil {
|
||||
t.Errorf("unable to convert raw resource to unstructured: %v", err)
|
||||
|
||||
}
|
||||
resourceDescription := v1.ResourceDescription{
|
||||
Kinds: []string{"Deployment"},
|
||||
|
@ -2046,7 +2042,6 @@ func TestResourceDescriptionMatch_Name_Regex(t *testing.T) {
|
|||
resource, err := utils.ConvertToUnstructured(rawResource)
|
||||
if err != nil {
|
||||
t.Errorf("unable to convert raw resource to unstructured: %v", err)
|
||||
|
||||
}
|
||||
resourceDescription := v1.ResourceDescription{
|
||||
Kinds: []string{"Deployment"},
|
||||
|
@ -2105,7 +2100,6 @@ func TestResourceDescriptionMatch_GenerateName_Regex(t *testing.T) {
|
|||
resource, err := utils.ConvertToUnstructured(rawResource)
|
||||
if err != nil {
|
||||
t.Errorf("unable to convert raw resource to unstructured: %v", err)
|
||||
|
||||
}
|
||||
resourceDescription := v1.ResourceDescription{
|
||||
Kinds: []string{"Deployment"},
|
||||
|
@ -2165,7 +2159,6 @@ func TestResourceDescriptionMatch_Label_Expression_NotMatch(t *testing.T) {
|
|||
resource, err := utils.ConvertToUnstructured(rawResource)
|
||||
if err != nil {
|
||||
t.Errorf("unable to convert raw resource to unstructured: %v", err)
|
||||
|
||||
}
|
||||
resourceDescription := v1.ResourceDescription{
|
||||
Kinds: []string{"Deployment"},
|
||||
|
@ -2233,7 +2226,6 @@ func TestResourceDescriptionMatch_Label_Expression_Match(t *testing.T) {
|
|||
resource, err := utils.ConvertToUnstructured(rawResource)
|
||||
if err != nil {
|
||||
t.Errorf("unable to convert raw resource to unstructured: %v", err)
|
||||
|
||||
}
|
||||
resourceDescription := v1.ResourceDescription{
|
||||
Kinds: []string{"Deployment"},
|
||||
|
@ -2303,7 +2295,6 @@ func TestResourceDescriptionExclude_Label_Expression_Match(t *testing.T) {
|
|||
resource, err := utils.ConvertToUnstructured(rawResource)
|
||||
if err != nil {
|
||||
t.Errorf("unable to convert raw resource to unstructured: %v", err)
|
||||
|
||||
}
|
||||
resourceDescription := v1.ResourceDescription{
|
||||
Kinds: []string{"Deployment"},
|
||||
|
@ -2331,8 +2322,10 @@ func TestResourceDescriptionExclude_Label_Expression_Match(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
rule := v1.Rule{MatchResources: v1.MatchResources{ResourceDescription: resourceDescription},
|
||||
ExcludeResources: v1.MatchResources{ResourceDescription: resourceDescriptionExclude}}
|
||||
rule := v1.Rule{
|
||||
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 {
|
||||
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) {
|
||||
|
||||
testSelector(t, &metav1.LabelSelector{}, map[string]string{}, 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) {
|
||||
|
||||
// test single annotation values
|
||||
testAnnotationMatch(t, map[string]string{}, map[string]string{}, true)
|
||||
testAnnotationMatch(t, map[string]string{"test/*": "*"}, map[string]string{}, false)
|
||||
|
|
|
@ -1667,7 +1667,8 @@ func testMatchPattern(t *testing.T, testCase struct {
|
|||
pattern []byte
|
||||
resource []byte
|
||||
status response.RuleStatus
|
||||
}) {
|
||||
},
|
||||
) {
|
||||
var pattern, resource interface{}
|
||||
err := json.Unmarshal(testCase.pattern, &pattern)
|
||||
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))
|
||||
} 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))
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import (
|
|||
"github.com/kyverno/kyverno/pkg/pss"
|
||||
"github.com/kyverno/kyverno/pkg/registryclient"
|
||||
"github.com/kyverno/kyverno/pkg/utils"
|
||||
"github.com/kyverno/kyverno/pkg/utils/api"
|
||||
"github.com/pkg/errors"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
batchv1 "k8s.io/api/batch/v1"
|
||||
|
@ -147,12 +148,8 @@ func validateResource(ctx context.Context, log logr.Logger, rclient registryclie
|
|||
return resp
|
||||
}
|
||||
|
||||
func processValidationRule(ctx context.Context, log logr.Logger, rclient registryclient.Client, enginectx *PolicyContext, rule *kyvernov1.Rule) *response.RuleResponse {
|
||||
v := newValidator(log, rclient, enginectx, rule)
|
||||
if rule.Validation.ForEachValidation != nil {
|
||||
return v.validateForEach(ctx)
|
||||
}
|
||||
|
||||
func processValidationRule(ctx context.Context, log logr.Logger, rclient registryclient.Client, policyContext *PolicyContext, rule *kyvernov1.Rule) *response.RuleResponse {
|
||||
v := newValidator(log, rclient, policyContext, rule)
|
||||
return v.validate(ctx)
|
||||
}
|
||||
|
||||
|
@ -172,7 +169,7 @@ func addRuleResponse(log logr.Logger, resp *response.EngineResponse, ruleResp *r
|
|||
|
||||
type validator struct {
|
||||
log logr.Logger
|
||||
ctx *PolicyContext
|
||||
policyContext *PolicyContext
|
||||
rule *kyvernov1.Rule
|
||||
contextEntries []kyvernov1.ContextEntry
|
||||
anyAllConditions apiextensions.JSON
|
||||
|
@ -180,7 +177,9 @@ type validator struct {
|
|||
anyPattern apiextensions.JSON
|
||||
deny *kyvernov1.Deny
|
||||
podSecurity *kyvernov1.PodSecurity
|
||||
foreach []kyvernov1.ForEachValidation
|
||||
rclient registryclient.Client
|
||||
nesting int
|
||||
}
|
||||
|
||||
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{
|
||||
log: log,
|
||||
rule: ruleCopy,
|
||||
ctx: ctx,
|
||||
policyContext: ctx,
|
||||
rclient: rclient,
|
||||
contextEntries: ruleCopy.Context,
|
||||
anyAllConditions: ruleCopy.GetAnyAllConditions(),
|
||||
pattern: ruleCopy.Validation.GetPattern(),
|
||||
anyPattern: ruleCopy.Validation.GetAnyPattern(),
|
||||
deny: ruleCopy.Validation.Deny,
|
||||
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()
|
||||
anyAllConditions, err := utils.ToMap(foreach.AnyAllConditions)
|
||||
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{
|
||||
log: log,
|
||||
ctx: ctx,
|
||||
policyContext: ctx,
|
||||
rule: ruleCopy,
|
||||
rclient: rclient,
|
||||
contextEntries: foreach.Context,
|
||||
anyAllConditions: anyAllConditions,
|
||||
pattern: foreach.GetPattern(),
|
||||
anyPattern: foreach.GetAnyPattern(),
|
||||
deny: foreach.Deny,
|
||||
rclient: rclient,
|
||||
}
|
||||
foreach: nestedForeach,
|
||||
nesting: nesting,
|
||||
}, nil
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
preconditionsPassed, err := checkPreconditions(v.log, v.ctx, v.anyAllConditions)
|
||||
preconditionsPassed, err := checkPreconditions(v.log, v.policyContext, v.anyAllConditions)
|
||||
if err != nil {
|
||||
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()
|
||||
|
||||
return ruleResponse
|
||||
}
|
||||
|
||||
if v.podSecurity != nil {
|
||||
if !isDeleteRequest(v.ctx) {
|
||||
if !isDeleteRequest(v.policyContext) {
|
||||
ruleResponse := v.validatePodSecurity()
|
||||
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")
|
||||
return nil
|
||||
}
|
||||
|
||||
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
|
||||
if foreachList == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, foreach := range foreachList {
|
||||
elements, err := evaluateList(foreach.List, v.ctx.jsonContext)
|
||||
for _, foreach := range v.foreach {
|
||||
elements, err := evaluateList(foreach.List, (v.policyContext.JSONContext()))
|
||||
if err != nil {
|
||||
v.log.V(2).Info("failed to evaluate list", "list", foreach.List, "error", err.Error())
|
||||
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 {
|
||||
return resp
|
||||
}
|
||||
|
@ -291,31 +287,42 @@ func (v *validator) validateForEach(ctx context.Context) *response.RuleResponse
|
|||
}
|
||||
|
||||
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 passed", response.RuleStatusPass)
|
||||
}
|
||||
|
||||
func (v *validator) validateElements(ctx context.Context, foreach kyvernov1.ForEachValidation, elements []interface{}, elementScope *bool) (*response.RuleResponse, int) {
|
||||
v.ctx.jsonContext.Checkpoint()
|
||||
defer v.ctx.jsonContext.Restore()
|
||||
func (v *validator) validateElements(ctx context.Context, rclient registryclient.Client, foreach kyvernov1.ForEachValidation, elements []interface{}, elementScope *bool) (*response.RuleResponse, int) {
|
||||
v.policyContext.jsonContext.Checkpoint()
|
||||
defer v.policyContext.jsonContext.Restore()
|
||||
applyCount := 0
|
||||
|
||||
for i, e := range elements {
|
||||
if e == nil {
|
||||
continue
|
||||
}
|
||||
store.SetForeachElement(i)
|
||||
v.ctx.jsonContext.Reset()
|
||||
|
||||
enginectx := v.ctx.Copy()
|
||||
if err := addElementToContext(enginectx, e, i, elementScope); err != nil {
|
||||
// TODO - this needs to be refactored. The engine should not have a dependency to the CLI code
|
||||
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")
|
||||
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)
|
||||
if r == nil {
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
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 {
|
||||
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 {
|
||||
v.log.V(3).Info("failed to load context", "reason", err.Error())
|
||||
} else {
|
||||
|
@ -390,7 +397,7 @@ func (v *validator) loadContext(ctx context.Context) error {
|
|||
|
||||
func (v *validator) validateDeny() *response.RuleResponse {
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
|
||||
deny := variables.EvaluateConditions(v.log, v.ctx.jsonContext, denyConditions)
|
||||
deny := variables.EvaluateConditions(v.log, v.policyContext.jsonContext, denyConditions)
|
||||
if deny {
|
||||
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)
|
||||
}
|
||||
|
||||
raw, err := variables.SubstituteAll(v.log, v.ctx.jsonContext, msg)
|
||||
raw, err := variables.SubstituteAll(v.log, v.policyContext.jsonContext, msg)
|
||||
if err != nil {
|
||||
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) {
|
||||
kind := v.ctx.newResource.GetKind()
|
||||
kind := v.policyContext.newResource.GetKind()
|
||||
|
||||
if kind == "DaemonSet" || kind == "Deployment" || kind == "Job" || kind == "StatefulSet" || kind == "ReplicaSet" || kind == "ReplicationController" {
|
||||
var deployment appsv1.Deployment
|
||||
|
||||
resourceBytes, err := v.ctx.newResource.MarshalJSON()
|
||||
resourceBytes, err := v.policyContext.newResource.MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -450,7 +457,7 @@ func getSpec(v *validator) (podSpec *corev1.PodSpec, metadata *metav1.ObjectMeta
|
|||
} else if kind == "CronJob" {
|
||||
var cronJob batchv1.CronJob
|
||||
|
||||
resourceBytes, err := v.ctx.newResource.MarshalJSON()
|
||||
resourceBytes, err := v.policyContext.newResource.MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -463,7 +470,7 @@ func getSpec(v *validator) (podSpec *corev1.PodSpec, metadata *metav1.ObjectMeta
|
|||
} else if kind == "Pod" {
|
||||
var pod corev1.Pod
|
||||
|
||||
resourceBytes, err := v.ctx.newResource.MarshalJSON()
|
||||
resourceBytes, err := v.policyContext.newResource.MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -508,16 +515,16 @@ func (v *validator) validatePodSecurity() *response.RuleResponse {
|
|||
}
|
||||
|
||||
func (v *validator) validateResourceWithRule() *response.RuleResponse {
|
||||
if !isEmptyUnstructured(&v.ctx.element) {
|
||||
return v.validatePatterns(v.ctx.element)
|
||||
if !isEmptyUnstructured(&v.policyContext.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")
|
||||
return nil
|
||||
}
|
||||
|
||||
resp := v.validatePatterns(v.ctx.newResource)
|
||||
resp := v.validatePatterns(v.policyContext.newResource)
|
||||
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())
|
||||
}
|
||||
|
||||
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 {
|
||||
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())
|
||||
|
@ -704,7 +711,7 @@ func buildAnyPatternErrorMessage(rule *kyvernov1.Rule, errors []string) string {
|
|||
|
||||
func (v *validator) substitutePatterns() error {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
@ -714,7 +721,7 @@ func (v *validator) substitutePatterns() error {
|
|||
}
|
||||
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
@ -731,7 +738,7 @@ func (v *validator) substituteDeny() error {
|
|||
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 {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -1479,7 +1479,8 @@ func Test_VariableSubstitutionPathNotExistInPattern(t *testing.T) {
|
|||
policyContext := &PolicyContext{
|
||||
policy: &policy,
|
||||
jsonContext: ctx,
|
||||
newResource: *resourceUnstructured}
|
||||
newResource: *resourceUnstructured,
|
||||
}
|
||||
er := Validate(context.TODO(), registryclient.NewOrDie(), policyContext)
|
||||
|
||||
assert.Equal(t, len(er.PolicyResponse.Rules), 1)
|
||||
|
@ -1572,7 +1573,8 @@ func Test_VariableSubstitutionPathNotExistInAnyPattern_OnePatternStatisfiesButSu
|
|||
policyContext := &PolicyContext{
|
||||
policy: &policy,
|
||||
jsonContext: ctx,
|
||||
newResource: *resourceUnstructured}
|
||||
newResource: *resourceUnstructured,
|
||||
}
|
||||
er := Validate(context.TODO(), registryclient.NewOrDie(), policyContext)
|
||||
|
||||
assert.Equal(t, len(er.PolicyResponse.Rules), 1)
|
||||
|
@ -1633,7 +1635,8 @@ func Test_VariableSubstitution_NotOperatorWithStringVariable(t *testing.T) {
|
|||
policyContext := &PolicyContext{
|
||||
policy: &policy,
|
||||
jsonContext: ctx,
|
||||
newResource: *resourceUnstructured}
|
||||
newResource: *resourceUnstructured,
|
||||
}
|
||||
er := Validate(context.TODO(), registryclient.NewOrDie(), policyContext)
|
||||
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/")
|
||||
|
@ -1724,7 +1727,8 @@ func Test_VariableSubstitutionPathNotExistInAnyPattern_AllPathNotPresent(t *test
|
|||
policyContext := &PolicyContext{
|
||||
policy: &policy,
|
||||
jsonContext: ctx,
|
||||
newResource: *resourceUnstructured}
|
||||
newResource: *resourceUnstructured,
|
||||
}
|
||||
er := Validate(context.TODO(), registryclient.NewOrDie(), policyContext)
|
||||
|
||||
assert.Equal(t, len(er.PolicyResponse.Rules), 1)
|
||||
|
@ -1817,7 +1821,8 @@ func Test_VariableSubstitutionPathNotExistInAnyPattern_AllPathPresent_NonePatter
|
|||
policyContext := &PolicyContext{
|
||||
policy: &policy,
|
||||
jsonContext: ctx,
|
||||
newResource: *resourceUnstructured}
|
||||
newResource: *resourceUnstructured,
|
||||
}
|
||||
er := Validate(context.TODO(), registryclient.NewOrDie(), policyContext)
|
||||
|
||||
assert.Equal(t, er.PolicyResponse.Rules[0].Status, response.RuleStatusFail)
|
||||
|
@ -1922,7 +1927,8 @@ func Test_VariableSubstitutionValidate_VariablesInMessageAreResolved(t *testing.
|
|||
policyContext := &PolicyContext{
|
||||
policy: &policy,
|
||||
jsonContext: ctx,
|
||||
newResource: *resourceUnstructured}
|
||||
newResource: *resourceUnstructured,
|
||||
}
|
||||
er := Validate(context.TODO(), registryclient.NewOrDie(), policyContext)
|
||||
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.")
|
||||
|
@ -1975,7 +1981,8 @@ func Test_Flux_Kustomization_PathNotPresent(t *testing.T) {
|
|||
policyContext := &PolicyContext{
|
||||
policy: &policy,
|
||||
jsonContext: ctx,
|
||||
newResource: *resourceUnstructured}
|
||||
newResource: *resourceUnstructured,
|
||||
}
|
||||
er := Validate(context.TODO(), registryclient.NewOrDie(), policyContext)
|
||||
|
||||
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) {
|
||||
|
||||
resourceRaw := []byte(`{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Deployment",
|
||||
|
@ -2752,7 +2758,6 @@ func Test_foreach_context_preconditions(t *testing.T) {
|
|||
}
|
||||
|
||||
func Test_foreach_context_preconditions_fail(t *testing.T) {
|
||||
|
||||
resourceRaw := []byte(`{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Deployment",
|
||||
|
@ -2847,7 +2852,6 @@ func Test_foreach_context_preconditions_fail(t *testing.T) {
|
|||
}
|
||||
|
||||
func Test_foreach_element_validation(t *testing.T) {
|
||||
|
||||
resourceRaw := []byte(`{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Pod",
|
||||
|
@ -2895,7 +2899,6 @@ func Test_foreach_element_validation(t *testing.T) {
|
|||
}
|
||||
|
||||
func Test_outof_foreach_element_validation(t *testing.T) {
|
||||
|
||||
resourceRaw := []byte(`{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Pod",
|
||||
|
@ -2938,7 +2941,6 @@ func Test_outof_foreach_element_validation(t *testing.T) {
|
|||
}
|
||||
|
||||
func Test_foreach_skip_initContainer_pass(t *testing.T) {
|
||||
|
||||
resourceRaw := []byte(`{"apiVersion": "v1",
|
||||
"kind": "Deployment",
|
||||
"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": {
|
||||
"image": "trusted-registry.io/*"
|
||||
}
|
||||
|
@ -2992,6 +2994,102 @@ func Test_foreach_skip_initContainer_pass(t *testing.T) {
|
|||
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) {
|
||||
var policy kyverno.ClusterPolicy
|
||||
assert.NilError(t, json.Unmarshal(policyraw, &policy))
|
||||
|
@ -3005,7 +3103,8 @@ func testForEach(t *testing.T, policyraw []byte, resourceRaw []byte, msg string,
|
|||
policyContext := &PolicyContext{
|
||||
policy: &policy,
|
||||
jsonContext: ctx,
|
||||
newResource: *resourceUnstructured}
|
||||
newResource: *resourceUnstructured,
|
||||
}
|
||||
er := Validate(context.TODO(), registryclient.NewOrDie(), policyContext)
|
||||
|
||||
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) {
|
||||
|
||||
resourceRaw := []byte(`{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Pod",
|
||||
|
@ -3069,7 +3167,8 @@ func Test_delete_ignore_pattern(t *testing.T) {
|
|||
policyContextCreate := &PolicyContext{
|
||||
policy: &policy,
|
||||
jsonContext: ctx,
|
||||
newResource: *resourceUnstructured}
|
||||
newResource: *resourceUnstructured,
|
||||
}
|
||||
engineResponseCreate := Validate(context.TODO(), registryclient.NewOrDie(), policyContextCreate)
|
||||
assert.Equal(t, len(engineResponseCreate.PolicyResponse.Rules), 1)
|
||||
assert.Equal(t, engineResponseCreate.PolicyResponse.Rules[0].Status, response.RuleStatusFail)
|
||||
|
@ -3077,7 +3176,8 @@ func Test_delete_ignore_pattern(t *testing.T) {
|
|||
policyContextDelete := &PolicyContext{
|
||||
policy: &policy,
|
||||
jsonContext: ctx,
|
||||
oldResource: *resourceUnstructured}
|
||||
oldResource: *resourceUnstructured,
|
||||
}
|
||||
engineResponseDelete := Validate(context.TODO(), registryclient.NewOrDie(), policyContextDelete)
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_variablesubstitution(t *testing.T) {
|
||||
patternMap := []byte(`
|
||||
{
|
||||
|
@ -277,7 +278,6 @@ func Test_variablesubstitution(t *testing.T) {
|
|||
}
|
||||
|
||||
func Test_variableSubstitutionValue(t *testing.T) {
|
||||
|
||||
resourceRaw := []byte(`
|
||||
{
|
||||
"metadata": {
|
||||
|
@ -336,7 +336,6 @@ func Test_variableSubstitutionValue(t *testing.T) {
|
|||
}
|
||||
|
||||
func Test_variableSubstitutionValueOperatorNotEqual(t *testing.T) {
|
||||
|
||||
resourceRaw := []byte(`
|
||||
{
|
||||
"metadata": {
|
||||
|
@ -396,7 +395,6 @@ func Test_variableSubstitutionValueOperatorNotEqual(t *testing.T) {
|
|||
}
|
||||
|
||||
func Test_variableSubstitutionValueFail(t *testing.T) {
|
||||
|
||||
resourceRaw := []byte(`
|
||||
{
|
||||
"metadata": {
|
||||
|
@ -444,7 +442,6 @@ func Test_variableSubstitutionValueFail(t *testing.T) {
|
|||
t.Log("expected to fails")
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func Test_variableSubstitutionObject(t *testing.T) {
|
||||
|
|
|
@ -15,10 +15,11 @@ import (
|
|||
"github.com/kyverno/kyverno/pkg/engine/context"
|
||||
jsonUtils "github.com/kyverno/kyverno/pkg/engine/jsonutils"
|
||||
"github.com/kyverno/kyverno/pkg/engine/operator"
|
||||
"github.com/kyverno/kyverno/pkg/logging"
|
||||
"github.com/kyverno/kyverno/pkg/utils/jsonpointer"
|
||||
)
|
||||
|
||||
var RegexVariables = regexp.MustCompile(`(?:^|[^\\])(\{\{(?:\{[^{}]*\}|[^{}])*\}\})`)
|
||||
var RegexVariables = regexp.MustCompile(`(^|[^\\])(\{\{(?:\{[^{}]*\}|[^{}])*\}\})`)
|
||||
|
||||
var RegexEscpVariables = regexp.MustCompile(`\\\{\{(\{[^{}]*\}|[^{}])*\}\}`)
|
||||
|
||||
|
@ -30,7 +31,7 @@ var RegexEscpReferences = 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 {{}}
|
||||
func IsVariable(value string) bool {
|
||||
|
@ -569,12 +570,13 @@ func replaceSubstituteVariables(document interface{}) interface{} {
|
|||
break
|
||||
}
|
||||
|
||||
rawDocument = RegexVariables.ReplaceAll(rawDocument, []byte(`placeholderValue`))
|
||||
rawDocument = RegexVariables.ReplaceAll(rawDocument, []byte(`${1}placeholderValue`))
|
||||
}
|
||||
|
||||
var output interface{}
|
||||
err = json.Unmarshal(rawDocument, &output)
|
||||
if err != nil {
|
||||
logging.Error(err, "failed to unmarshall JSON: %s", string(rawDocument))
|
||||
return document
|
||||
}
|
||||
|
||||
|
|
|
@ -365,7 +365,7 @@ func Test_subVars_withRegexReplaceAll(t *testing.T) {
|
|||
func Test_ReplacingPathWhenDeleting(t *testing.T) {
|
||||
patternRaw := []byte(`"{{request.object.metadata.annotations.target}}"`)
|
||||
|
||||
var resourceRaw = []byte(`
|
||||
resourceRaw := []byte(`
|
||||
{
|
||||
"request": {
|
||||
"operation": "DELETE",
|
||||
|
@ -408,7 +408,7 @@ func Test_ReplacingPathWhenDeleting(t *testing.T) {
|
|||
func Test_ReplacingNestedVariableWhenDeleting(t *testing.T) {
|
||||
patternRaw := []byte(`"{{request.object.metadata.annotations.{{request.object.metadata.annotations.targetnew}}}}"`)
|
||||
|
||||
var resourceRaw = []byte(`
|
||||
resourceRaw := []byte(`
|
||||
{
|
||||
"request":{
|
||||
"operation":"DELETE",
|
||||
|
@ -468,8 +468,8 @@ func Test_SubstituteSuccess(t *testing.T) {
|
|||
results, err := action(&ju.ActionData{
|
||||
Document: nil,
|
||||
Element: string(patternRaw),
|
||||
Path: "/"})
|
||||
|
||||
Path: "/",
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("substitution failed: %v", err.Error())
|
||||
return
|
||||
|
@ -492,7 +492,8 @@ func Test_SubstituteRecursiveErrors(t *testing.T) {
|
|||
results, err := action(&ju.ActionData{
|
||||
Document: nil,
|
||||
Element: string(patternRaw),
|
||||
Path: "/"})
|
||||
Path: "/",
|
||||
})
|
||||
|
||||
if err == nil {
|
||||
t.Errorf("expected error but received: %v", results)
|
||||
|
@ -505,7 +506,8 @@ func Test_SubstituteRecursiveErrors(t *testing.T) {
|
|||
results, err = action(&ju.ActionData{
|
||||
Document: nil,
|
||||
Element: string(patternRaw),
|
||||
Path: "/"})
|
||||
Path: "/",
|
||||
})
|
||||
|
||||
if err == nil {
|
||||
t.Errorf("expected error but received: %v", results)
|
||||
|
@ -524,8 +526,8 @@ func Test_SubstituteRecursive(t *testing.T) {
|
|||
results, err := action(&ju.ActionData{
|
||||
Document: nil,
|
||||
Element: string(patternRaw),
|
||||
Path: "/"})
|
||||
|
||||
Path: "/",
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("substitution failed: %v", err.Error())
|
||||
return
|
||||
|
@ -1146,7 +1148,7 @@ func Test_EscpReferenceSubstitution(t *testing.T) {
|
|||
func Test_ReplacingEscpNestedVariableWhenDeleting(t *testing.T) {
|
||||
patternRaw := []byte(`"\\{{request.object.metadata.annotations.{{request.object.metadata.annotations.targetnew}}}}"`)
|
||||
|
||||
var resourceRaw = []byte(`
|
||||
resourceRaw := []byte(`
|
||||
{
|
||||
"request":{
|
||||
"operation":"DELETE",
|
||||
|
@ -1177,3 +1179,38 @@ func Test_ReplacingEscpNestedVariableWhenDeleting(t *testing.T) {
|
|||
|
||||
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) {
|
||||
//testExpand(t, map[string]string{"test/*": "*"}, map[string]string{},
|
||||
// testExpand(t, map[string]string{"test/*": "*"}, map[string]string{},
|
||||
// map[string]string{"test/0": "0"})
|
||||
|
||||
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"}]}}}}]}}`),
|
||||
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()
|
||||
|
|
|
@ -31,8 +31,8 @@ func containsUserVariables(policy kyvernov1.PolicyInterface, vars [][]string) er
|
|||
}
|
||||
for _, s := range vars {
|
||||
for _, banned := range forbidden {
|
||||
if banned.Match([]byte(s[1])) {
|
||||
return fmt.Errorf("variable %s is not allowed", s[1])
|
||||
if banned.Match([]byte(s[2])) {
|
||||
return fmt.Errorf("variable %s is not allowed", s[2])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,9 @@ import (
|
|||
"fmt"
|
||||
|
||||
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
|
||||
|
@ -21,7 +24,11 @@ func NewMutateFactory(m kyvernov1.Mutation) *Mutate {
|
|||
// Validate validates the 'mutate' rule
|
||||
func (m *Mutate) Validate() (string, error) {
|
||||
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() {
|
||||
|
@ -31,21 +38,35 @@ func (m *Mutate) Validate() (string, error) {
|
|||
return "", nil
|
||||
}
|
||||
|
||||
func (m *Mutate) validateForEach() (string, error) {
|
||||
if m.hasPatchStrategicMerge() || m.hasPatchesJSON6902() {
|
||||
return "foreach", fmt.Errorf("only one of `foreach`, `patchStrategicMerge`, or `patchesJson6902` is allowed")
|
||||
}
|
||||
func (m *Mutate) validateForEach(tag string, foreach []kyvernov1.ForEachMutation) (string, error) {
|
||||
for i, fe := range foreach {
|
||||
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()
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
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