diff --git a/.github/actions/kyverno-logs/action.yaml b/.github/actions/kyverno-logs/action.yaml index 9cd0485c95..10ccde2156 100644 --- a/.github/actions/kyverno-logs/action.yaml +++ b/.github/actions/kyverno-logs/action.yaml @@ -14,15 +14,9 @@ runs: run: | kubectl -n kyverno get pod kubectl -n kyverno describe pod | grep -i events -A10 - - shell: bash - run: | - kubectl -n kyverno logs deploy/kyverno-admission-controller --all-containers -p || true - kubectl -n kyverno logs deploy/kyverno-reports-controller --all-containers -p || true - kubectl -n kyverno logs deploy/kyverno-cleanup-controller --all-containers -p || true - kubectl -n kyverno logs deploy/kyverno-background-controller --all-containers -p || true - shell: bash run: | kubectl -n kyverno logs deploy/kyverno-admission-controller --all-containers + kubectl -n kyverno logs deploy/kyverno-background-controller --all-containers kubectl -n kyverno logs deploy/kyverno-reports-controller --all-containers kubectl -n kyverno logs deploy/kyverno-cleanup-controller --all-containers - kubectl -n kyverno logs deploy/kyverno-background-controller --all-containers diff --git a/Makefile b/Makefile index 3a50e8fe0f..b607859d49 100644 --- a/Makefile +++ b/Makefile @@ -501,7 +501,7 @@ codegen-client-all: codegen-client-wrappers codegen-crds-kyverno: ## Generate kyverno CRDs @echo Generate kyverno crds... >&2 @rm -rf $(CRDS_PATH)/kyverno && mkdir -p $(CRDS_PATH)/kyverno - @go run ./hack/controller-gen -- paths=./api/kyverno/... crd:crdVersions=v1,ignoreUnexportedFields=true,generateEmbeddedObjectMeta=false output:dir=$(CRDS_PATH)/kyverno + @go run ./hack/controller-gen -- paths=./api/kyverno/v1/... paths=./api/kyverno/v2/... paths=./api/kyverno/v2alpha1/... paths=./api/kyverno/v2beta1/... crd:crdVersions=v1,ignoreUnexportedFields=true,generateEmbeddedObjectMeta=false output:dir=$(CRDS_PATH)/kyverno .PHONY: codegen-crds-policyreport codegen-crds-policyreport: ## Generate policy reports CRDs diff --git a/api/kyverno/v2/constants.go b/api/kyverno/v2/constants.go index 09977515a3..d10de6903c 100644 --- a/api/kyverno/v2/constants.go +++ b/api/kyverno/v2/constants.go @@ -10,9 +10,5 @@ const ( // URGeneratePolicyLabel adds the policy name to URs for generate policies URGeneratePolicyLabel = "generate.kyverno.io/policy-name" - URGenerateResourceNameLabel = "generate.kyverno.io/resource-name" - URGenerateResourceUIDLabel = "generate.kyverno.io/resource-uid" - URGenerateResourceNSLabel = "generate.kyverno.io/resource-namespace" - URGenerateResourceKindLabel = "generate.kyverno.io/resource-kind" URGenerateRetryCountAnnotation = "generate.kyverno.io/retry-count" ) diff --git a/api/kyverno/v2/updaterequest_types.go b/api/kyverno/v2/updaterequest_types.go index 142b0ba8ca..99b8d7fb37 100644 --- a/api/kyverno/v2/updaterequest_types.go +++ b/api/kyverno/v2/updaterequest_types.go @@ -82,6 +82,31 @@ type UpdateRequestSpec struct { // Specifies the name of the policy. Policy string `json:"policy" yaml:"policy"` + // RuleContext is the associate context to apply rules. + // optional + RuleContext []RuleContext `json:"ruleContext,omitempty" yaml:"ruleContext,omitempty"` + + // Rule is the associate rule name of the current UR. + Rule string `json:"rule" yaml:"rule"` + + // DeleteDownstream represents whether the downstream needs to be deleted. + // Deprecated + DeleteDownstream bool `json:"deleteDownstream" yaml:"deleteDownstream"` + + // Synchronize represents the sync behavior of the corresponding rule + // Optional. Defaults to "false" if not specified. + // Deprecated, will be removed in 1.14. + Synchronize bool `json:"synchronize,omitempty" yaml:"synchronize,omitempty"` + + // ResourceSpec is the information to identify the trigger resource. + Resource kyvernov1.ResourceSpec `json:"resource" yaml:"resource"` + + // Context represents admission request context. + // It is used upon admission review only and is shared across rules within the same UR. + Context UpdateRequestSpecContext `json:"context" yaml:"context"` +} + +type RuleContext struct { // Rule is the associate rule name of the current UR. Rule string `json:"rule" yaml:"rule"` @@ -93,10 +118,7 @@ type UpdateRequestSpec struct { Synchronize bool `json:"synchronize,omitempty" yaml:"synchronize,omitempty"` // ResourceSpec is the information to identify the trigger resource. - Resource kyvernov1.ResourceSpec `json:"resource" yaml:"resource"` - - // Context ... - Context UpdateRequestSpecContext `json:"context" yaml:"context"` + Trigger kyvernov1.ResourceSpec `json:"trigger" yaml:"resource"` } // UpdateRequestSpecContext stores the context to be shared. diff --git a/api/kyverno/v2/zz_generated.deepcopy.go b/api/kyverno/v2/zz_generated.deepcopy.go index 62dab042e0..afa073fb3f 100644 --- a/api/kyverno/v2/zz_generated.deepcopy.go +++ b/api/kyverno/v2/zz_generated.deepcopy.go @@ -433,6 +433,23 @@ func (in *RequestInfo) DeepCopy() *RequestInfo { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RuleContext) DeepCopyInto(out *RuleContext) { + *out = *in + out.Trigger = in.Trigger + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RuleContext. +func (in *RuleContext) DeepCopy() *RuleContext { + if in == nil { + return nil + } + out := new(RuleContext) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *UpdateRequest) DeepCopyInto(out *UpdateRequest) { *out = *in @@ -497,6 +514,11 @@ func (in *UpdateRequestList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *UpdateRequestSpec) DeepCopyInto(out *UpdateRequestSpec) { *out = *in + if in.RuleContext != nil { + in, out := &in.RuleContext, &out.RuleContext + *out = make([]RuleContext, len(*in)) + copy(*out, *in) + } out.Resource = in.Resource in.Context.DeepCopyInto(&out.Context) return diff --git a/charts/kyverno/charts/crds/templates/kyverno.io/kyverno.io_updaterequests.yaml b/charts/kyverno/charts/crds/templates/kyverno.io/kyverno.io_updaterequests.yaml index a9885c56e1..030e189ccf 100644 --- a/charts/kyverno/charts/crds/templates/kyverno.io/kyverno.io_updaterequests.yaml +++ b/charts/kyverno/charts/crds/templates/kyverno.io/kyverno.io_updaterequests.yaml @@ -24,392 +24,6 @@ spec: singular: updaterequest scope: Namespaced versions: - - additionalPrinterColumns: - - jsonPath: .spec.policy - name: Policy - type: string - - jsonPath: .spec.rule - name: Rule - type: string - - jsonPath: .spec.requestType - name: RuleType - type: string - - jsonPath: .spec.resource.kind - name: ResourceKind - type: string - - jsonPath: .spec.resource.name - name: ResourceName - type: string - - jsonPath: .spec.resource.namespace - name: ResourceNamespace - type: string - - jsonPath: .status.state - name: status - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - deprecated: true - name: v1beta1 - schema: - openAPIV3Schema: - description: UpdateRequest is a request to process mutate and generate rules - in background. - properties: - apiVersion: - description: |- - APIVersion defines the versioned schema of this representation of an object. - Servers should convert recognized schemas to the latest internal value, and - may reject unrecognized values. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources - type: string - kind: - description: |- - Kind is a string value representing the REST resource this object represents. - Servers may infer this from the endpoint the client submits requests to. - Cannot be updated. - In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds - type: string - metadata: - type: object - spec: - description: ResourceSpec is the information to identify the trigger resource. - properties: - context: - description: Context ... - properties: - admissionRequestInfo: - description: AdmissionRequestInfoObject stores the admission request - and operation details - properties: - admissionRequest: - description: AdmissionRequest describes the admission.Attributes - for the admission request. - properties: - dryRun: - description: |- - DryRun indicates that modifications will definitely not be persisted for this request. - Defaults to false. - type: boolean - kind: - description: Kind is the fully-qualified type of object - being submitted (for example, v1.Pod or autoscaling.v1.Scale) - properties: - group: - type: string - kind: - type: string - version: - type: string - required: - - group - - kind - - version - type: object - name: - description: |- - Name is the name of the object as presented in the request. On a CREATE operation, the client may omit name and - rely on the server to generate the name. If that is the case, this field will contain an empty string. - type: string - namespace: - description: Namespace is the namespace associated with - the request (if any). - type: string - object: - description: Object is the object from the incoming request. - type: object - x-kubernetes-preserve-unknown-fields: true - oldObject: - description: OldObject is the existing object. Only populated - for DELETE and UPDATE requests. - type: object - x-kubernetes-preserve-unknown-fields: true - operation: - description: |- - Operation is the operation being performed. This may be different than the operation - requested. e.g. a patch can result in either a CREATE or UPDATE Operation. - type: string - options: - description: |- - Options is the operation option structure of the operation being performed. - e.g. `meta.k8s.io/v1.DeleteOptions` or `meta.k8s.io/v1.CreateOptions`. This may be - different than the options the caller provided. e.g. for a patch request the performed - Operation might be a CREATE, in which case the Options will a - `meta.k8s.io/v1.CreateOptions` even though the caller provided `meta.k8s.io/v1.PatchOptions`. - type: object - x-kubernetes-preserve-unknown-fields: true - requestKind: - description: |- - RequestKind is the fully-qualified type of the original API request (for example, v1.Pod or autoscaling.v1.Scale). - If this is specified and differs from the value in "kind", an equivalent match and conversion was performed. - - - For example, if deployments can be modified via apps/v1 and apps/v1beta1, and a webhook registered a rule of - `apiGroups:["apps"], apiVersions:["v1"], resources: ["deployments"]` and `matchPolicy: Equivalent`, - an API request to apps/v1beta1 deployments would be converted and sent to the webhook - with `kind: {group:"apps", version:"v1", kind:"Deployment"}` (matching the rule the webhook registered for), - and `requestKind: {group:"apps", version:"v1beta1", kind:"Deployment"}` (indicating the kind of the original API request). - - - See documentation for the "matchPolicy" field in the webhook configuration type for more details. - properties: - group: - type: string - kind: - type: string - version: - type: string - required: - - group - - kind - - version - type: object - requestResource: - description: |- - RequestResource is the fully-qualified resource of the original API request (for example, v1.pods). - If this is specified and differs from the value in "resource", an equivalent match and conversion was performed. - - - For example, if deployments can be modified via apps/v1 and apps/v1beta1, and a webhook registered a rule of - `apiGroups:["apps"], apiVersions:["v1"], resources: ["deployments"]` and `matchPolicy: Equivalent`, - an API request to apps/v1beta1 deployments would be converted and sent to the webhook - with `resource: {group:"apps", version:"v1", resource:"deployments"}` (matching the resource the webhook registered for), - and `requestResource: {group:"apps", version:"v1beta1", resource:"deployments"}` (indicating the resource of the original API request). - - - See documentation for the "matchPolicy" field in the webhook configuration type. - properties: - group: - type: string - resource: - type: string - version: - type: string - required: - - group - - resource - - version - type: object - requestSubResource: - description: |- - RequestSubResource is the name of the subresource of the original API request, if any (for example, "status" or "scale") - If this is specified and differs from the value in "subResource", an equivalent match and conversion was performed. - See documentation for the "matchPolicy" field in the webhook configuration type. - type: string - resource: - description: Resource is the fully-qualified resource - being requested (for example, v1.pods) - properties: - group: - type: string - resource: - type: string - version: - type: string - required: - - group - - resource - - version - type: object - subResource: - description: SubResource is the subresource being requested, - if any (for example, "status" or "scale") - type: string - uid: - description: |- - UID is an identifier for the individual request/response. It allows us to distinguish instances of requests which are - otherwise identical (parallel requests, requests when earlier requests did not modify etc) - The UID is meant to track the round trip (request/response) between the KAS and the WebHook, not the user request. - It is suitable for correlating log entries between the webhook and apiserver, for either auditing or debugging. - type: string - userInfo: - description: UserInfo is information about the requesting - user - properties: - extra: - additionalProperties: - description: ExtraValue masks the value so protobuf - can generate - items: - type: string - type: array - description: Any additional information provided by - the authenticator. - type: object - groups: - description: The names of groups this user is a part - of. - items: - type: string - type: array - x-kubernetes-list-type: atomic - uid: - description: |- - A unique value that identifies this user across time. If this user is - deleted and another user by the same name is added, they will have - different UIDs. - type: string - username: - description: The name that uniquely identifies this - user among all active users. - type: string - type: object - required: - - kind - - operation - - resource - - uid - - userInfo - type: object - operation: - description: Operation is the type of resource operation being - checked for admission control - type: string - type: object - userInfo: - description: RequestInfo contains permission info carried in an - admission request. - properties: - clusterRoles: - description: ClusterRoles is a list of possible clusterRoles - send the request. - items: - type: string - nullable: true - type: array - roles: - description: Roles is a list of possible role send the request. - items: - type: string - nullable: true - type: array - userInfo: - description: UserInfo is the userInfo carried in the admission - request. - properties: - extra: - additionalProperties: - description: ExtraValue masks the value so protobuf - can generate - items: - type: string - type: array - description: Any additional information provided by the - authenticator. - type: object - groups: - description: The names of groups this user is a part of. - items: - type: string - type: array - x-kubernetes-list-type: atomic - uid: - description: |- - A unique value that identifies this user across time. If this user is - deleted and another user by the same name is added, they will have - different UIDs. - type: string - username: - description: The name that uniquely identifies this user - among all active users. - type: string - type: object - type: object - type: object - deleteDownstream: - description: DeleteDownstream represents whether the downstream needs - to be deleted. - type: boolean - policy: - description: Specifies the name of the policy. - type: string - requestType: - description: Type represents request type for background processing - enum: - - mutate - - generate - type: string - resource: - description: ResourceSpec is the information to identify the trigger - resource. - properties: - apiVersion: - description: APIVersion specifies resource apiVersion. - type: string - kind: - description: Kind specifies resource kind. - type: string - name: - description: Name specifies the resource name. - type: string - namespace: - description: Namespace specifies resource namespace. - type: string - uid: - description: UID specifies the resource uid. - type: string - type: object - rule: - description: Rule is the associate rule name of the current UR. - type: string - synchronize: - description: |- - Synchronize represents the sync behavior of the corresponding rule - Optional. Defaults to "false" if not specified. - type: boolean - required: - - context - - deleteDownstream - - policy - - resource - - rule - type: object - status: - description: Status contains statistics related to update request. - properties: - generatedResources: - description: |- - This will track the resources that are updated by the generate Policy. - Will be used during clean up resources. - items: - properties: - apiVersion: - description: APIVersion specifies resource apiVersion. - type: string - kind: - description: Kind specifies resource kind. - type: string - name: - description: Name specifies the resource name. - type: string - namespace: - description: Namespace specifies resource namespace. - type: string - uid: - description: UID specifies the resource uid. - type: string - type: object - type: array - handler: - description: Deprecated - type: string - message: - description: Specifies request status message. - type: string - retryCount: - type: integer - state: - description: State represents state of the update request. - type: string - required: - - state - type: object - type: object - served: true - storage: false - subresources: - status: {} - additionalPrinterColumns: - jsonPath: .spec.policy name: Policy @@ -459,7 +73,9 @@ spec: description: ResourceSpec is the information to identify the trigger resource. properties: context: - description: Context ... + description: |- + Context represents admission request context. + It is used upon admission review only and is shared across rules within the same UR. properties: admissionRequestInfo: description: AdmissionRequestInfoObject stores the admission request @@ -700,8 +316,9 @@ spec: type: object type: object deleteDownstream: - description: DeleteDownstream represents whether the downstream needs - to be deleted. + description: |- + DeleteDownstream represents whether the downstream needs to be deleted. + Deprecated type: boolean policy: description: Specifies the name of the policy. @@ -735,10 +352,56 @@ spec: rule: description: Rule is the associate rule name of the current UR. type: string + ruleContext: + description: |- + RuleContext is the associate context to apply rules. + optional + items: + properties: + deleteDownstream: + description: DeleteDownstream represents whether the downstream + needs to be deleted. + type: boolean + rule: + description: Rule is the associate rule name of the current + UR. + type: string + synchronize: + description: |- + Synchronize represents the sync behavior of the corresponding rule + Optional. Defaults to "false" if not specified. + type: boolean + trigger: + description: ResourceSpec is the information to identify the + trigger resource. + properties: + apiVersion: + description: APIVersion specifies resource apiVersion. + type: string + kind: + description: Kind specifies resource kind. + type: string + name: + description: Name specifies the resource name. + type: string + namespace: + description: Namespace specifies resource namespace. + type: string + uid: + description: UID specifies the resource uid. + type: string + type: object + required: + - deleteDownstream + - rule + - trigger + type: object + type: array synchronize: description: |- Synchronize represents the sync behavior of the corresponding rule Optional. Defaults to "false" if not specified. + Deprecated, will be removed in 1.14. type: boolean required: - context diff --git a/cmd/cli/kubectl-kyverno/processor/generate.go b/cmd/cli/kubectl-kyverno/processor/generate.go index 31bd1d56de..8510113456 100644 --- a/cmd/cli/kubectl-kyverno/processor/generate.go +++ b/cmd/cli/kubectl-kyverno/processor/generate.go @@ -5,8 +5,6 @@ import ( "io" "strings" - kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1" - kyvernov2 "github.com/kyverno/kyverno/api/kyverno/v2" "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/log" "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/resource" "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/store" @@ -80,23 +78,10 @@ func handleGeneratePolicy(out io.Writer, store *store.Store, generateResponse *e return nil, err } - gr := kyvernov2.UpdateRequest{ - Spec: kyvernov2.UpdateRequestSpec{ - Type: kyvernov2.Generate, - Policy: generateResponse.Policy().GetName(), - Resource: kyvernov1.ResourceSpec{ - Kind: generateResponse.Resource.GetKind(), - Namespace: generateResponse.Resource.GetNamespace(), - Name: generateResponse.Resource.GetName(), - APIVersion: generateResponse.Resource.GetAPIVersion(), - }, - }, - } - var newRuleResponse []engineapi.RuleResponse for _, rule := range generateResponse.PolicyResponse.Rules { - genResource, err := c.ApplyGeneratePolicy(log.Log.V(2), &policyContext, gr, []string{rule.Name()}) + genResource, err := c.ApplyGeneratePolicy(log.Log.V(2), &policyContext, []string{rule.Name()}) if err != nil { return nil, err } diff --git a/config/crds/kyverno/kyverno.io_updaterequests.yaml b/config/crds/kyverno/kyverno.io_updaterequests.yaml index a4e549d44c..20cbe100d1 100644 --- a/config/crds/kyverno/kyverno.io_updaterequests.yaml +++ b/config/crds/kyverno/kyverno.io_updaterequests.yaml @@ -18,392 +18,6 @@ spec: singular: updaterequest scope: Namespaced versions: - - additionalPrinterColumns: - - jsonPath: .spec.policy - name: Policy - type: string - - jsonPath: .spec.rule - name: Rule - type: string - - jsonPath: .spec.requestType - name: RuleType - type: string - - jsonPath: .spec.resource.kind - name: ResourceKind - type: string - - jsonPath: .spec.resource.name - name: ResourceName - type: string - - jsonPath: .spec.resource.namespace - name: ResourceNamespace - type: string - - jsonPath: .status.state - name: status - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - deprecated: true - name: v1beta1 - schema: - openAPIV3Schema: - description: UpdateRequest is a request to process mutate and generate rules - in background. - properties: - apiVersion: - description: |- - APIVersion defines the versioned schema of this representation of an object. - Servers should convert recognized schemas to the latest internal value, and - may reject unrecognized values. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources - type: string - kind: - description: |- - Kind is a string value representing the REST resource this object represents. - Servers may infer this from the endpoint the client submits requests to. - Cannot be updated. - In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds - type: string - metadata: - type: object - spec: - description: ResourceSpec is the information to identify the trigger resource. - properties: - context: - description: Context ... - properties: - admissionRequestInfo: - description: AdmissionRequestInfoObject stores the admission request - and operation details - properties: - admissionRequest: - description: AdmissionRequest describes the admission.Attributes - for the admission request. - properties: - dryRun: - description: |- - DryRun indicates that modifications will definitely not be persisted for this request. - Defaults to false. - type: boolean - kind: - description: Kind is the fully-qualified type of object - being submitted (for example, v1.Pod or autoscaling.v1.Scale) - properties: - group: - type: string - kind: - type: string - version: - type: string - required: - - group - - kind - - version - type: object - name: - description: |- - Name is the name of the object as presented in the request. On a CREATE operation, the client may omit name and - rely on the server to generate the name. If that is the case, this field will contain an empty string. - type: string - namespace: - description: Namespace is the namespace associated with - the request (if any). - type: string - object: - description: Object is the object from the incoming request. - type: object - x-kubernetes-preserve-unknown-fields: true - oldObject: - description: OldObject is the existing object. Only populated - for DELETE and UPDATE requests. - type: object - x-kubernetes-preserve-unknown-fields: true - operation: - description: |- - Operation is the operation being performed. This may be different than the operation - requested. e.g. a patch can result in either a CREATE or UPDATE Operation. - type: string - options: - description: |- - Options is the operation option structure of the operation being performed. - e.g. `meta.k8s.io/v1.DeleteOptions` or `meta.k8s.io/v1.CreateOptions`. This may be - different than the options the caller provided. e.g. for a patch request the performed - Operation might be a CREATE, in which case the Options will a - `meta.k8s.io/v1.CreateOptions` even though the caller provided `meta.k8s.io/v1.PatchOptions`. - type: object - x-kubernetes-preserve-unknown-fields: true - requestKind: - description: |- - RequestKind is the fully-qualified type of the original API request (for example, v1.Pod or autoscaling.v1.Scale). - If this is specified and differs from the value in "kind", an equivalent match and conversion was performed. - - - For example, if deployments can be modified via apps/v1 and apps/v1beta1, and a webhook registered a rule of - `apiGroups:["apps"], apiVersions:["v1"], resources: ["deployments"]` and `matchPolicy: Equivalent`, - an API request to apps/v1beta1 deployments would be converted and sent to the webhook - with `kind: {group:"apps", version:"v1", kind:"Deployment"}` (matching the rule the webhook registered for), - and `requestKind: {group:"apps", version:"v1beta1", kind:"Deployment"}` (indicating the kind of the original API request). - - - See documentation for the "matchPolicy" field in the webhook configuration type for more details. - properties: - group: - type: string - kind: - type: string - version: - type: string - required: - - group - - kind - - version - type: object - requestResource: - description: |- - RequestResource is the fully-qualified resource of the original API request (for example, v1.pods). - If this is specified and differs from the value in "resource", an equivalent match and conversion was performed. - - - For example, if deployments can be modified via apps/v1 and apps/v1beta1, and a webhook registered a rule of - `apiGroups:["apps"], apiVersions:["v1"], resources: ["deployments"]` and `matchPolicy: Equivalent`, - an API request to apps/v1beta1 deployments would be converted and sent to the webhook - with `resource: {group:"apps", version:"v1", resource:"deployments"}` (matching the resource the webhook registered for), - and `requestResource: {group:"apps", version:"v1beta1", resource:"deployments"}` (indicating the resource of the original API request). - - - See documentation for the "matchPolicy" field in the webhook configuration type. - properties: - group: - type: string - resource: - type: string - version: - type: string - required: - - group - - resource - - version - type: object - requestSubResource: - description: |- - RequestSubResource is the name of the subresource of the original API request, if any (for example, "status" or "scale") - If this is specified and differs from the value in "subResource", an equivalent match and conversion was performed. - See documentation for the "matchPolicy" field in the webhook configuration type. - type: string - resource: - description: Resource is the fully-qualified resource - being requested (for example, v1.pods) - properties: - group: - type: string - resource: - type: string - version: - type: string - required: - - group - - resource - - version - type: object - subResource: - description: SubResource is the subresource being requested, - if any (for example, "status" or "scale") - type: string - uid: - description: |- - UID is an identifier for the individual request/response. It allows us to distinguish instances of requests which are - otherwise identical (parallel requests, requests when earlier requests did not modify etc) - The UID is meant to track the round trip (request/response) between the KAS and the WebHook, not the user request. - It is suitable for correlating log entries between the webhook and apiserver, for either auditing or debugging. - type: string - userInfo: - description: UserInfo is information about the requesting - user - properties: - extra: - additionalProperties: - description: ExtraValue masks the value so protobuf - can generate - items: - type: string - type: array - description: Any additional information provided by - the authenticator. - type: object - groups: - description: The names of groups this user is a part - of. - items: - type: string - type: array - x-kubernetes-list-type: atomic - uid: - description: |- - A unique value that identifies this user across time. If this user is - deleted and another user by the same name is added, they will have - different UIDs. - type: string - username: - description: The name that uniquely identifies this - user among all active users. - type: string - type: object - required: - - kind - - operation - - resource - - uid - - userInfo - type: object - operation: - description: Operation is the type of resource operation being - checked for admission control - type: string - type: object - userInfo: - description: RequestInfo contains permission info carried in an - admission request. - properties: - clusterRoles: - description: ClusterRoles is a list of possible clusterRoles - send the request. - items: - type: string - nullable: true - type: array - roles: - description: Roles is a list of possible role send the request. - items: - type: string - nullable: true - type: array - userInfo: - description: UserInfo is the userInfo carried in the admission - request. - properties: - extra: - additionalProperties: - description: ExtraValue masks the value so protobuf - can generate - items: - type: string - type: array - description: Any additional information provided by the - authenticator. - type: object - groups: - description: The names of groups this user is a part of. - items: - type: string - type: array - x-kubernetes-list-type: atomic - uid: - description: |- - A unique value that identifies this user across time. If this user is - deleted and another user by the same name is added, they will have - different UIDs. - type: string - username: - description: The name that uniquely identifies this user - among all active users. - type: string - type: object - type: object - type: object - deleteDownstream: - description: DeleteDownstream represents whether the downstream needs - to be deleted. - type: boolean - policy: - description: Specifies the name of the policy. - type: string - requestType: - description: Type represents request type for background processing - enum: - - mutate - - generate - type: string - resource: - description: ResourceSpec is the information to identify the trigger - resource. - properties: - apiVersion: - description: APIVersion specifies resource apiVersion. - type: string - kind: - description: Kind specifies resource kind. - type: string - name: - description: Name specifies the resource name. - type: string - namespace: - description: Namespace specifies resource namespace. - type: string - uid: - description: UID specifies the resource uid. - type: string - type: object - rule: - description: Rule is the associate rule name of the current UR. - type: string - synchronize: - description: |- - Synchronize represents the sync behavior of the corresponding rule - Optional. Defaults to "false" if not specified. - type: boolean - required: - - context - - deleteDownstream - - policy - - resource - - rule - type: object - status: - description: Status contains statistics related to update request. - properties: - generatedResources: - description: |- - This will track the resources that are updated by the generate Policy. - Will be used during clean up resources. - items: - properties: - apiVersion: - description: APIVersion specifies resource apiVersion. - type: string - kind: - description: Kind specifies resource kind. - type: string - name: - description: Name specifies the resource name. - type: string - namespace: - description: Namespace specifies resource namespace. - type: string - uid: - description: UID specifies the resource uid. - type: string - type: object - type: array - handler: - description: Deprecated - type: string - message: - description: Specifies request status message. - type: string - retryCount: - type: integer - state: - description: State represents state of the update request. - type: string - required: - - state - type: object - type: object - served: true - storage: false - subresources: - status: {} - additionalPrinterColumns: - jsonPath: .spec.policy name: Policy @@ -453,7 +67,9 @@ spec: description: ResourceSpec is the information to identify the trigger resource. properties: context: - description: Context ... + description: |- + Context represents admission request context. + It is used upon admission review only and is shared across rules within the same UR. properties: admissionRequestInfo: description: AdmissionRequestInfoObject stores the admission request @@ -694,8 +310,9 @@ spec: type: object type: object deleteDownstream: - description: DeleteDownstream represents whether the downstream needs - to be deleted. + description: |- + DeleteDownstream represents whether the downstream needs to be deleted. + Deprecated type: boolean policy: description: Specifies the name of the policy. @@ -729,10 +346,56 @@ spec: rule: description: Rule is the associate rule name of the current UR. type: string + ruleContext: + description: |- + RuleContext is the associate context to apply rules. + optional + items: + properties: + deleteDownstream: + description: DeleteDownstream represents whether the downstream + needs to be deleted. + type: boolean + rule: + description: Rule is the associate rule name of the current + UR. + type: string + synchronize: + description: |- + Synchronize represents the sync behavior of the corresponding rule + Optional. Defaults to "false" if not specified. + type: boolean + trigger: + description: ResourceSpec is the information to identify the + trigger resource. + properties: + apiVersion: + description: APIVersion specifies resource apiVersion. + type: string + kind: + description: Kind specifies resource kind. + type: string + name: + description: Name specifies the resource name. + type: string + namespace: + description: Namespace specifies resource namespace. + type: string + uid: + description: UID specifies the resource uid. + type: string + type: object + required: + - deleteDownstream + - rule + - trigger + type: object + type: array synchronize: description: |- Synchronize represents the sync behavior of the corresponding rule Optional. Defaults to "false" if not specified. + Deprecated, will be removed in 1.14. type: boolean required: - context diff --git a/config/install-latest-testing.yaml b/config/install-latest-testing.yaml index 2825cb4fc6..65b752f2ac 100644 --- a/config/install-latest-testing.yaml +++ b/config/install-latest-testing.yaml @@ -41380,392 +41380,6 @@ spec: singular: updaterequest scope: Namespaced versions: - - additionalPrinterColumns: - - jsonPath: .spec.policy - name: Policy - type: string - - jsonPath: .spec.rule - name: Rule - type: string - - jsonPath: .spec.requestType - name: RuleType - type: string - - jsonPath: .spec.resource.kind - name: ResourceKind - type: string - - jsonPath: .spec.resource.name - name: ResourceName - type: string - - jsonPath: .spec.resource.namespace - name: ResourceNamespace - type: string - - jsonPath: .status.state - name: status - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - deprecated: true - name: v1beta1 - schema: - openAPIV3Schema: - description: UpdateRequest is a request to process mutate and generate rules - in background. - properties: - apiVersion: - description: |- - APIVersion defines the versioned schema of this representation of an object. - Servers should convert recognized schemas to the latest internal value, and - may reject unrecognized values. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources - type: string - kind: - description: |- - Kind is a string value representing the REST resource this object represents. - Servers may infer this from the endpoint the client submits requests to. - Cannot be updated. - In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds - type: string - metadata: - type: object - spec: - description: ResourceSpec is the information to identify the trigger resource. - properties: - context: - description: Context ... - properties: - admissionRequestInfo: - description: AdmissionRequestInfoObject stores the admission request - and operation details - properties: - admissionRequest: - description: AdmissionRequest describes the admission.Attributes - for the admission request. - properties: - dryRun: - description: |- - DryRun indicates that modifications will definitely not be persisted for this request. - Defaults to false. - type: boolean - kind: - description: Kind is the fully-qualified type of object - being submitted (for example, v1.Pod or autoscaling.v1.Scale) - properties: - group: - type: string - kind: - type: string - version: - type: string - required: - - group - - kind - - version - type: object - name: - description: |- - Name is the name of the object as presented in the request. On a CREATE operation, the client may omit name and - rely on the server to generate the name. If that is the case, this field will contain an empty string. - type: string - namespace: - description: Namespace is the namespace associated with - the request (if any). - type: string - object: - description: Object is the object from the incoming request. - type: object - x-kubernetes-preserve-unknown-fields: true - oldObject: - description: OldObject is the existing object. Only populated - for DELETE and UPDATE requests. - type: object - x-kubernetes-preserve-unknown-fields: true - operation: - description: |- - Operation is the operation being performed. This may be different than the operation - requested. e.g. a patch can result in either a CREATE or UPDATE Operation. - type: string - options: - description: |- - Options is the operation option structure of the operation being performed. - e.g. `meta.k8s.io/v1.DeleteOptions` or `meta.k8s.io/v1.CreateOptions`. This may be - different than the options the caller provided. e.g. for a patch request the performed - Operation might be a CREATE, in which case the Options will a - `meta.k8s.io/v1.CreateOptions` even though the caller provided `meta.k8s.io/v1.PatchOptions`. - type: object - x-kubernetes-preserve-unknown-fields: true - requestKind: - description: |- - RequestKind is the fully-qualified type of the original API request (for example, v1.Pod or autoscaling.v1.Scale). - If this is specified and differs from the value in "kind", an equivalent match and conversion was performed. - - - For example, if deployments can be modified via apps/v1 and apps/v1beta1, and a webhook registered a rule of - `apiGroups:["apps"], apiVersions:["v1"], resources: ["deployments"]` and `matchPolicy: Equivalent`, - an API request to apps/v1beta1 deployments would be converted and sent to the webhook - with `kind: {group:"apps", version:"v1", kind:"Deployment"}` (matching the rule the webhook registered for), - and `requestKind: {group:"apps", version:"v1beta1", kind:"Deployment"}` (indicating the kind of the original API request). - - - See documentation for the "matchPolicy" field in the webhook configuration type for more details. - properties: - group: - type: string - kind: - type: string - version: - type: string - required: - - group - - kind - - version - type: object - requestResource: - description: |- - RequestResource is the fully-qualified resource of the original API request (for example, v1.pods). - If this is specified and differs from the value in "resource", an equivalent match and conversion was performed. - - - For example, if deployments can be modified via apps/v1 and apps/v1beta1, and a webhook registered a rule of - `apiGroups:["apps"], apiVersions:["v1"], resources: ["deployments"]` and `matchPolicy: Equivalent`, - an API request to apps/v1beta1 deployments would be converted and sent to the webhook - with `resource: {group:"apps", version:"v1", resource:"deployments"}` (matching the resource the webhook registered for), - and `requestResource: {group:"apps", version:"v1beta1", resource:"deployments"}` (indicating the resource of the original API request). - - - See documentation for the "matchPolicy" field in the webhook configuration type. - properties: - group: - type: string - resource: - type: string - version: - type: string - required: - - group - - resource - - version - type: object - requestSubResource: - description: |- - RequestSubResource is the name of the subresource of the original API request, if any (for example, "status" or "scale") - If this is specified and differs from the value in "subResource", an equivalent match and conversion was performed. - See documentation for the "matchPolicy" field in the webhook configuration type. - type: string - resource: - description: Resource is the fully-qualified resource - being requested (for example, v1.pods) - properties: - group: - type: string - resource: - type: string - version: - type: string - required: - - group - - resource - - version - type: object - subResource: - description: SubResource is the subresource being requested, - if any (for example, "status" or "scale") - type: string - uid: - description: |- - UID is an identifier for the individual request/response. It allows us to distinguish instances of requests which are - otherwise identical (parallel requests, requests when earlier requests did not modify etc) - The UID is meant to track the round trip (request/response) between the KAS and the WebHook, not the user request. - It is suitable for correlating log entries between the webhook and apiserver, for either auditing or debugging. - type: string - userInfo: - description: UserInfo is information about the requesting - user - properties: - extra: - additionalProperties: - description: ExtraValue masks the value so protobuf - can generate - items: - type: string - type: array - description: Any additional information provided by - the authenticator. - type: object - groups: - description: The names of groups this user is a part - of. - items: - type: string - type: array - x-kubernetes-list-type: atomic - uid: - description: |- - A unique value that identifies this user across time. If this user is - deleted and another user by the same name is added, they will have - different UIDs. - type: string - username: - description: The name that uniquely identifies this - user among all active users. - type: string - type: object - required: - - kind - - operation - - resource - - uid - - userInfo - type: object - operation: - description: Operation is the type of resource operation being - checked for admission control - type: string - type: object - userInfo: - description: RequestInfo contains permission info carried in an - admission request. - properties: - clusterRoles: - description: ClusterRoles is a list of possible clusterRoles - send the request. - items: - type: string - nullable: true - type: array - roles: - description: Roles is a list of possible role send the request. - items: - type: string - nullable: true - type: array - userInfo: - description: UserInfo is the userInfo carried in the admission - request. - properties: - extra: - additionalProperties: - description: ExtraValue masks the value so protobuf - can generate - items: - type: string - type: array - description: Any additional information provided by the - authenticator. - type: object - groups: - description: The names of groups this user is a part of. - items: - type: string - type: array - x-kubernetes-list-type: atomic - uid: - description: |- - A unique value that identifies this user across time. If this user is - deleted and another user by the same name is added, they will have - different UIDs. - type: string - username: - description: The name that uniquely identifies this user - among all active users. - type: string - type: object - type: object - type: object - deleteDownstream: - description: DeleteDownstream represents whether the downstream needs - to be deleted. - type: boolean - policy: - description: Specifies the name of the policy. - type: string - requestType: - description: Type represents request type for background processing - enum: - - mutate - - generate - type: string - resource: - description: ResourceSpec is the information to identify the trigger - resource. - properties: - apiVersion: - description: APIVersion specifies resource apiVersion. - type: string - kind: - description: Kind specifies resource kind. - type: string - name: - description: Name specifies the resource name. - type: string - namespace: - description: Namespace specifies resource namespace. - type: string - uid: - description: UID specifies the resource uid. - type: string - type: object - rule: - description: Rule is the associate rule name of the current UR. - type: string - synchronize: - description: |- - Synchronize represents the sync behavior of the corresponding rule - Optional. Defaults to "false" if not specified. - type: boolean - required: - - context - - deleteDownstream - - policy - - resource - - rule - type: object - status: - description: Status contains statistics related to update request. - properties: - generatedResources: - description: |- - This will track the resources that are updated by the generate Policy. - Will be used during clean up resources. - items: - properties: - apiVersion: - description: APIVersion specifies resource apiVersion. - type: string - kind: - description: Kind specifies resource kind. - type: string - name: - description: Name specifies the resource name. - type: string - namespace: - description: Namespace specifies resource namespace. - type: string - uid: - description: UID specifies the resource uid. - type: string - type: object - type: array - handler: - description: Deprecated - type: string - message: - description: Specifies request status message. - type: string - retryCount: - type: integer - state: - description: State represents state of the update request. - type: string - required: - - state - type: object - type: object - served: true - storage: false - subresources: - status: {} - additionalPrinterColumns: - jsonPath: .spec.policy name: Policy @@ -41815,7 +41429,9 @@ spec: description: ResourceSpec is the information to identify the trigger resource. properties: context: - description: Context ... + description: |- + Context represents admission request context. + It is used upon admission review only and is shared across rules within the same UR. properties: admissionRequestInfo: description: AdmissionRequestInfoObject stores the admission request @@ -42056,8 +41672,9 @@ spec: type: object type: object deleteDownstream: - description: DeleteDownstream represents whether the downstream needs - to be deleted. + description: |- + DeleteDownstream represents whether the downstream needs to be deleted. + Deprecated type: boolean policy: description: Specifies the name of the policy. @@ -42091,10 +41708,56 @@ spec: rule: description: Rule is the associate rule name of the current UR. type: string + ruleContext: + description: |- + RuleContext is the associate context to apply rules. + optional + items: + properties: + deleteDownstream: + description: DeleteDownstream represents whether the downstream + needs to be deleted. + type: boolean + rule: + description: Rule is the associate rule name of the current + UR. + type: string + synchronize: + description: |- + Synchronize represents the sync behavior of the corresponding rule + Optional. Defaults to "false" if not specified. + type: boolean + trigger: + description: ResourceSpec is the information to identify the + trigger resource. + properties: + apiVersion: + description: APIVersion specifies resource apiVersion. + type: string + kind: + description: Kind specifies resource kind. + type: string + name: + description: Name specifies the resource name. + type: string + namespace: + description: Namespace specifies resource namespace. + type: string + uid: + description: UID specifies the resource uid. + type: string + type: object + required: + - deleteDownstream + - rule + - trigger + type: object + type: array synchronize: description: |- Synchronize represents the sync behavior of the corresponding rule Optional. Defaults to "false" if not specified. + Deprecated, will be removed in 1.14. type: boolean required: - context diff --git a/docs/user/crd/index.html b/docs/user/crd/index.html index 98908837ed..47308c9ebf 100644 --- a/docs/user/crd/index.html +++ b/docs/user/crd/index.html @@ -3559,6 +3559,7 @@ ResourceDescription TargetResourceSpec, UpdateRequestSpec, UpdateRequestStatus, +RuleContext, UpdateRequestSpec, UpdateRequestStatus)

