Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal for extending forEach to cover generate rules #52

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
182 changes: 182 additions & 0 deletions extend_foreach_to_cover_generate_rules.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
# Support `forEach` in Generate Rules

- **Authors**: [Mariam Fahmy](https://github.com/MariamFahmy98)
- **Created**: Nov 11th, 2023
- **Abstract**: Supporting `forEach` in Generate Rules

# Table of Contents
- [Overview](#overview)
- [Definitions](#definitions)
- [Motivation](#motivation)
- [Proposal](#proposal)
- [Implementation](#implementation)
- [Drawbacks](#drawbacks)
- [Alternatives](#alternatives)
- [Prior Art](#prior-art)
- [Unresolved Questions](#unresolved-questions)

# Overview
[overview]: #overview
Generate rules are in charge of creating new resources. The rule can either define the new resource using the `data` object or use an existing resource in the cluster with the `clone` object. Currently, only one resource can be generated.

This proposal is to extend the `forEach` to cover generate rules.

# Definitions
[definitions]: #definitions

1. Trigger: It refers to the resource responsible for triggering the generate rule as defined in a combination of `match` and `exclude` blocks.

2. Downstream: It refers to the generated resource(s).

3. Source: It refers to the clone source when generating new resources from existing ones in the cluster.

# Motivation
[motivation]: #motivation

There are some use cases where a single "trigger" resource should result in the creation of multiple downstream resources.

Examples:
1. Generating a list of new network policies based on a comma separated string in an annotation using a `forEach` and `Split()`.
2. Migrating from ingress to Kubernetes Gateway API requires generating at least two resources: `Gateway` and `HTTPRoute`.

# Proposal
In this proposal, Kyverno generate rule can be extended to allowing the generation of multiple resources using `forEach`.
This could be done by using the `list` attribute which is a JMESPath expression that defines the sub-elements it processes.

Examples:
1. Generate multiple network policies when a new namespace is created:
```yaml
spec:
rules:
- name: generate-network-policies
match:
any:
- resources:
kinds:
- Namespace
generate:
synchronize: true
foreach:
- list: request.object.metadata.labels.networkpolicies | split(@, ',')
resources:
- apiVersion: v1
kind: NetworkPolicy
name: '{{ element }}'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be good to clarify the built-in variables for generate foreach type of rule.

namespace: "{{request.object.metadata.name}}"
data:
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
```

2. Generate ConfigMap and Secret for each container:
```yaml
spec:
rules:
- name: generate-configmaps-and-secrets
match:
any:
- resources:
kinds:
- Pod
generate:
synchronize: true
foreach:
- list: request.object.spec.containers
resources:
- apiVersion: v1
kind: ConfigMap
name: cm
namespace: "{{request.object.metadata.namespace}}"
data:
foo: bar
- apiVersion: v1
kind: Secret
name: secret
namespace: "{{request.object.metadata.namespace}}"
data:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we support clone and cloneList in foreach?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No. Since cloneList is used to clone multiple resources at once, then there is no need for using foreach. WDYT?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about clone?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think foreach with clone will have the same functionality as cloneList, so why we introduce it in clone if there is no extra functionality?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using foreach with clone will allow us to clone the existing resource multiple times.
Do we have a real use case for that?

For example, we can clone a secret depending on the number of containers. For each container, the same secret will be cloned.

Copy link

@pinkfloydx33 pinkfloydx33 Apr 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this handle the case of cloning a resource into multiple namespaces?

For example matching on Secret with an annotation of the form sync: a=b, using the API server to find all Namespace with label a: b and then foreach'ing that list to generate the Secrets.

This is a case we can't currently do with Kyverno (so far as I could tell). It's a task tools like KubeD or Reflector perform as their primary purpose. (Along with many others) I'm looking for a replacement for the former and was hoping I'd be able to write a ClusterPolicy for that case... but it seems I'd need foreach in a generate rule that could also clone first

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might consider using external-secrets kubernetes provider along with its PushSecret custom resource to replace many kubed use cases.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @pinkfloydx33 - this is an interesting use case.

I have crafted an example rule which looks up a list of namespaces by the label and clone the secret to multiple namespaces.

spec:
  rules:
  - name: clone-seret-to-multiple-namespace
    match:
      any:
      - resources:
          kinds:
          - Secret
          namespaces:
          - default
    context:
    - variable:
        name: selector
        jmesPath: request.object.metadata.labels.sync
        default: a=b
    - name: namespaces
      apiCall:
        urlPath: /api/v1/namespaces?labelSelector={{selector}}?limit=5
    generate:
      synchronize: false
      foreach:
        - list: "{{namespaces}}"
          cloneList:
          - source:
              namespace: default
              name: regcred
              kind: v1
              apiVersion: Secret
            target:
              namespace: "{{element}}"
              name: regcred

What do you think?

foo: bar
```

3. Generate Gateway and HTTPRoute from Ingress:
```yaml
spec:
rules:
- name: generate-gatewayapi
match:
any:
- resources:
kinds:
- Ingress
generate:
synchronize: false
foreach:
- list: request.object.spec.rules
resources:
- apiVersion: gateway.networking.k8s.io/v1beta1
kind: HTTPRoute
# the name must be unique
name: httproute
namespace: "{{request.object.metadata.namespace}}"
data:
spec:
hostnames:
- '{{ element.host }}'
- list: request.object.spec.ingressClassName
resources:
- apiVersion: gateway.networking.k8s.io/v1beta1
kind: Gateway
name: gateway
data:
spec:
gatewayClassName: '{{ element }}'
Copy link
Member

@realshuting realshuting Nov 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently the mapping for a generate rule is - one rule can support M foreach, one foreach can generate N resources. Therefore one generate rule manages M*N resources, I wonder how the complexity of the management work can be, for example, to sync changes from the rule to downstream resources.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For simplicity, we can limit the rule to have 1 foreach and it will only generate 1 resource. I amn't sure how we sync the changes given there is M*N managed resources rather than applying the rule again.

```

# Implementation

We will extend the generate rule as follows:
```go
type Generation struct {
ForEachGeneration []ForEachGeneration `json:"foreach,omitempty" yaml:"foreach,omitempty"`
}
```

```go
type ForEachGeneration struct {
List string `json:"list,omitempty" yaml:"list,omitempty"`

Resource []Resource `json:"resources,omitempty" yaml:"resources,omitempty"`
}
```

```go
type Resource struct {
ResourceSpec `json:",omitempty" yaml:",omitempty"`

RawData *apiextv1.JSON `json:"data,omitempty" yaml:"data,omitempty"`
}
```

- `foreach` will only support generating Kubernetes resources that are defined as a part of the rule.

- Nested `foreach` can be supported but there is no real use case for it.

- At least one of the following: `data`, `clone`, or `foreach` must be declared.

# Drawbacks

N/A

# Alternatives

N/A

# Prior Art

N/A

# Unresolved Questions

N/A