# Extend Kyverno's test command to cover generate policy This is the initial design doc of how we are planning to implement the `test` command for generate policy. ## List of contents - [What's a generate rule?](#-Generate-rule) - [How generate policy works?](#How-generate-policy-works) - [Test command for generate policy](#Test-command-for-generate-policy) - [Structure of `test.yaml`](#Structure-of-testyaml) - [Initial Prototype](#Initial-Prototype) ## <a name="intro">Generate rule</a> _A **generate rule** can be used to create additional resources when a new resource is created or when the source is updated. This is useful to create supporting resources, such as new RoleBindings or NetworkPolicies for a Namespace._ ## <a name="how-it-works">How generate policy works?</a> Let us first see how the generate policy works by the help of an example. Let's take [Add Quota](https://kyverno.io/policies/best-practices/add_ns_quota/) policy for instance: * Set-up and run kyverno in a local cluster since the `generate policy` can only be applied to a live cluster. * You can find instructions on how to run kyverno in development mode [here](https://github.com/kyverno/kyverno/wiki/Running-in-development-mode) * Suppose we have the policy definition in `addQuota.yaml` file. Let's apply it to the cluster: ``` kubectl apply -f addQuota.yaml ``` * Now, let's create a demo namespace ``` kubectl create ns demo ``` * Now, search for the resources listed in `addQuota.yaml` that are meant to be generated (here, `limitranges` and `resourcequotas`) ``` kubectl get limitranges --all-namespaces kubectl get resourcequotas --all-namespaces ``` * You'll get the resources being listed on your terminal. ## <a name="test-command">Test command for generate policy</a> _Now, coming to the test command for **Generate Policy**. We can go about it in two ways:_ 1. If the user doesn't provide the **desired resource**, then we can just display if kyverno can generate the resource or not 2. If the user provides the **resource manifests** then we can compare it with the kyverno created resource is a subset of the user provided resource(in terms of tags). If that pass, test success else failure. _The next part is how should we log the result?_ So if both the resources match, we can update the test results to `pass` or else `fail`. ## <a name="test-yaml">Structure of `test.yaml`</a> _To demonstrate this, let us take [Add Network Policy](https://kyverno.io/policies/best-practices/add_network_policy/) \& [Add Quota](https://kyverno.io/policies/best-practices/add_ns_quota/) policy for example._ 1. When the user doesn't provide the desired resource ```yaml name: add-networkpolicy policies: - add_network_policy.yaml resources: - resource.yaml results: - policy: add-networkpolicy rule: default-deny kind: Namespace resource: demo result: pass ``` ```yaml # What if the NetworkPolicy was only applicable to Pods name: add-networkpolicy policies: - add_network_policy.yaml resources: - resource.yaml results: - policy: add-networkpolicy rule: default-deny kind: Pod resource: demo # name of resource(Namespace, Pod, etc) namespace: testing # namespace where the target resource is being created result: pass ``` ```yaml name: add-ns-quota policies: - add_ns_quota.yaml resources: - resource.yaml results: - policy: add-ns-quota rule: generate-resourcequota kind: Namespace # source resource: demo # target result: pass - policy: add-ns-quota rule: generate-limitrange kind: Namespace resource: demo result: pass ``` 2. When the user provides the desired resource ```yaml name: add-networkpolicy policies: - add_network_policy.yaml resources: - resource.yaml results: - policy: add-networkpolicy generatedResource: generatedResource.yaml rule: default-deny kind: Namespace resource: demo result: pass ``` ```yaml name: add-ns-quota policies: - add_ns_quota.yaml resources: - resource.yaml results: - policy: add-ns-quota rule: generate-resourcequota generatedResource: generatedResource1.yaml kind: Namespace resource: demo result: pass - policy: add-ns-quota rule: generate-limitrange generatedResource: generatedResource2.yaml kind: Namespace resource: demo result: pass ``` ## <a name="initial-prototype">Initial Prototype</a> - We would have to add a new field i.e. `generatedResource` in the [TestResults](https://github.com/kyverno/kyverno/blob/50cb1859c39abd900315326f22e46e20942e8644/pkg/kyverno/test/test_command.go#L176) struct. - Then we'll extract the `test.yaml` from the path provided by the user in the form of bytes by executing the test command in cli. - Next we'll extract the `policy` by passing the above extracted result into [`applyPoliciesFromPath`](https://github.com/kyverno/kyverno/blob/e5e849acfebd0461ae147d066821fdf86c620fc7/pkg/kyverno/test/test_command.go#L575) in `pkg/kyverno/test/test_command.go`. - Inside `applyPoliciesFromPath` we call [`getFullPath`](https://github.com/kyverno/kyverno/blob/50cb1859c39abd900315326f22e46e20942e8644/pkg/kyverno/test/test_command.go#L562) to update the path of `generatedResource` in the fields. - Also, we've to call `GetPoliciesFromPaths` in `pkg/kyverno/common/common.go` to get the policies - Next, we are going to loop through all the resources and invoke [`ApplyPolicyOnResource`](https://github.com/kyverno/kyverno/blob/5c50191d8a8ab8e3863d06531679a124a4d81931/pkg/kyverno/common/common.go#L459) to apply a policy on the resource. Inside `ApplyPolicyOnResource`: - we'll make a policy context - call the `engine.Generate()` by passing the context in it and store the set of applicable rules in `engineResponses` for generating the resource - Next, we can create a standalone function to create the GR spec from the given policy and resource manifests and the `enigneResponse`. (for instance the [`transform()`](https://github.com/kyverno/kyverno/blob/17e671bf53c37ea7d77e1eea45263df8842e2c5d/pkg/webhooks/generation.go#L443) function) - Then we'll be creating an abstraction using [`processGR()`](https://github.com/kyverno/kyverno/blob/50cb1859c39abd900315326f22e46e20942e8644/pkg/generate/generate.go#L32) to process the GRs. - [**Update-1**: Since the `applyGeneratePolicy()` is a function with receiver type `Controller` and uses the client, we've to create an abstraction for it.] - [**Update-2**: We can leverage the `applyGeneratePolicy()` by using the [client-go/kubernetes/fake](https://pkg.go.dev/k8s.io/client-go/kubernetes/fake) package. So, no need of creating an abstraction.] - Finally, we get the result from the above. - If the user hasn't provided the resource manifests then we'll update the `TestResults` struct according to the result obtained. - If the user has provided the resource manifests, we can compare the `GeneratedResource` with the `engineResponse` and update the `TestResults` struct. > **Note**: No logs should be added to the default log level.