@@ -5877,6 +5878,20 @@ string +ruleContext
+ + +[]RuleContext + + + + +

RuleContext is the associate context to apply rules. +optional

+ + + + rule
string @@ -5894,7 +5909,8 @@ bool -

DeleteDownstream represents whether the downstream needs to be deleted.

+

DeleteDownstream represents whether the downstream needs to be deleted. +Deprecated

@@ -5906,7 +5922,8 @@ bool

Synchronize represents the sync behavior of the corresponding rule -Optional. Defaults to “false” if not specified.

+Optional. Defaults to “false” if not specified. +Deprecated, will be removed in 1.14.

@@ -5932,7 +5949,8 @@ UpdateRequestSpecContext -

Context …

+

Context represents admission request context. +It is used upon admission review only and is shared across rules within the same UR.

@@ -6475,6 +6493,72 @@ Kubernetes authentication/v1.UserInfo

+

RuleContext +

+

+(Appears on: +UpdateRequestSpec) +

+

+

+ + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+rule
+ +string + +
+

Rule is the associate rule name of the current UR.

+
+deleteDownstream
+ +bool + +
+

DeleteDownstream represents whether the downstream needs to be deleted.

+
+synchronize
+ +bool + +
+

Synchronize represents the sync behavior of the corresponding rule +Optional. Defaults to “false” if not specified.

+
+trigger
+ + +ResourceSpec + + +
+

ResourceSpec is the information to identify the trigger resource.

+
+

UpdateRequestSpec

@@ -6518,6 +6602,20 @@ string +ruleContext
+ + +[]RuleContext + + + + +

RuleContext is the associate context to apply rules. +optional

+ + + + rule
string @@ -6535,7 +6633,8 @@ bool -

DeleteDownstream represents whether the downstream needs to be deleted.

+

DeleteDownstream represents whether the downstream needs to be deleted. +Deprecated

@@ -6547,7 +6646,8 @@ bool

Synchronize represents the sync behavior of the corresponding rule -Optional. Defaults to “false” if not specified.

+Optional. Defaults to “false” if not specified. +Deprecated, will be removed in 1.14.

@@ -6573,7 +6673,8 @@ UpdateRequestSpecContext -

Context …

+

Context represents admission request context. +It is used upon admission review only and is shared across rules within the same UR.

diff --git a/pkg/background/common/context.go b/pkg/background/common/context.go index 22fa697e93..f4cc5ca15f 100644 --- a/pkg/background/common/context.go +++ b/pkg/background/common/context.go @@ -17,7 +17,7 @@ import ( func NewBackgroundContext( logger logr.Logger, dclient dclient.Interface, - ur *kyvernov2.UpdateRequest, + urContext kyvernov2.UpdateRequestSpecContext, policy kyvernov1.PolicyInterface, trigger *unstructured.Unstructured, cfg config.Configuration, @@ -27,15 +27,15 @@ func NewBackgroundContext( var new, old unstructured.Unstructured var err error - if ur.Spec.Context.AdmissionRequestInfo.AdmissionRequest != nil { - new, old, err = admissionutils.ExtractResources(nil, *ur.Spec.Context.AdmissionRequestInfo.AdmissionRequest) + if urContext.AdmissionRequestInfo.AdmissionRequest != nil { + new, old, err = admissionutils.ExtractResources(nil, *urContext.AdmissionRequestInfo.AdmissionRequest) if err != nil { return nil, fmt.Errorf("failed to load request in context: %w", err) } if new.Object != nil { if !check(&new, trigger) { - err := fmt.Errorf("resources don't match") - return nil, fmt.Errorf("resource %v: %w", ur.Spec.GetResource().String(), err) + return nil, fmt.Errorf("resources don't match, want: %v/%v, got: %v/%v", + trigger.GetNamespace(), trigger.GetName(), new.GetNamespace(), new.GetName()) } } } @@ -47,19 +47,19 @@ func NewBackgroundContext( } var policyContext *engine.PolicyContext - if ur.Spec.Context.AdmissionRequestInfo.AdmissionRequest == nil { + if urContext.AdmissionRequestInfo.AdmissionRequest == nil { policyContext, err = engine.NewPolicyContext( jp, *trigger, - kyvernov1.AdmissionOperation(ur.Spec.Context.AdmissionRequestInfo.Operation), - &ur.Spec.Context.UserRequestInfo, + kyvernov1.AdmissionOperation(urContext.AdmissionRequestInfo.Operation), + &urContext.UserRequestInfo, cfg, ) } else { policyContext, err = engine.NewPolicyContextFromAdmissionRequest( jp, - *ur.Spec.Context.AdmissionRequestInfo.AdmissionRequest, - ur.Spec.Context.UserRequestInfo, + *urContext.AdmissionRequestInfo.AdmissionRequest, + urContext.UserRequestInfo, trigger.GroupVersionKind(), cfg, ) diff --git a/pkg/background/common/labels.go b/pkg/background/common/labels.go index 14b29e9459..8157c243ff 100644 --- a/pkg/background/common/labels.go +++ b/pkg/background/common/labels.go @@ -53,18 +53,12 @@ func MutateLabelsSet(policyKey string, trigger Object) pkglabels.Set { return set } -func GenerateLabelsSet(policyKey string, trigger Object) pkglabels.Set { +func GenerateLabelsSet(policyKey string) pkglabels.Set { _, policyName, _ := cache.SplitMetaNamespaceKey(policyKey) set := pkglabels.Set{ kyvernov2.URGeneratePolicyLabel: policyName, } - isNil := trigger == nil || (reflect.ValueOf(trigger).Kind() == reflect.Ptr && reflect.ValueOf(trigger).IsNil()) - if !isNil { - set[kyvernov2.URGenerateResourceUIDLabel] = string(trigger.GetUID()) - set[kyvernov2.URGenerateResourceNSLabel] = trigger.GetNamespace() - set[kyvernov2.URGenerateResourceKindLabel] = trigger.GetKind() - } return set } diff --git a/pkg/background/common/resource.go b/pkg/background/common/resource.go index f4c00d39d0..c02bfb3b4b 100644 --- a/pkg/background/common/resource.go +++ b/pkg/background/common/resource.go @@ -3,8 +3,10 @@ package common import ( "context" "fmt" + "reflect" "github.com/go-logr/logr" + kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1" kyvernov2 "github.com/kyverno/kyverno/api/kyverno/v2" "github.com/kyverno/kyverno/pkg/clients/dclient" kubeutils "github.com/kyverno/kyverno/pkg/utils/kube" @@ -13,10 +15,13 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) -func GetResource(client dclient.Interface, urSpec kyvernov2.UpdateRequestSpec, log logr.Logger) (resource *unstructured.Unstructured, err error) { - resourceSpec := urSpec.GetResource() +func GetResource(client dclient.Interface, resourceSpec kyvernov1.ResourceSpec, urSpec kyvernov2.UpdateRequestSpec, log logr.Logger) (resource *unstructured.Unstructured, err error) { + obj := resourceSpec + if reflect.DeepEqual(obj, kyvernov1.ResourceSpec{}) { + obj = urSpec.GetResource() + } - if urSpec.GetResource().GetUID() != "" { + if obj.GetUID() != "" { triggers, err := client.ListResource(context.TODO(), resourceSpec.GetAPIVersion(), resourceSpec.GetKind(), resourceSpec.GetNamespace(), nil) if err != nil { return nil, fmt.Errorf("failed to list trigger resources: %v", err) @@ -27,7 +32,7 @@ func GetResource(client dclient.Interface, urSpec kyvernov2.UpdateRequestSpec, l return &trigger, nil } } - } else if urSpec.GetResource().GetName() != "" { + } else if obj.GetName() != "" { if resourceSpec.Kind == "Namespace" { resourceSpec.Namespace = "" } @@ -44,7 +49,7 @@ func GetResource(client dclient.Interface, urSpec kyvernov2.UpdateRequestSpec, l return resource, nil } - if resource == nil && urSpec.Context.AdmissionRequestInfo.AdmissionRequest != nil { + if urSpec.Context.AdmissionRequestInfo.AdmissionRequest != nil { request := urSpec.Context.AdmissionRequestInfo.AdmissionRequest raw := request.Object.Raw if request.Operation == admissionv1.Delete { @@ -52,8 +57,12 @@ func GetResource(client dclient.Interface, urSpec kyvernov2.UpdateRequestSpec, l } resource, err = kubeutils.BytesToUnstructured(raw) + if err != nil { + return nil, fmt.Errorf("failed to convert raw object to unstructured: %v", err) + } else { + return resource, nil + } } - log.V(3).Info("fetched trigger resource", "resourceSpec", resourceSpec) - return resource, err + return nil, fmt.Errorf("resource not found") } diff --git a/pkg/background/generate/cleanup.go b/pkg/background/generate/cleanup.go index 40ec3204f0..f6049bd75d 100644 --- a/pkg/background/generate/cleanup.go +++ b/pkg/background/generate/cleanup.go @@ -14,14 +14,10 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) -func (c *GenerateController) deleteDownstream(policy kyvernov1.PolicyInterface, ur *kyvernov2.UpdateRequest) (err error) { - if !ur.Spec.DeleteDownstream { - return nil - } - +func (c *GenerateController) deleteDownstream(policy kyvernov1.PolicyInterface, ruleContext kyvernov2.RuleContext, ur *kyvernov2.UpdateRequest) (err error) { // handle data policy/rule deletion if ur.Status.GeneratedResources != nil { - c.log.V(4).Info("policy/rule no longer exists, deleting the downstream resource based on synchronize", "ur", ur.Name, "policy", ur.Spec.Policy, "rule", ur.Spec.Rule) + c.log.V(4).Info("policy/rule no longer exists, deleting the downstream resource based on synchronize", "ur", ur.Name, "policy", ur.Spec.Policy) var errs []error failedDownstreams := []kyvernov1.ResourceSpec{} for _, e := range ur.Status.GeneratedResources { @@ -46,18 +42,17 @@ func (c *GenerateController) deleteDownstream(policy kyvernov1.PolicyInterface, return nil } - return c.handleNonPolicyChanges(policy, ur) + return c.handleNonPolicyChanges(policy, ruleContext, ur) } -func (c *GenerateController) handleNonPolicyChanges(policy kyvernov1.PolicyInterface, ur *kyvernov2.UpdateRequest) error { - if !ur.Spec.DeleteDownstream { - return nil - } - +func (c *GenerateController) handleNonPolicyChanges(policy kyvernov1.PolicyInterface, ruleContext kyvernov2.RuleContext, ur *kyvernov2.UpdateRequest) error { + logger := c.log.V(4).WithValues("ur", ur.Name, "policy", ur.Spec.Policy, "rule", ruleContext.Rule) + logger.Info("synchronize for none-policy changes") for _, rule := range policy.GetSpec().Rules { - if ur.Spec.Rule != rule.Name { + if ruleContext.Rule != rule.Name { continue } + logger.Info("deleting the downstream resource based on synchronize") labels := map[string]string{ common.GeneratePolicyLabel: policy.GetName(), common.GeneratePolicyNamespaceLabel: policy.GetNamespace(), @@ -65,7 +60,7 @@ func (c *GenerateController) handleNonPolicyChanges(policy kyvernov1.PolicyInter kyverno.LabelAppManagedBy: kyverno.ValueKyvernoApp, } - downstreams, err := c.getDownstreams(rule, labels, ur) + downstreams, err := c.getDownstreams(rule, labels, &ruleContext) if err != nil { return fmt.Errorf("failed to fetch downstream resources: %v", err) } @@ -77,7 +72,7 @@ func (c *GenerateController) handleNonPolicyChanges(policy kyvernov1.PolicyInter failedDownstreams = append(failedDownstreams, spec) errs = append(errs, err) } else { - c.log.V(4).Info("downstream resource deleted", "spec", spec.String()) + logger.Info("downstream resource deleted", "spec", spec.String()) } } if len(errs) != 0 { @@ -88,22 +83,22 @@ func (c *GenerateController) handleNonPolicyChanges(policy kyvernov1.PolicyInter _, err = c.statusControl.Success(ur.GetName(), nil) } if err != nil { - c.log.Error(err, "failed to update ur status") + logger.Error(err, "failed to update ur status") } } return nil } -func (c *GenerateController) getDownstreams(rule kyvernov1.Rule, selector map[string]string, ur *kyvernov2.UpdateRequest) (*unstructured.UnstructuredList, error) { - gv, err := ur.Spec.GetResource().GetGroupVersion() +func (c *GenerateController) getDownstreams(rule kyvernov1.Rule, selector map[string]string, ruleContext *kyvernov2.RuleContext) (*unstructured.UnstructuredList, error) { + gv, err := ruleContext.Trigger.GetGroupVersion() if err != nil { return nil, err } - selector[common.GenerateTriggerUIDLabel] = string(ur.Spec.GetResource().GetUID()) - selector[common.GenerateTriggerNSLabel] = ur.Spec.GetResource().GetNamespace() - selector[common.GenerateTriggerKindLabel] = ur.Spec.GetResource().GetKind() + selector[common.GenerateTriggerUIDLabel] = string(ruleContext.Trigger.GetUID()) + selector[common.GenerateTriggerNSLabel] = ruleContext.Trigger.GetNamespace() + selector[common.GenerateTriggerKindLabel] = ruleContext.Trigger.GetKind() selector[common.GenerateTriggerGroupLabel] = gv.Group selector[common.GenerateTriggerVersionLabel] = gv.Version if rule.Generation.GetKind() != "" { @@ -117,7 +112,7 @@ func (c *GenerateController) getDownstreams(rule kyvernov1.Rule, selector map[st if len(downstreamList.Items) == 0 { // Fetch downstream resources using the trigger name label delete(selector, common.GenerateTriggerUIDLabel) - selector[common.GenerateTriggerNameLabel] = ur.Spec.GetResource().GetName() + selector[common.GenerateTriggerNameLabel] = ruleContext.Trigger.GetName() c.log.V(4).Info("fetching downstream resource by the name", "APIVersion", rule.Generation.GetAPIVersion(), "kind", rule.Generation.GetKind(), "selector", selector) dsList, err := common.FindDownstream(c.client, rule.Generation.GetAPIVersion(), rule.Generation.GetKind(), selector) if err != nil { @@ -140,7 +135,7 @@ func (c *GenerateController) getDownstreams(rule kyvernov1.Rule, selector map[st if len(dsList.Items) == 0 { delete(selector, common.GenerateTriggerUIDLabel) - selector[common.GenerateTriggerNameLabel] = ur.Spec.GetResource().GetName() + selector[common.GenerateTriggerNameLabel] = ruleContext.Trigger.GetName() c.log.V(4).Info("fetching downstream resource by the name", "APIVersion", rule.Generation.GetAPIVersion(), "kind", rule.Generation.GetKind(), "selector", selector) dsList, err = common.FindDownstream(c.client, rule.Generation.GetAPIVersion(), rule.Generation.GetKind(), selector) if err != nil { diff --git a/pkg/background/generate/clone.go b/pkg/background/generate/clone.go index 49bb522204..bd85a80006 100644 --- a/pkg/background/generate/clone.go +++ b/pkg/background/generate/clone.go @@ -6,7 +6,6 @@ import ( "github.com/go-logr/logr" kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1" - kyvernov2 "github.com/kyverno/kyverno/api/kyverno/v2" "github.com/kyverno/kyverno/pkg/clients/dclient" datautils "github.com/kyverno/kyverno/pkg/utils/data" kubeutils "github.com/kyverno/kyverno/pkg/utils/kube" @@ -14,7 +13,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -func manageClone(log logr.Logger, target, sourceSpec kyvernov1.ResourceSpec, policy kyvernov1.PolicyInterface, ur kyvernov2.UpdateRequest, rule kyvernov1.Rule, client dclient.Interface) generateResponse { +func manageClone(log logr.Logger, target, sourceSpec kyvernov1.ResourceSpec, policy kyvernov1.PolicyInterface, rule kyvernov1.Rule, client dclient.Interface) generateResponse { source := sourceSpec clone := rule.Generation if clone.Clone.Name != "" { @@ -60,15 +59,9 @@ func manageClone(log logr.Logger, target, sourceSpec kyvernov1.ResourceSpec, pol sourceObjCopy.SetResourceVersion("") targetObj, err := client.GetResource(context.TODO(), target.GetAPIVersion(), target.GetKind(), target.GetNamespace(), target.GetName()) - if err != nil { - if apierrors.IsNotFound(err) && len(ur.Status.GeneratedResources) != 0 && !clone.Synchronize { - log.V(4).Info("synchronization is disabled, recreation will be skipped", "target resource", targetObj) - return newSkipGenerateResponse(nil, target, nil) - } - if apierrors.IsNotFound(err) { - return newCreateGenerateResponse(sourceObjCopy.UnstructuredContent(), target, nil) - } - return newSkipGenerateResponse(nil, target, fmt.Errorf("failed to get the target source: %v", err)) + if err != nil && apierrors.IsNotFound(err) { + // the target resource should always exist regardless of synchronize settings + return newCreateGenerateResponse(sourceObjCopy.UnstructuredContent(), target, nil) } if targetObj != nil { @@ -88,7 +81,7 @@ func manageClone(log logr.Logger, target, sourceSpec kyvernov1.ResourceSpec, pol return newCreateGenerateResponse(sourceObjCopy.UnstructuredContent(), target, nil) } -func manageCloneList(log logr.Logger, targetNamespace string, ur kyvernov2.UpdateRequest, policy kyvernov1.PolicyInterface, rule kyvernov1.Rule, client dclient.Interface) []generateResponse { +func manageCloneList(log logr.Logger, targetNamespace string, policy kyvernov1.PolicyInterface, rule kyvernov1.Rule, client dclient.Interface) []generateResponse { var responses []generateResponse cloneList := rule.Generation.CloneList sourceNamespace := cloneList.Namespace @@ -109,7 +102,7 @@ func manageCloneList(log logr.Logger, targetNamespace string, ur kyvernov2.Updat for _, source := range sources.Items { target := newResourceSpec(source.GetAPIVersion(), source.GetKind(), targetNamespace, source.GetName()) responses = append(responses, - manageClone(log, target, newResourceSpec(source.GetAPIVersion(), source.GetKind(), source.GetNamespace(), source.GetName()), policy, ur, rule, client)) + manageClone(log, target, newResourceSpec(source.GetAPIVersion(), source.GetKind(), source.GetNamespace(), source.GetName()), policy, rule, client)) } } return responses diff --git a/pkg/background/generate/data.go b/pkg/background/generate/data.go index f6a374dc7f..32097b718b 100644 --- a/pkg/background/generate/data.go +++ b/pkg/background/generate/data.go @@ -2,18 +2,16 @@ package generate import ( "context" - "fmt" "github.com/go-logr/logr" kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1" - kyvernov2 "github.com/kyverno/kyverno/api/kyverno/v2" "github.com/kyverno/kyverno/pkg/clients/dclient" datautils "github.com/kyverno/kyverno/pkg/utils/data" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) -func manageData(log logr.Logger, target kyvernov1.ResourceSpec, data interface{}, synchronize bool, ur kyvernov2.UpdateRequest, client dclient.Interface) generateResponse { +func manageData(log logr.Logger, target kyvernov1.ResourceSpec, data interface{}, synchronize bool, client dclient.Interface) generateResponse { if data == nil { log.V(4).Info("data is nil - skipping update") return newSkipGenerateResponse(nil, target, nil) @@ -25,16 +23,9 @@ func manageData(log logr.Logger, target kyvernov1.ResourceSpec, data interface{} } targetObj, err := client.GetResource(context.TODO(), target.GetAPIVersion(), target.GetKind(), target.GetNamespace(), target.GetName()) - if err != nil { - if apierrors.IsNotFound(err) && len(ur.Status.GeneratedResources) != 0 && !synchronize { - log.V(4).Info("synchronize is disable - skip re-create") - return newSkipGenerateResponse(nil, target, nil) - } - if apierrors.IsNotFound(err) { - return newCreateGenerateResponse(resource, target, nil) - } - - return newSkipGenerateResponse(nil, target, fmt.Errorf("failed to get the target source: %v", err)) + if err != nil && apierrors.IsNotFound(err) { + // the target resource should always exist regardless of synchronize settings + return newCreateGenerateResponse(resource, target, nil) } log.V(4).Info("found target resource") diff --git a/pkg/background/generate/generate.go b/pkg/background/generate/generate.go index 408f532fed..cff081e90b 100644 --- a/pkg/background/generate/generate.go +++ b/pkg/background/generate/generate.go @@ -20,7 +20,6 @@ import ( "github.com/kyverno/kyverno/pkg/config" "github.com/kyverno/kyverno/pkg/engine" engineapi "github.com/kyverno/kyverno/pkg/engine/api" - enginecontext "github.com/kyverno/kyverno/pkg/engine/context" "github.com/kyverno/kyverno/pkg/engine/jmespath" "github.com/kyverno/kyverno/pkg/engine/validate" "github.com/kyverno/kyverno/pkg/engine/variables" @@ -31,13 +30,11 @@ import ( kubeutils "github.com/kyverno/kyverno/pkg/utils/kube" validationpolicy "github.com/kyverno/kyverno/pkg/validation/policy" "github.com/pkg/errors" + "go.uber.org/multierr" admissionv1 "k8s.io/api/admission/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/selection" corev1listers "k8s.io/client-go/listers/core/v1" "k8s.io/client-go/tools/cache" ) @@ -95,58 +92,56 @@ func NewGenerateController( } func (c *GenerateController) ProcessUR(ur *kyvernov2.UpdateRequest) error { - logger := c.log.WithValues("name", ur.GetName(), "policy", ur.Spec.GetPolicyKey(), "rule", ur.Spec.GetRuleName(), "resource", ur.Spec.GetResource().String()) - var err error + logger := c.log.WithValues("name", ur.GetName(), "policy", ur.Spec.GetPolicyKey()) var genResources []kyvernov1.ResourceSpec logger.Info("start processing UR", "ur", ur.Name, "resourceVersion", ur.GetResourceVersion()) - trigger, err := c.getTrigger(ur.Spec) - if err != nil || trigger == nil { - logger.V(3).Info("the trigger resource does not exist or is pending creation") - if err := updateStatus(c.statusControl, *ur, err, nil); err != nil { - return err - } - return nil - } - - namespaceLabels := engineutils.GetNamespaceSelectorsFromNamespaceLister(trigger.GetKind(), trigger.GetNamespace(), c.nsLister, logger) - genResources, err = c.applyGenerate(*trigger, *ur, namespaceLabels) - if err != nil { - if strings.Contains(err.Error(), doesNotApply) { - ur.Status.State = kyvernov2.Completed - logger.V(4).Info(fmt.Sprintf("%s, updating UR status to Completed", err.Error())) - _, err := c.kyvernoClient.KyvernoV2().UpdateRequests(config.KyvernoNamespace()).UpdateStatus(context.TODO(), ur, metav1.UpdateOptions{}) - return err + var failures []error + for i := 0; i < len(ur.Spec.RuleContext); i++ { + rule := ur.Spec.RuleContext[i] + trigger, err := c.getTrigger(ur.Spec, i) + if err != nil || trigger == nil { + logger.V(4).Info("the trigger resource does not exist or is pending creation") + failures = append(failures, fmt.Errorf("rule %s failed: failed to fetch trigger resource: %v", rule.Rule, err)) + continue } - policy, err := c.getPolicySpec(*ur) + namespaceLabels := engineutils.GetNamespaceSelectorsFromNamespaceLister(trigger.GetKind(), trigger.GetNamespace(), c.nsLister, logger) + genResources, err = c.applyGenerate(*trigger, *ur, i, namespaceLabels) if err != nil { - return err + if strings.Contains(err.Error(), doesNotApply) { + logger.V(4).Info(fmt.Sprintf("skipping rule %s: %v", rule.Rule, err.Error())) + } + + policy, err := c.getPolicyObject(*ur) + if err != nil { + failures = append(failures, fmt.Errorf("rule %v failed: failed to get policy object: %s", rule.Rule, err)) + continue + } + + events := event.NewBackgroundFailedEvent(err, policy, ur.Spec.RuleContext[i].Rule, event.GeneratePolicyController, + kyvernov1.ResourceSpec{Kind: trigger.GetKind(), Namespace: trigger.GetNamespace(), Name: trigger.GetName()}) + c.eventGen.Add(events...) } - - events := event.NewBackgroundFailedEvent(err, policy, ur.Spec.Rule, event.GeneratePolicyController, - kyvernov1.ResourceSpec{Kind: trigger.GetKind(), Namespace: trigger.GetNamespace(), Name: trigger.GetName()}) - c.eventGen.Add(events...) } - if err = updateStatus(c.statusControl, *ur, err, genResources); err != nil { - return err - } - return err + return updateStatus(c.statusControl, *ur, multierr.Combine(failures...), genResources) } const doesNotApply = "policy does not apply to resource" -func (c *GenerateController) getTrigger(spec kyvernov2.UpdateRequestSpec) (*unstructured.Unstructured, error) { +func (c *GenerateController) getTrigger(spec kyvernov2.UpdateRequestSpec, i int) (*unstructured.Unstructured, error) { + resourceSpec := spec.RuleContext[i].Trigger + c.log.V(4).Info("fetching trigger", "trigger", resourceSpec.String()) admissionRequest := spec.Context.AdmissionRequestInfo.AdmissionRequest if admissionRequest == nil { - return common.GetResource(c.client, spec, c.log) + return common.GetResource(c.client, resourceSpec, spec, c.log) } else { operation := spec.Context.AdmissionRequestInfo.Operation if operation == admissionv1.Delete { - return c.getTriggerForDeleteOperation(spec) + return c.getTriggerForDeleteOperation(spec, i) } else if operation == admissionv1.Create { - return c.getTriggerForCreateOperation(spec) + return c.getTriggerForCreateOperation(spec, i) } else { newResource, oldResource, err := admissionutils.ExtractResources(nil, *admissionRequest) if err != nil { @@ -163,24 +158,26 @@ func (c *GenerateController) getTrigger(spec kyvernov2.UpdateRequestSpec) (*unst } } -func (c *GenerateController) getTriggerForDeleteOperation(spec kyvernov2.UpdateRequestSpec) (*unstructured.Unstructured, error) { +func (c *GenerateController) getTriggerForDeleteOperation(spec kyvernov2.UpdateRequestSpec, i int) (*unstructured.Unstructured, error) { request := spec.Context.AdmissionRequestInfo.AdmissionRequest _, oldResource, err := admissionutils.ExtractResources(nil, *request) if err != nil { return nil, fmt.Errorf("failed to load resource from context: %w", err) } labels := oldResource.GetLabels() + resourceSpec := spec.RuleContext[i].Trigger if labels[common.GeneratePolicyLabel] != "" { // non-trigger deletion, get trigger from ur spec c.log.V(4).Info("non-trigger resource is deleted, fetching the trigger from the UR spec", "trigger", spec.Resource.String()) - return common.GetResource(c.client, spec, c.log) + return common.GetResource(c.client, resourceSpec, spec, c.log) } return &oldResource, nil } -func (c *GenerateController) getTriggerForCreateOperation(spec kyvernov2.UpdateRequestSpec) (*unstructured.Unstructured, error) { +func (c *GenerateController) getTriggerForCreateOperation(spec kyvernov2.UpdateRequestSpec, i int) (*unstructured.Unstructured, error) { admissionRequest := spec.Context.AdmissionRequestInfo.AdmissionRequest - trigger, err := common.GetResource(c.client, spec, c.log) + resourceSpec := spec.RuleContext[i].Trigger + trigger, err := common.GetResource(c.client, resourceSpec, spec, c.log) if err != nil || trigger == nil { if admissionRequest.SubResource == "" { return nil, err @@ -197,22 +194,36 @@ func (c *GenerateController) getTriggerForCreateOperation(spec kyvernov2.UpdateR return trigger, err } -func (c *GenerateController) applyGenerate(resource unstructured.Unstructured, ur kyvernov2.UpdateRequest, namespaceLabels map[string]string) ([]kyvernov1.ResourceSpec, error) { - logger := c.log.WithValues("name", ur.GetName(), "policy", ur.Spec.GetPolicyKey(), "rule", ur.Spec.GetRuleName(), "resource", ur.Spec.GetResource().String()) - logger.V(3).Info("applying generate policy rule") +func (c *GenerateController) applyGenerate(trigger unstructured.Unstructured, ur kyvernov2.UpdateRequest, i int, namespaceLabels map[string]string) ([]kyvernov1.ResourceSpec, error) { + logger := c.log.WithValues("name", ur.GetName(), "policy", ur.Spec.GetPolicyKey()) + logger.V(3).Info("applying generate policy") - policy, err := c.getPolicySpec(ur) + policy, err := c.getPolicyObject(ur) if err != nil && !apierrors.IsNotFound(err) { logger.Error(err, "error in fetching policy") return nil, err } - if ur.Spec.DeleteDownstream || apierrors.IsNotFound(err) { - err = c.deleteDownstream(policy, &ur) + ruleContext := ur.Spec.RuleContext[i] + if ruleContext.DeleteDownstream || apierrors.IsNotFound(err) { + err = c.deleteDownstream(policy, ruleContext, &ur) return nil, err } - policyContext, err := common.NewBackgroundContext(logger, c.client, &ur, policy, &resource, c.configuration, c.jp, namespaceLabels) + var rule *kyvernov1.Rule + p := policy.CreateDeepCopy() + for j := range p.GetSpec().Rules { + if p.GetSpec().Rules[j].Name == ruleContext.Rule { + rule = &p.GetSpec().Rules[j] + break + } + } + if rule == nil { + logger.Info("skip rule application as the rule does not exist in the updaterequest", "rule", ruleContext.Rule) + return nil, nil + } + p.GetSpec().SetRules([]kyvernov1.Rule{*rule}) + policyContext, err := common.NewBackgroundContext(logger, c.client, ur.Spec.Context, p, &trigger, c.configuration, c.jp, namespaceLabels) if err != nil { return nil, err } @@ -235,62 +246,17 @@ func (c *GenerateController) applyGenerate(resource unstructured.Unstructured, u } var applicableRules []string - // Removing UR if rule is failed. Used when the generate condition failed but ur exist for _, r := range engineResponse.PolicyResponse.Rules { - if r.Name() != ur.Spec.GetRuleName() { - continue - } - - if r.Status() != engineapi.RuleStatusPass { - logger.V(4).Info("querying all update requests") - selector := labels.SelectorFromSet(labels.Set(map[string]string{ - kyvernov2.URGeneratePolicyLabel: engineResponse.Policy().GetName(), - kyvernov2.URGenerateResourceKindLabel: engineResponse.Resource.GetKind(), - kyvernov2.URGenerateResourceNSLabel: engineResponse.Resource.GetNamespace(), - })) - // get update requests that have the resource UID label - requirement, err := labels.NewRequirement(kyvernov2.URGenerateResourceUIDLabel, selection.Equals, []string{string(engineResponse.Resource.GetUID())}) - if err != nil { - logger.Error(err, "failed to add the resource UID label") - } - selectorWithResUID := selector.Add(*requirement) - urList, err := c.urLister.List(selectorWithResUID) - if err != nil { - logger.Error(err, "failed to get update request for the resource", "kind", engineResponse.Resource.GetKind(), "name", engineResponse.Resource.GetName(), "namespace", engineResponse.Resource.GetNamespace()) - continue - } - - if len(urList) == 0 { - // get update requests that have the resource name label - requirement, err = labels.NewRequirement(kyvernov2.URGenerateResourceNameLabel, selection.Equals, []string{engineResponse.Resource.GetName()}) - if err != nil { - logger.Error(err, "failed to add the resource name label") - continue - } - selectorWithResName := selector.Add(*requirement) - urList, err = c.urLister.List(selectorWithResName) - if err != nil { - logger.Error(err, "failed to get update request for the resource", "kind", engineResponse.Resource.GetKind(), "name", engineResponse.Resource.GetName(), "namespace", engineResponse.Resource.GetNamespace()) - continue - } - } - - for _, v := range urList { - err := c.kyvernoClient.KyvernoV2().UpdateRequests(config.KyvernoNamespace()).Delete(context.TODO(), v.GetName(), metav1.DeleteOptions{}) - if err != nil { - logger.Error(err, "failed to delete update request") - } - } - } else { + if r.Status() == engineapi.RuleStatusPass { applicableRules = append(applicableRules, r.Name()) } } // Apply the generate rule on resource - genResources, err := c.ApplyGeneratePolicy(logger, policyContext, ur, applicableRules) + genResources, err := c.ApplyGeneratePolicy(logger, policyContext, applicableRules) if err == nil { for _, res := range genResources { - e := event.NewResourceGenerationEvent(ur.Spec.Policy, ur.Spec.Rule, event.GeneratePolicyController, res) + e := event.NewResourceGenerationEvent(ur.Spec.Policy, ur.Spec.RuleContext[i].Rule, event.GeneratePolicyController, res) c.eventGen.Add(e) } @@ -301,8 +267,8 @@ func (c *GenerateController) applyGenerate(resource unstructured.Unstructured, u return genResources, err } -// getPolicySpec gets the policy spec from the ClusterPolicy/Policy -func (c *GenerateController) getPolicySpec(ur kyvernov2.UpdateRequest) (kyvernov1.PolicyInterface, error) { +// getPolicyObject gets the policy spec from the ClusterPolicy/Policy +func (c *GenerateController) getPolicyObject(ur kyvernov2.UpdateRequest) (kyvernov1.PolicyInterface, error) { pNamespace, pName, err := cache.SplitMetaNamespaceKey(ur.Spec.Policy) if err != nil { return nil, err @@ -335,12 +301,9 @@ func updateStatus(statusControl common.StatusControlInterface, ur kyvernov2.Upda return nil } -func (c *GenerateController) ApplyGeneratePolicy(log logr.Logger, policyContext *engine.PolicyContext, ur kyvernov2.UpdateRequest, applicableRules []string) (genResources []kyvernov1.ResourceSpec, err error) { - // Get the response as the actions to be performed on the resource - // - - substitute values +func (c *GenerateController) ApplyGeneratePolicy(log logr.Logger, policyContext *engine.PolicyContext, applicableRules []string) (genResources []kyvernov1.ResourceSpec, err error) { policy := policyContext.Policy() resource := policyContext.NewResource() - jsonContext := policyContext.JSONContext() // To manage existing resources, we compare the creation time for the default resource to be generated and policy creation time ruleNameToProcessingTime := make(map[string]time.Duration) applyRules := policy.GetSpec().GetApplyRules() @@ -355,7 +318,6 @@ func (c *GenerateController) ApplyGeneratePolicy(log logr.Logger, policyContext if !slices.Contains(applicableRules, rule.Name) { continue } - if rule.Generation.Synchronize { ruleRaw, err := json.Marshal(rule.DeepCopy()) if err != nil { @@ -377,7 +339,7 @@ func (c *GenerateController) ApplyGeneratePolicy(log logr.Logger, policyContext if applyRules == kyvernov1.ApplyOne && applyCount > 0 { break } - + logger := log.WithValues("rule", rule.Name) // add configmap json data to context if err := c.engine.ContextLoader(policyContext.Policy(), rule)(context.TODO(), rule.Context, policyContext.JSONContext()); err != nil { log.Error(err, "cannot add configmaps to context") @@ -389,7 +351,7 @@ func (c *GenerateController) ApplyGeneratePolicy(log logr.Logger, policyContext return nil, err } - genResource, err = applyRule(log, c.client, rule, resource, jsonContext, policy, ur) + genResource, err = applyRule(logger, c.client, rule, resource, policy) if err != nil { log.Error(err, "failed to apply generate rule", "policy", policy.GetName(), "rule", rule.Name, "resource", resource.GetName()) return nil, err @@ -402,7 +364,7 @@ func (c *GenerateController) ApplyGeneratePolicy(log logr.Logger, policyContext return genResources, nil } -func applyRule(log logr.Logger, client dclient.Interface, rule kyvernov1.Rule, trigger unstructured.Unstructured, ctx enginecontext.EvalInterface, policy kyvernov1.PolicyInterface, ur kyvernov2.UpdateRequest) ([]kyvernov1.ResourceSpec, error) { +func applyRule(log logr.Logger, client dclient.Interface, rule kyvernov1.Rule, trigger unstructured.Unstructured, policy kyvernov1.PolicyInterface) ([]kyvernov1.ResourceSpec, error) { responses := []generateResponse{} var err error var newGenResources []kyvernov1.ResourceSpec @@ -411,12 +373,12 @@ func applyRule(log logr.Logger, client dclient.Interface, rule kyvernov1.Rule, t logger := log.WithValues("target", target.String()) if rule.Generation.Clone.Name != "" { - resp := manageClone(logger.WithValues("type", "clone"), target, kyvernov1.ResourceSpec{}, policy, ur, rule, client) + resp := manageClone(logger.WithValues("type", "clone"), target, kyvernov1.ResourceSpec{}, policy, rule, client) responses = append(responses, resp) } else if len(rule.Generation.CloneList.Kinds) != 0 { - responses = manageCloneList(logger.WithValues("type", "cloneList"), target.GetNamespace(), ur, policy, rule, client) + responses = manageCloneList(logger.WithValues("type", "cloneList"), target.GetNamespace(), policy, rule, client) } else { - resp := manageData(logger.WithValues("type", "data"), target, rule.Generation.RawData, rule.Generation.Synchronize, ur, client) + resp := manageData(logger.WithValues("type", "data"), target, rule.Generation.RawData, rule.Generation.Synchronize, client) responses = append(responses, resp) } @@ -464,7 +426,7 @@ func applyRule(log logr.Logger, client dclient.Interface, rule kyvernov1.Rule, t } else if response.GetAction() == Update { generatedObj, err := client.GetResource(context.TODO(), targetMeta.GetAPIVersion(), targetMeta.GetKind(), targetMeta.GetNamespace(), targetMeta.GetName()) if err != nil { - logger.V(2).Info("target resource not found, creating new target") + logger.V(2).Info("creating new target due to the failure when fetching", "err", err.Error()) if policy.GetSpec().UseServerSideApply { _, err = client.ApplyResource(context.TODO(), targetMeta.GetAPIVersion(), targetMeta.GetKind(), targetMeta.GetNamespace(), targetMeta.GetName(), newResource, false, "generate") } else { diff --git a/pkg/background/generate/validate.go b/pkg/background/generate/validate.go index e2fa32ef71..8d1522856e 100644 --- a/pkg/background/generate/validate.go +++ b/pkg/background/generate/validate.go @@ -43,7 +43,7 @@ func validateResourceElement(log logr.Logger, resourceElement, patternElement, o log.V(4).Info("Pattern and resource have different structures.", "path", path, "expected", fmt.Sprintf("%T", patternElement), "current", fmt.Sprintf("%T", resourceElement)) return path, fmt.Errorf("pattern and resource have different structures. Path: %s. Expected %T, found %T", path, patternElement, resourceElement) } - return validateMap(log, typedResourceElement, typedPatternElement, originPattern, path) + return validateMap(typedResourceElement, typedPatternElement, originPattern, path) // array case []interface{}: typedResourceElement, ok := resourceElement.([]interface{}) @@ -67,7 +67,7 @@ func validateResourceElement(log logr.Logger, resourceElement, patternElement, o // If validateResourceElement detects map element inside resource and pattern trees, it goes to validateMap // For each element of the map we must detect the type again, so we pass these elements to validateResourceElement -func validateMap(log logr.Logger, resourceMap, patternMap map[string]interface{}, origPattern interface{}, path string) (string, error) { +func validateMap(resourceMap, patternMap map[string]interface{}, origPattern interface{}, path string) (string, error) { patternMap = wildcards.ExpandInMetadata(patternMap, resourceMap) sortedResourceKeys := list.New() for k := range patternMap { diff --git a/pkg/background/mutate/mutate.go b/pkg/background/mutate/mutate.go index e4d37a9336..0d2e6130e2 100644 --- a/pkg/background/mutate/mutate.go +++ b/pkg/background/mutate/mutate.go @@ -94,7 +94,7 @@ func (c *mutateExistingController) ProcessUR(ur *kyvernov2.UpdateRequest) error var trigger *unstructured.Unstructured admissionRequest := ur.Spec.Context.AdmissionRequestInfo.AdmissionRequest if admissionRequest == nil { - trigger, err = common.GetResource(c.client, ur.Spec, c.log) + trigger, err = common.GetResource(c.client, ur.Spec.Resource, ur.Spec, c.log) if err != nil || trigger == nil { logger.WithName(rule.Name).Error(err, "failed to get trigger resource") if err := updateURStatus(c.statusControl, *ur, err); err != nil { @@ -104,7 +104,7 @@ func (c *mutateExistingController) ProcessUR(ur *kyvernov2.UpdateRequest) error } } else { if admissionRequest.Operation == admissionv1.Create { - trigger, err = common.GetResource(c.client, ur.Spec, c.log) + trigger, err = common.GetResource(c.client, ur.Spec.Resource, ur.Spec, c.log) if err != nil || trigger == nil { if admissionRequest.SubResource == "" { logger.WithName(rule.Name).Error(err, "failed to get trigger resource") @@ -139,7 +139,7 @@ func (c *mutateExistingController) ProcessUR(ur *kyvernov2.UpdateRequest) error } namespaceLabels := engineutils.GetNamespaceSelectorsFromNamespaceLister(trigger.GetKind(), trigger.GetNamespace(), c.nsLister, logger) - policyContext, err := common.NewBackgroundContext(logger, c.client, ur, policy, trigger, c.configuration, c.jp, namespaceLabels) + policyContext, err := common.NewBackgroundContext(logger, c.client, ur.Spec.Context, policy, trigger, c.configuration, c.jp, namespaceLabels) if err != nil { logger.WithName(rule.Name).Error(err, "failed to build policy context") errs = append(errs, err) diff --git a/pkg/client/applyconfigurations/kyverno/v2/rulecontext.go b/pkg/client/applyconfigurations/kyverno/v2/rulecontext.go new file mode 100644 index 0000000000..373ea59966 --- /dev/null +++ b/pkg/client/applyconfigurations/kyverno/v2/rulecontext.go @@ -0,0 +1,70 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v2 + +import ( + v1 "github.com/kyverno/kyverno/pkg/client/applyconfigurations/kyverno/v1" +) + +// RuleContextApplyConfiguration represents an declarative configuration of the RuleContext type for use +// with apply. +type RuleContextApplyConfiguration struct { + Rule *string `json:"rule,omitempty"` + DeleteDownstream *bool `json:"deleteDownstream,omitempty"` + Synchronize *bool `json:"synchronize,omitempty"` + Trigger *v1.ResourceSpecApplyConfiguration `json:"trigger,omitempty"` +} + +// RuleContextApplyConfiguration constructs an declarative configuration of the RuleContext type for use with +// apply. +func RuleContext() *RuleContextApplyConfiguration { + return &RuleContextApplyConfiguration{} +} + +// WithRule sets the Rule field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Rule field is set to the value of the last call. +func (b *RuleContextApplyConfiguration) WithRule(value string) *RuleContextApplyConfiguration { + b.Rule = &value + return b +} + +// WithDeleteDownstream sets the DeleteDownstream field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeleteDownstream field is set to the value of the last call. +func (b *RuleContextApplyConfiguration) WithDeleteDownstream(value bool) *RuleContextApplyConfiguration { + b.DeleteDownstream = &value + return b +} + +// WithSynchronize sets the Synchronize field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Synchronize field is set to the value of the last call. +func (b *RuleContextApplyConfiguration) WithSynchronize(value bool) *RuleContextApplyConfiguration { + b.Synchronize = &value + return b +} + +// WithTrigger sets the Trigger field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Trigger field is set to the value of the last call. +func (b *RuleContextApplyConfiguration) WithTrigger(value *v1.ResourceSpecApplyConfiguration) *RuleContextApplyConfiguration { + b.Trigger = value + return b +} diff --git a/pkg/client/applyconfigurations/kyverno/v2/updaterequestspec.go b/pkg/client/applyconfigurations/kyverno/v2/updaterequestspec.go index 0f1977f6ce..6e6bf59bdb 100644 --- a/pkg/client/applyconfigurations/kyverno/v2/updaterequestspec.go +++ b/pkg/client/applyconfigurations/kyverno/v2/updaterequestspec.go @@ -28,6 +28,7 @@ import ( type UpdateRequestSpecApplyConfiguration struct { Type *v2.RequestType `json:"requestType,omitempty"` Policy *string `json:"policy,omitempty"` + RuleContext []RuleContextApplyConfiguration `json:"ruleContext,omitempty"` Rule *string `json:"rule,omitempty"` DeleteDownstream *bool `json:"deleteDownstream,omitempty"` Synchronize *bool `json:"synchronize,omitempty"` @@ -57,6 +58,19 @@ func (b *UpdateRequestSpecApplyConfiguration) WithPolicy(value string) *UpdateRe return b } +// WithRuleContext adds the given value to the RuleContext field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the RuleContext field. +func (b *UpdateRequestSpecApplyConfiguration) WithRuleContext(values ...*RuleContextApplyConfiguration) *UpdateRequestSpecApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithRuleContext") + } + b.RuleContext = append(b.RuleContext, *values[i]) + } + return b +} + // WithRule sets the Rule field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the Rule field is set to the value of the last call. diff --git a/pkg/client/applyconfigurations/utils.go b/pkg/client/applyconfigurations/utils.go index f0c396bac2..576c664969 100644 --- a/pkg/client/applyconfigurations/utils.go +++ b/pkg/client/applyconfigurations/utils.go @@ -185,6 +185,8 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &kyvernov2.PolicyExceptionSpecApplyConfiguration{} case v2.SchemeGroupVersion.WithKind("RequestInfo"): return &kyvernov2.RequestInfoApplyConfiguration{} + case v2.SchemeGroupVersion.WithKind("RuleContext"): + return &kyvernov2.RuleContextApplyConfiguration{} case v2.SchemeGroupVersion.WithKind("UpdateRequest"): return &kyvernov2.UpdateRequestApplyConfiguration{} case v2.SchemeGroupVersion.WithKind("UpdateRequestSpec"): diff --git a/pkg/event/events.go b/pkg/event/events.go index 2251f5d59d..7b88a4b4e2 100644 --- a/pkg/event/events.go +++ b/pkg/event/events.go @@ -167,6 +167,12 @@ func NewBackgroundFailedEvent(err error, policy kyvernov1.PolicyInterface, rule Namespace: policy.GetNamespace(), UID: policy.GetUID(), } + var msg string + if rule == "" { + msg = fmt.Sprintf("policy %s error: %v", policy.GetName(), err) + } else { + msg = fmt.Sprintf("policy %s/%s error: %v", policy.GetName(), rule, err) + } events = append(events, Info{ Regarding: regarding, Related: &corev1.ObjectReference{ @@ -178,7 +184,7 @@ func NewBackgroundFailedEvent(err error, policy kyvernov1.PolicyInterface, rule }, Source: source, Reason: PolicyError, - Message: fmt.Sprintf("policy %s/%s error: %v", policy.GetName(), rule, err), + Message: msg, Action: None, }) @@ -366,6 +372,12 @@ func NewValidatingAdmissionPolicyEvent(policy kyvernov1.PolicyInterface, vapName } func NewFailedEvent(err error, policy, rule string, source Source, resource kyvernov1.ResourceSpec) Info { + var msg string + if rule == "" { + msg = fmt.Sprintf("policy %s error: %v", policy, err) + } else { + msg = fmt.Sprintf("policy %s/%s error: %v", policy, rule, err) + } return Info{ Regarding: corev1.ObjectReference{ APIVersion: resource.APIVersion, @@ -376,7 +388,7 @@ func NewFailedEvent(err error, policy, rule string, source Source, resource kyve }, Source: source, Reason: PolicyError, - Message: fmt.Sprintf("policy %s/%s error: %v", policy, rule, err), + Message: msg, Action: None, } } diff --git a/pkg/policy/generate.go b/pkg/policy/generate.go index 9ba2d61644..40c62c921e 100644 --- a/pkg/policy/generate.go +++ b/pkg/policy/generate.go @@ -11,6 +11,7 @@ import ( "github.com/kyverno/kyverno/pkg/background/common" generateutils "github.com/kyverno/kyverno/pkg/background/generate" "github.com/kyverno/kyverno/pkg/config" + engineutils "github.com/kyverno/kyverno/pkg/utils/engine" "go.uber.org/multierr" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -25,10 +26,6 @@ func (pc *policyController) handleGenerate(policyKey string, policy kyvernov1.Po return err } - if !policy.GetSpec().IsGenerateExisting() { - return nil - } - logger.V(4).Info("reconcile policy with generateExisting enabled") if err := pc.handleGenerateForExisting(policy); err != nil { logger.Error(err, "failed to create UR for generateExisting") @@ -37,65 +34,9 @@ func (pc *policyController) handleGenerate(policyKey string, policy kyvernov1.Po return nil } -func (pc *policyController) handleGenerateForExisting(policy kyvernov1.PolicyInterface) error { - var errors []error - var triggers []*unstructured.Unstructured - ruleType := kyvernov2.Generate - spec := policy.GetSpec() - policyNew := policy.CreateDeepCopy() - policyNew.GetSpec().Rules = nil - - for _, rule := range spec.Rules { - // check if the rule sets the generateExisting field. - // if not, use the policy level setting - generateExisting := rule.Generation.GenerateExisting - if generateExisting != nil { - if !*generateExisting { - continue - } - } else if !spec.GenerateExisting { - continue - } - - triggers = getTriggers(pc.client, rule, policy.IsNamespaced(), policy.GetNamespace(), pc.log) - policyNew.GetSpec().SetRules([]kyvernov1.Rule{rule}) - for _, trigger := range triggers { - ur := newUR(policyNew, common.ResourceSpecFromUnstructured(*trigger), rule.Name, ruleType, false) - skip, err := pc.handleUpdateRequest(ur, trigger, rule.Name, policyNew) - if err != nil { - pc.log.Error(err, "failed to create new UR on policy update", "policy", policyNew.GetName(), "rule", rule.Name, "rule type", ruleType, - "target", fmt.Sprintf("%s/%s/%s/%s", trigger.GetAPIVersion(), trigger.GetKind(), trigger.GetNamespace(), trigger.GetName())) - errors = append(errors, err) - continue - } - - if skip { - continue - } - - pc.log.V(4).Info("successfully created UR on policy update", "policy", policyNew.GetName(), "rule", rule.Name, "rule type", ruleType, - "target", fmt.Sprintf("%s/%s/%s/%s", trigger.GetAPIVersion(), trigger.GetKind(), trigger.GetNamespace(), trigger.GetName())) - } - } - return multierr.Combine(errors...) -} - -func (pc *policyController) createURForDownstreamDeletion(policy kyvernov1.PolicyInterface) error { - var errs []error - rules := autogen.ComputeRules(policy, "") - for _, r := range rules { - generateType, sync, orphanDownstreamOnPolicyDelete := r.GetTypeAndSyncAndOrphanDownstream() - if sync && (generateType == kyvernov1.Data) && !orphanDownstreamOnPolicyDelete { - if err := pc.syncDataPolicyChanges(policy, true); err != nil { - errs = append(errs, err) - } - } - } - return multierr.Combine(errs...) -} - func (pc *policyController) syncDataPolicyChanges(policy kyvernov1.PolicyInterface, deleteDownstream bool) error { - var errorList []error + var errs []error + ur := newGenerateUR(policy) for _, rule := range policy.GetSpec().Rules { generate := rule.Generation if !generate.Synchronize { @@ -104,14 +45,139 @@ func (pc *policyController) syncDataPolicyChanges(policy kyvernov1.PolicyInterfa if generate.GetData() == nil { continue } - if err := pc.syncDataRulechanges(policy, rule, deleteDownstream); err != nil { - errorList = append(errorList, err) + var err error + if ur, err = pc.buildUrForDataRuleChanges(policy, ur, rule, deleteDownstream, false); err != nil { + errs = append(errs, err) } } - return multierr.Combine(errorList...) + + if len(ur.Spec.RuleContext) == 0 { + return multierr.Combine(errs...) + } + pc.log.V(2).WithName("syncDataPolicyChanges").Info("creating new UR for generate") + created, err := pc.urGenerator.Generate(context.TODO(), pc.kyvernoClient, ur, pc.log) + if err != nil { + errs = append(errs, err) + } + if created != nil { + updated := created.DeepCopy() + updated.Status.State = kyvernov2.Pending + _, err = pc.kyvernoClient.KyvernoV2().UpdateRequests(config.KyvernoNamespace()).UpdateStatus(context.TODO(), updated, metav1.UpdateOptions{}) + if err != nil { + errs = append(errs, err) + } + } + return multierr.Combine(errs...) } -func (pc *policyController) syncDataRulechanges(policy kyvernov1.PolicyInterface, rule kyvernov1.Rule, deleteDownstream bool) error { +func (pc *policyController) handleGenerateForExisting(policy kyvernov1.PolicyInterface) error { + var errors []error + var triggers []*unstructured.Unstructured + policyNew := policy.CreateDeepCopy() + policyNew.GetSpec().Rules = nil + ur := newGenerateUR(policy) + logger := pc.log.WithName("handleGenerateForExisting") + for _, rule := range policy.GetSpec().Rules { + if !rule.HasGenerate() { + continue + } + + // check if the rule sets the generateExisting field. + // if not, use the policy level setting + generateExisting := rule.Generation.GenerateExisting + if generateExisting != nil { + if !*generateExisting { + continue + } + } else if !policy.GetSpec().GenerateExisting { + continue + } + + triggers = getTriggers(pc.client, rule, policy.IsNamespaced(), policy.GetNamespace(), pc.log) + policyNew.GetSpec().SetRules([]kyvernov1.Rule{rule}) + for _, trigger := range triggers { + namespaceLabels := engineutils.GetNamespaceSelectorsFromNamespaceLister(trigger.GetKind(), trigger.GetNamespace(), pc.nsLister, pc.log) + policyContext, err := common.NewBackgroundContext(pc.log, pc.client, ur.Spec.Context, policy, trigger, pc.configuration, pc.jp, namespaceLabels) + if err != nil { + errors = append(errors, fmt.Errorf("failed to build policy context for rule %s: %w", rule.Name, err)) + continue + } + + engineResponse := pc.engine.ApplyBackgroundChecks(context.TODO(), policyContext) + if len(engineResponse.PolicyResponse.Rules) == 0 { + continue + } + logger.V(4).Info("adding rule context", "rule", rule.Name, "trigger", trigger.GetNamespace()+"/"+trigger.GetName()) + addRuleContext(ur, rule.Name, common.ResourceSpecFromUnstructured(*trigger), false) + } + } + + if len(ur.Spec.RuleContext) == 0 { + return multierr.Combine(errors...) + } + + logger.V(2).Info("creating new UR for generate") + created, err := pc.urGenerator.Generate(context.TODO(), pc.kyvernoClient, ur, pc.log) + if err != nil { + errors = append(errors, err) + return multierr.Combine(errors...) + } + if created != nil { + updated := created.DeepCopy() + updated.Status.State = kyvernov2.Pending + _, err = pc.kyvernoClient.KyvernoV2().UpdateRequests(config.KyvernoNamespace()).UpdateStatus(context.TODO(), updated, metav1.UpdateOptions{}) + if err != nil { + errors = append(errors, err) + return multierr.Combine(errors...) + } + pc.log.V(4).Info("successfully created UR on policy update", "policy", policyNew.GetName()) + } + return multierr.Combine(errors...) +} + +func (pc *policyController) createURForDownstreamDeletion(policy kyvernov1.PolicyInterface) error { + var errs []error + rules := autogen.ComputeRules(policy, "") + ur := newGenerateUR(policy) + for _, r := range rules { + generate := r.Generation + if !generate.Synchronize { + continue + } + if generate.GetData() == nil { + continue + } + generateType, sync, orphanDownstreamOnPolicyDelete := r.GetTypeAndSyncAndOrphanDownstream() + if sync && (generateType == kyvernov1.Data) && !orphanDownstreamOnPolicyDelete { + var err error + if ur, err = pc.buildUrForDataRuleChanges(policy, ur, r, true, true); err != nil { + errs = append(errs, err) + } + } + } + + if len(ur.Spec.RuleContext) == 0 { + return multierr.Combine(errs...) + } + + pc.log.V(2).WithName("createURForDownstreamDeletion").Info("creating new UR for generate") + created, err := pc.urGenerator.Generate(context.TODO(), pc.kyvernoClient, ur, pc.log) + if err != nil { + errs = append(errs, err) + } + if created != nil { + updated := created.DeepCopy() + updated.Status.State = kyvernov2.Pending + updated.Status.GeneratedResources = ur.Status.GeneratedResources + _, err = pc.kyvernoClient.KyvernoV2().UpdateRequests(config.KyvernoNamespace()).UpdateStatus(context.TODO(), updated, metav1.UpdateOptions{}) + if err != nil { + errs = append(errs, err) + } + } + return multierr.Combine(errs...) +} + +func (pc *policyController) buildUrForDataRuleChanges(policy kyvernov1.PolicyInterface, ur *kyvernov2.UpdateRequest, rule kyvernov1.Rule, deleteDownstream, policyDeletion bool) (*kyvernov2.UpdateRequest, error) { labels := map[string]string{ common.GeneratePolicyLabel: policy.GetName(), common.GeneratePolicyNamespaceLabel: policy.GetNamespace(), @@ -121,29 +187,24 @@ func (pc *policyController) syncDataRulechanges(policy kyvernov1.PolicyInterface downstreams, err := common.FindDownstream(pc.client, rule.Generation.GetAPIVersion(), rule.Generation.GetKind(), labels) if err != nil { - return err + return ur, err + } + + if len(downstreams.Items) == 0 { + return ur, nil } pc.log.V(4).Info("sync data rule changes to downstream targets") - var errorList []error for _, downstream := range downstreams.Items { labels := downstream.GetLabels() trigger := generateutils.TriggerFromLabels(labels) - ur := newUR(policy, trigger, rule.Name, kyvernov2.Generate, deleteDownstream) - created, err := pc.kyvernoClient.KyvernoV2().UpdateRequests(config.KyvernoNamespace()).Create(context.TODO(), ur, metav1.CreateOptions{}) - if err != nil { - errorList = append(errorList, err) - continue - } - updated := created.DeepCopy() - updated.Status = newURStatus(downstream) - _, err = pc.kyvernoClient.KyvernoV2().UpdateRequests(config.KyvernoNamespace()).UpdateStatus(context.TODO(), updated, metav1.UpdateOptions{}) - if err != nil { - errorList = append(errorList, err) - continue + addRuleContext(ur, rule.Name, trigger, deleteDownstream) + if policyDeletion { + addGeneratedResources(ur, downstream) } } - return multierr.Combine(errorList...) + + return ur, nil } // ruleDeletion returns true if any rule is deleted, along with deleted rules diff --git a/pkg/policy/mutate.go b/pkg/policy/mutate.go index d52a5beb75..401031a47c 100644 --- a/pkg/policy/mutate.go +++ b/pkg/policy/mutate.go @@ -43,7 +43,7 @@ func (pc *policyController) handleMutate(policyKey string, policy kyvernov1.Poli } logger.Info("creating new UR for mutate") - ur := newUR(policy, backgroundcommon.ResourceSpecFromUnstructured(*trigger), rule.Name, ruleType, false) + ur := newMutateUR(policy, backgroundcommon.ResourceSpecFromUnstructured(*trigger), rule.Name) skip, err := pc.handleUpdateRequest(ur, trigger, rule.Name, policyNew) if err != nil { pc.log.Error(err, "failed to create new UR on policy update", "policy", policyNew.GetName(), "rule", rule.Name, "rule type", ruleType, diff --git a/pkg/policy/policy_controller.go b/pkg/policy/policy_controller.go index 4eb10025fe..eb31677f66 100644 --- a/pkg/policy/policy_controller.go +++ b/pkg/policy/policy_controller.go @@ -398,7 +398,7 @@ func (pc *policyController) requeuePolicies() { func (pc *policyController) handleUpdateRequest(ur *kyvernov2.UpdateRequest, triggerResource *unstructured.Unstructured, ruleName string, policy kyvernov1.PolicyInterface) (skip bool, err error) { namespaceLabels := engineutils.GetNamespaceSelectorsFromNamespaceLister(triggerResource.GetKind(), triggerResource.GetNamespace(), pc.nsLister, pc.log) - policyContext, err := backgroundcommon.NewBackgroundContext(pc.log, pc.client, ur, policy, triggerResource, pc.configuration, pc.jp, namespaceLabels) + policyContext, err := backgroundcommon.NewBackgroundContext(pc.log, pc.client, ur.Spec.Context, policy, triggerResource, pc.configuration, pc.jp, namespaceLabels) if err != nil { return false, fmt.Errorf("failed to build policy context for rule %s: %w", ruleName, err) } diff --git a/pkg/policy/updaterequest.go b/pkg/policy/updaterequest.go index fade189e0e..88a15c0b7e 100644 --- a/pkg/policy/updaterequest.go +++ b/pkg/policy/updaterequest.go @@ -7,25 +7,37 @@ import ( "github.com/kyverno/kyverno/pkg/config" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/labels" ) -func newUR(policy kyvernov1.PolicyInterface, trigger kyvernov1.ResourceSpec, ruleName string, ruleType kyvernov2.RequestType, deleteDownstream bool) *kyvernov2.UpdateRequest { - var policyNameNamespaceKey string - - if policy.IsNamespaced() { - policyNameNamespaceKey = policy.GetNamespace() + "/" + policy.GetName() - } else { - policyNameNamespaceKey = policy.GetName() +func newMutateUR(policy kyvernov1.PolicyInterface, trigger kyvernov1.ResourceSpec, ruleName string) *kyvernov2.UpdateRequest { + ur := newUrMeta() + ur.Labels = common.MutateLabelsSet(policyKey(policy), trigger) + ur.Spec = kyvernov2.UpdateRequestSpec{ + Type: kyvernov2.Mutate, + Policy: policyKey(policy), + Rule: ruleName, + Resource: kyvernov1.ResourceSpec{ + Kind: trigger.GetKind(), + Namespace: trigger.GetNamespace(), + Name: trigger.GetName(), + APIVersion: trigger.GetAPIVersion(), + UID: trigger.GetUID(), + }, } + return ur +} - var label labels.Set - if ruleType == kyvernov2.Mutate { - label = common.MutateLabelsSet(policyNameNamespaceKey, trigger) - } else { - label = common.GenerateLabelsSet(policyNameNamespaceKey, trigger) +func newGenerateUR(policy kyvernov1.PolicyInterface) *kyvernov2.UpdateRequest { + ur := newUrMeta() + ur.Labels = common.GenerateLabelsSet(policyKey(policy)) + ur.Spec = kyvernov2.UpdateRequestSpec{ + Type: kyvernov2.Generate, + Policy: policyKey(policy), } + return ur +} +func newUrMeta() *kyvernov2.UpdateRequest { return &kyvernov2.UpdateRequest{ TypeMeta: metav1.TypeMeta{ APIVersion: kyvernov2.SchemeGroupVersion.String(), @@ -34,35 +46,32 @@ func newUR(policy kyvernov1.PolicyInterface, trigger kyvernov1.ResourceSpec, rul ObjectMeta: metav1.ObjectMeta{ GenerateName: "ur-", Namespace: config.KyvernoNamespace(), - Labels: label, - }, - Spec: kyvernov2.UpdateRequestSpec{ - Type: ruleType, - Policy: policyNameNamespaceKey, - Rule: ruleName, - Resource: kyvernov1.ResourceSpec{ - Kind: trigger.GetKind(), - Namespace: trigger.GetNamespace(), - Name: trigger.GetName(), - APIVersion: trigger.GetAPIVersion(), - UID: trigger.GetUID(), - }, - DeleteDownstream: deleteDownstream, }, } } -func newURStatus(downstream unstructured.Unstructured) kyvernov2.UpdateRequestStatus { - return kyvernov2.UpdateRequestStatus{ - State: kyvernov2.Pending, - GeneratedResources: []kyvernov1.ResourceSpec{ - { - APIVersion: downstream.GetAPIVersion(), - Kind: downstream.GetKind(), - Namespace: downstream.GetNamespace(), - Name: downstream.GetName(), - UID: downstream.GetUID(), - }, +func addGeneratedResources(ur *kyvernov2.UpdateRequest, downstream unstructured.Unstructured) { + ur.Status.GeneratedResources = append(ur.Status.GeneratedResources, + kyvernov1.ResourceSpec{ + APIVersion: downstream.GetAPIVersion(), + Kind: downstream.GetKind(), + Namespace: downstream.GetNamespace(), + Name: downstream.GetName(), + UID: downstream.GetUID(), }, - } + ) +} + +func addRuleContext(ur *kyvernov2.UpdateRequest, ruleName string, trigger kyvernov1.ResourceSpec, deleteDownstream bool) { + ur.Spec.RuleContext = append(ur.Spec.RuleContext, kyvernov2.RuleContext{ + Rule: ruleName, + Trigger: kyvernov1.ResourceSpec{ + Kind: trigger.GetKind(), + Namespace: trigger.GetNamespace(), + Name: trigger.GetName(), + APIVersion: trigger.GetAPIVersion(), + UID: trigger.GetUID(), + }, + DeleteDownstream: deleteDownstream, + }) } diff --git a/pkg/policy/utils.go b/pkg/policy/utils.go index be3f6a60ac..bf8b6e08aa 100644 --- a/pkg/policy/utils.go +++ b/pkg/policy/utils.go @@ -37,3 +37,14 @@ func castPolicy(p interface{}) kyvernov1.PolicyInterface { } return policy } + +func policyKey(policy kyvernov1.PolicyInterface) string { + var policyNameNamespaceKey string + + if policy.IsNamespaced() { + policyNameNamespaceKey = policy.GetNamespace() + "/" + policy.GetName() + } else { + policyNameNamespaceKey = policy.GetName() + } + return policyNameNamespaceKey +} diff --git a/pkg/webhooks/resource/generation/handler.go b/pkg/webhooks/resource/generation/handler.go index 5f59e48f3f..6e52d32868 100644 --- a/pkg/webhooks/resource/generation/handler.go +++ b/pkg/webhooks/resource/generation/handler.go @@ -89,7 +89,7 @@ func (h *generationHandler) Handle( if h.backgroundServiceAccountName == policyContext.AdmissionInfo().AdmissionUserInfo.Username { return } - h.handleNonTrigger(ctx, policyContext, request) + h.handleNonTrigger(ctx, policyContext) } func getAppliedRules(policy kyvernov1.PolicyInterface, applied []engineapi.RuleResponse) []kyvernov1.Rule { @@ -137,13 +137,12 @@ func (h *generationHandler) handleTrigger( func (h *generationHandler) handleNonTrigger( ctx context.Context, policyContext *engine.PolicyContext, - request admissionv1.AdmissionRequest, ) { resource := policyContext.OldResource() labels := resource.GetLabels() if _, ok := labels[common.GenerateTypeCloneSourceLabel]; ok || labels[common.GeneratePolicyLabel] != "" { h.log.V(4).Info("handle non-trigger resource operation for generate") - if err := h.processRequest(ctx, policyContext, request); err != nil { + if err := h.processRequest(ctx, policyContext); err != nil { h.log.Error(err, "failed to create the UR on non-trigger admission request") } } @@ -171,16 +170,14 @@ func (h *generationHandler) applyGeneration( } rules := getAppliedRules(policy, appliedRules) - for _, rule := range rules { - h.log.V(4).Info("creating the UR to generate downstream on trigger's operation", "operation", request.Operation, "rule", rule.Name) - urSpec := buildURSpec(kyvernov2.Generate, pKey, rule.Name, triggerSpec, false) - urSpec.Context = buildURContext(request, policyContext) - if err := h.urGenerator.Apply(ctx, urSpec); err != nil { - h.log.Error(err, "failed to create the UR to create downstream on trigger's operation", "operation", request.Operation, "rule", rule.Name) - e := event.NewFailedEvent(err, pKey, rule.Name, event.GeneratePolicyController, - kyvernov1.ResourceSpec{Kind: policy.GetKind(), Namespace: policy.GetNamespace(), Name: policy.GetName()}) - h.eventGen.Add(e) - } + h.log.V(4).Info("creating the UR to generate downstream on trigger's operation", "operation", request.Operation, "policy", pKey) + urSpec := buildURSpecNew(kyvernov2.Generate, pKey, rules, triggerSpec, false) + urSpec.Context = buildURContext(request, policyContext) + if err := h.urGenerator.Apply(ctx, urSpec); err != nil { + h.log.Error(err, "failed to create the UR to create downstream on trigger's operation", "operation", request.Operation, "policy", pKey) + e := event.NewFailedEvent(err, pKey, "", event.GeneratePolicyController, + kyvernov1.ResourceSpec{Kind: policy.GetKind(), Namespace: policy.GetNamespace(), Name: policy.GetName()}) + h.eventGen.Add(e) } } @@ -199,7 +196,7 @@ func (h *generationHandler) syncTriggerAction( pKey := common.PolicyKey(policy.GetNamespace(), policy.GetName()) trigger := policyContext.OldResource() - urSpec := kyvernov1.ResourceSpec{ + triggerSpec := kyvernov1.ResourceSpec{ APIVersion: trigger.GetAPIVersion(), Kind: trigger.GetKind(), Namespace: trigger.GetNamespace(), @@ -208,38 +205,39 @@ func (h *generationHandler) syncTriggerAction( } rules := getAppliedRules(policy, failedRules) + urSpec := kyvernov2.UpdateRequestSpec{ + Type: kyvernov2.Generate, + Policy: pKey, + RuleContext: make([]kyvernov2.RuleContext, 0), + Context: buildURContext(request, policyContext), + } for _, rule := range rules { // fire generation on trigger deletion if (request.Operation == admissionv1.Delete) && webhookutils.MatchDeleteOperation(rule) { h.log.V(4).Info("creating the UR to generate downstream on trigger's deletion", "operation", request.Operation, "rule", rule.Name) - ur := buildURSpec(kyvernov2.Generate, pKey, rule.Name, urSpec, false) - ur.Context = buildURContext(request, policyContext) - if err := h.urGenerator.Apply(ctx, ur); err != nil { - h.log.Error(err, "failed to create the UR to generate downstream on trigger's deletion", "operation", request.Operation, "rule", rule.Name) - e := event.NewFailedEvent(err, pKey, rule.Name, event.GeneratePolicyController, - kyvernov1.ResourceSpec{Kind: policy.GetKind(), Namespace: policy.GetNamespace(), Name: policy.GetName()}) - h.eventGen.Add(e) - } + ruleCtx := buildRuleContext(rule, triggerSpec, false) + urSpec.RuleContext = append(urSpec.RuleContext, ruleCtx) continue } // delete downstream on trigger deletion if rule.Generation.Synchronize { h.log.V(4).Info("creating the UR to delete downstream on trigger's event", "operation", request.Operation, "rule", rule.Name) - ur := buildURSpec(kyvernov2.Generate, pKey, rule.Name, urSpec, true) - ur.Context = buildURContext(request, policyContext) - if err := h.urGenerator.Apply(ctx, ur); err != nil { - h.log.Error(err, "failed to create the UR to delete downstream on trigger's event", "operation", request.Operation, "rule", rule.Name) - e := event.NewFailedEvent(err, pKey, rule.Name, event.GeneratePolicyController, - kyvernov1.ResourceSpec{Kind: policy.GetKind(), Namespace: policy.GetNamespace(), Name: policy.GetName()}) - h.eventGen.Add(e) - } + ruleCtx := buildRuleContext(rule, triggerSpec, true) + urSpec.RuleContext = append(urSpec.RuleContext, ruleCtx) } } + + if err := h.urGenerator.Apply(ctx, urSpec); err != nil { + h.log.Error(err, "failed to create the UR on trigger's event", "operation", request.Operation, "policy", pKey) + e := event.NewFailedEvent(err, pKey, "", event.GeneratePolicyController, + kyvernov1.ResourceSpec{Kind: policy.GetKind(), Namespace: policy.GetNamespace(), Name: policy.GetName()}) + h.eventGen.Add(e) + } } // processRequest determine if it needs to re-apply the generate rule to the source or the target changes -func (h *generationHandler) processRequest(ctx context.Context, policyContext *engine.PolicyContext, request admissionv1.AdmissionRequest) (err error) { +func (h *generationHandler) processRequest(ctx context.Context, policyContext *engine.PolicyContext) (err error) { var policy kyvernov1.PolicyInterface var labelsList []map[string]string var deleteDownstream bool @@ -310,6 +308,12 @@ func (h *generationHandler) processRequest(ctx context.Context, policyContext *e } pKey := common.PolicyKey(pNamespace, pName) + urSpec := kyvernov2.UpdateRequestSpec{ + Type: kyvernov2.Generate, + Policy: pKey, + RuleContext: make([]kyvernov2.RuleContext, 0), + } + for _, rule := range policy.GetSpec().Rules { if rule.Name == pRuleName && rule.Generation.Synchronize { gvk, subresource := policyContext.ResourceKind() @@ -327,15 +331,16 @@ func (h *generationHandler) processRequest(ctx context.Context, policyContext *e continue } - ur := buildURSpec(kyvernov2.Generate, pKey, rule.Name, generateutils.TriggerFromLabels(labels), deleteDownstream) - if err := h.urGenerator.Apply(ctx, ur); err != nil { - e := event.NewBackgroundFailedEvent(err, policy, pRuleName, event.GeneratePolicyController, - kyvernov1.ResourceSpec{Kind: new.GetKind(), Namespace: new.GetNamespace(), Name: new.GetName()}) - h.eventGen.Add(e...) - return err - } + ruleCtx := buildRuleContext(rule, generateutils.TriggerFromLabels(labels), deleteDownstream) + urSpec.RuleContext = append(urSpec.RuleContext, ruleCtx) } } + if err := h.urGenerator.Apply(ctx, urSpec); err != nil { + e := event.NewBackgroundFailedEvent(err, policy, "", event.GeneratePolicyController, + kyvernov1.ResourceSpec{Kind: new.GetKind(), Namespace: new.GetNamespace(), Name: new.GetName()}) + h.eventGen.Add(e...) + return err + } } return nil } diff --git a/pkg/webhooks/resource/generation/utils.go b/pkg/webhooks/resource/generation/utils.go index 83e9319140..d217f854c8 100644 --- a/pkg/webhooks/resource/generation/utils.go +++ b/pkg/webhooks/resource/generation/utils.go @@ -7,12 +7,23 @@ import ( admissionv1 "k8s.io/api/admission/v1" ) -func buildURSpec(requestType kyvernov2.RequestType, policyKey, ruleName string, resource kyvernov1.ResourceSpec, deleteDownstream bool) kyvernov2.UpdateRequestSpec { +func buildURSpecNew(requestType kyvernov2.RequestType, policyKey string, rules []kyvernov1.Rule, trigger kyvernov1.ResourceSpec, deleteDownstream bool) kyvernov2.UpdateRequestSpec { + ruleCtx := make([]kyvernov2.RuleContext, 0) + for _, rule := range rules { + ctx := buildRuleContext(rule, trigger, deleteDownstream) + ruleCtx = append(ruleCtx, ctx) + } return kyvernov2.UpdateRequestSpec{ - Type: requestType, - Policy: policyKey, - Rule: ruleName, - Resource: resource, + Type: requestType, + Policy: policyKey, + RuleContext: ruleCtx, + } +} + +func buildRuleContext(rule kyvernov1.Rule, trigger kyvernov1.ResourceSpec, deleteDownstream bool) kyvernov2.RuleContext { + return kyvernov2.RuleContext{ + Rule: rule.Name, + Trigger: trigger, DeleteDownstream: deleteDownstream, } } diff --git a/pkg/webhooks/updaterequest/generator.go b/pkg/webhooks/updaterequest/generator.go index 1cd8fd9828..c6cec2de05 100644 --- a/pkg/webhooks/updaterequest/generator.go +++ b/pkg/webhooks/updaterequest/generator.go @@ -70,7 +70,7 @@ func (g *generator) tryApplyResource(ctx context.Context, urSpec kyvernov2.Updat if urSpec.GetRequestType() == kyvernov2.Mutate { queryLabels = common.MutateLabelsSet(urSpec.Policy, urSpec.GetResource()) } else if urSpec.GetRequestType() == kyvernov2.Generate { - queryLabels = common.GenerateLabelsSet(urSpec.Policy, urSpec.GetResource()) + queryLabels = common.GenerateLabelsSet(urSpec.Policy) } l.V(4).Info("creating new UpdateRequest") diff --git a/scripts/config/standard/kyverno.yaml b/scripts/config/standard/kyverno.yaml index 8cb4dcc7af..b85c35e10e 100644 --- a/scripts/config/standard/kyverno.yaml +++ b/scripts/config/standard/kyverno.yaml @@ -5,6 +5,8 @@ features: eventTypes: [] backgroundController: + extraArgs: + v: 4 rbac: clusterRole: extraResources: diff --git a/test/conformance/chainsaw/generate/clusterpolicy/cornercases/clone-list-sync-same-trigger-source-delete-source/chainsaw-test.yaml b/test/conformance/chainsaw/generate/clusterpolicy/cornercases/clone-list-sync-same-trigger-source-delete-source/chainsaw-test.yaml index 7869202284..df15d93cba 100755 --- a/test/conformance/chainsaw/generate/clusterpolicy/cornercases/clone-list-sync-same-trigger-source-delete-source/chainsaw-test.yaml +++ b/test/conformance/chainsaw/generate/clusterpolicy/cornercases/clone-list-sync-same-trigger-source-delete-source/chainsaw-test.yaml @@ -19,9 +19,15 @@ spec: try: - apply: file: trigger.yaml + - name: step-03 + try: + - sleep: + duration: 2s + - name: step-04 + try: - assert: file: target.yaml - - name: step-03 + - name: step-05 try: - delete: ref: @@ -29,11 +35,11 @@ spec: kind: Secret name: mysecret namespace: clone-list-sync-same-trigger-source-trigger-ns - - name: step-04 + - name: step-06 try: - sleep: duration: 3s - - name: step-05 + - name: step-07 try: - error: file: target.yaml diff --git a/test/conformance/chainsaw/generate/clusterpolicy/standard/existing/different-generate-existing-values-reorder/README.md b/test/conformance/chainsaw/generate/clusterpolicy/standard/existing/different-generate-existing-values-reorder/README.md new file mode 100644 index 0000000000..f183346bb5 --- /dev/null +++ b/test/conformance/chainsaw/generate/clusterpolicy/standard/existing/different-generate-existing-values-reorder/README.md @@ -0,0 +1,17 @@ +## Description + +This test ensures that a generate policy works as expected in case the rules have a different value for the `generateExisting` field. + +## Expected Behavior + +1. Create two Namespaces named `red-ns` and `green-ns`. + +2. Create a policy with two generate rules: + - The first rule named `generate-network-policy` matches Namespaces sets the `generateExisting` to `true`. + - The second rule named `generate-config-map` matches Namespaces sets the `generateExisting` to `false`. + +3. It is expected that a NetworkPolicy will be generated for each Namespace whereas ConfigMaps will not be generated. + +## Reference Issue(s) + +N/A diff --git a/test/conformance/chainsaw/generate/clusterpolicy/standard/existing/different-generate-existing-values-reorder/chainsaw-test.yaml b/test/conformance/chainsaw/generate/clusterpolicy/standard/existing/different-generate-existing-values-reorder/chainsaw-test.yaml new file mode 100755 index 0000000000..231349992e --- /dev/null +++ b/test/conformance/chainsaw/generate/clusterpolicy/standard/existing/different-generate-existing-values-reorder/chainsaw-test.yaml @@ -0,0 +1,27 @@ +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + creationTimestamp: null + name: different-generate-existing-values +spec: + steps: + - name: step-01 + try: + - apply: + file: existing-resources.yaml + - name: step-02 + try: + - apply: + file: policy.yaml + - assert: + file: policy-ready.yaml + - name: step-03 + try: + - sleep: + duration: 3s + - name: step-04 + try: + - assert: + file: generated-resources.yaml + - error: + file: fail-generated-resources.yaml diff --git a/test/conformance/chainsaw/generate/clusterpolicy/standard/existing/different-generate-existing-values-reorder/existing-resources.yaml b/test/conformance/chainsaw/generate/clusterpolicy/standard/existing/different-generate-existing-values-reorder/existing-resources.yaml new file mode 100644 index 0000000000..ab3740d2bd --- /dev/null +++ b/test/conformance/chainsaw/generate/clusterpolicy/standard/existing/different-generate-existing-values-reorder/existing-resources.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: red-ns + labels: + color: red +--- +apiVersion: v1 +kind: Namespace +metadata: + name: green-ns + labels: + color: green diff --git a/test/conformance/chainsaw/generate/clusterpolicy/standard/existing/different-generate-existing-values-reorder/fail-generated-resources.yaml b/test/conformance/chainsaw/generate/clusterpolicy/standard/existing/different-generate-existing-values-reorder/fail-generated-resources.yaml new file mode 100644 index 0000000000..fc1fbc5222 --- /dev/null +++ b/test/conformance/chainsaw/generate/clusterpolicy/standard/existing/different-generate-existing-values-reorder/fail-generated-resources.yaml @@ -0,0 +1,34 @@ +apiVersion: v1 +data: + KAFKA_ADDRESS: 192.168.10.13:9092,192.168.10.14:9092,192.168.10.15:9092 + ZK_ADDRESS: 192.168.10.10:2181,192.168.10.11:2181,192.168.10.12:2181 +kind: ConfigMap +metadata: + labels: + somekey: somevalue + name: zk-kafka-address + namespace: red-ns +--- +apiVersion: v1 +data: + KAFKA_ADDRESS: 192.168.10.13:9092,192.168.10.14:9092,192.168.10.15:9092 + ZK_ADDRESS: 192.168.10.10:2181,192.168.10.11:2181,192.168.10.12:2181 +kind: ConfigMap +metadata: + labels: + somekey: somevalue + name: zk-kafka-address + namespace: green-ns +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + labels: + created-by: kyverno + name: default-deny + namespace: red-ns +spec: + podSelector: {} + policyTypes: + - Ingress + - Egress diff --git a/test/conformance/chainsaw/generate/clusterpolicy/standard/existing/different-generate-existing-values-reorder/generated-resources.yaml b/test/conformance/chainsaw/generate/clusterpolicy/standard/existing/different-generate-existing-values-reorder/generated-resources.yaml new file mode 100644 index 0000000000..31a31fa1b6 --- /dev/null +++ b/test/conformance/chainsaw/generate/clusterpolicy/standard/existing/different-generate-existing-values-reorder/generated-resources.yaml @@ -0,0 +1,12 @@ +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + labels: + created-by: kyverno + name: default-deny + namespace: green-ns +spec: + podSelector: {} + policyTypes: + - Ingress + - Egress diff --git a/test/conformance/chainsaw/generate/clusterpolicy/standard/existing/different-generate-existing-values-reorder/policy-ready.yaml b/test/conformance/chainsaw/generate/clusterpolicy/standard/existing/different-generate-existing-values-reorder/policy-ready.yaml new file mode 100644 index 0000000000..73a7bc1dfb --- /dev/null +++ b/test/conformance/chainsaw/generate/clusterpolicy/standard/existing/different-generate-existing-values-reorder/policy-ready.yaml @@ -0,0 +1,9 @@ +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: different-generate-existing-values-reorder +status: + conditions: + - reason: Succeeded + status: "True" + type: Ready \ No newline at end of file diff --git a/test/conformance/chainsaw/generate/clusterpolicy/standard/existing/different-generate-existing-values-reorder/policy.yaml b/test/conformance/chainsaw/generate/clusterpolicy/standard/existing/different-generate-existing-values-reorder/policy.yaml new file mode 100644 index 0000000000..ac3ca4cad3 --- /dev/null +++ b/test/conformance/chainsaw/generate/clusterpolicy/standard/existing/different-generate-existing-values-reorder/policy.yaml @@ -0,0 +1,53 @@ +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: different-generate-existing-values-reorder +spec: + rules: + - name: generate-config-map + match: + any: + - resources: + kinds: + - Namespace + names: + - red-ns + generate: + generateExisting: false + synchronize: true + apiVersion: v1 + kind: ConfigMap + name: zk-kafka-address + namespace: "{{request.object.metadata.name}}" + data: + kind: ConfigMap + metadata: + labels: + somekey: somevalue + data: + ZK_ADDRESS: "192.168.10.10:2181,192.168.10.11:2181,192.168.10.12:2181" + KAFKA_ADDRESS: "192.168.10.13:9092,192.168.10.14:9092,192.168.10.15:9092" + - name: generate-network-policy + match: + any: + - resources: + kinds: + - Namespace + names: + - green-ns + generate: + generateExisting: true + kind: NetworkPolicy + apiVersion: networking.k8s.io/v1 + name: default-deny + namespace: "{{request.object.metadata.name}}" + synchronize: true + data: + metadata: + labels: + created-by: kyverno + spec: + podSelector: {} + policyTypes: + - Ingress + - Egress \ No newline at end of file diff --git a/test/conformance/chainsaw/generate/clusterpolicy/standard/existing/different-generate-existing-values/fail-generated-resources.yaml b/test/conformance/chainsaw/generate/clusterpolicy/standard/existing/different-generate-existing-values/fail-generated-resources.yaml index 96b86fb5dc..fc1fbc5222 100644 --- a/test/conformance/chainsaw/generate/clusterpolicy/standard/existing/different-generate-existing-values/fail-generated-resources.yaml +++ b/test/conformance/chainsaw/generate/clusterpolicy/standard/existing/different-generate-existing-values/fail-generated-resources.yaml @@ -19,3 +19,16 @@ metadata: somekey: somevalue name: zk-kafka-address namespace: green-ns +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + labels: + created-by: kyverno + name: default-deny + namespace: red-ns +spec: + podSelector: {} + policyTypes: + - Ingress + - Egress diff --git a/test/conformance/chainsaw/generate/clusterpolicy/standard/existing/different-generate-existing-values/generated-resources.yaml b/test/conformance/chainsaw/generate/clusterpolicy/standard/existing/different-generate-existing-values/generated-resources.yaml index f61700ca9f..31a31fa1b6 100644 --- a/test/conformance/chainsaw/generate/clusterpolicy/standard/existing/different-generate-existing-values/generated-resources.yaml +++ b/test/conformance/chainsaw/generate/clusterpolicy/standard/existing/different-generate-existing-values/generated-resources.yaml @@ -1,18 +1,5 @@ apiVersion: networking.k8s.io/v1 kind: NetworkPolicy -metadata: - labels: - created-by: kyverno - name: default-deny - namespace: red-ns -spec: - podSelector: {} - policyTypes: - - Ingress - - Egress ---- -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy metadata: labels: created-by: kyverno diff --git a/test/conformance/chainsaw/generate/clusterpolicy/standard/existing/different-generate-existing-values/policy.yaml b/test/conformance/chainsaw/generate/clusterpolicy/standard/existing/different-generate-existing-values/policy.yaml index 302c9f5712..08dd7e15fe 100644 --- a/test/conformance/chainsaw/generate/clusterpolicy/standard/existing/different-generate-existing-values/policy.yaml +++ b/test/conformance/chainsaw/generate/clusterpolicy/standard/existing/different-generate-existing-values/policy.yaml @@ -10,6 +10,8 @@ spec: - resources: kinds: - Namespace + names: + - green-ns generate: generateExisting: true kind: NetworkPolicy @@ -32,6 +34,8 @@ spec: - resources: kinds: - Namespace + names: + - red-ns generate: generateExisting: false synchronize: true