diff --git a/README.md b/README.md index 93eabee..b1bb988 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,10 @@ Configurable K8S webhook that can implement multiple validators and mutators using a simple yaml config file. -For example, this file configures it to validate that no `serviceaccount` is uses the `kube-system` namespace. This validator can be accessed on `:/check-namespace-sa`. +For example, this is the config to validate that no `serviceaccount` uses the `kube-system` namespace. This validator can be accessed on `:/check-namespace-sa`. ```yaml -apiVersion: generic-webhook/v1alpha1 +apiVersion: generic-webhook/v1beta1 kind: GenericWebhookConfig webhooks: - name: check-namespace-sa @@ -13,14 +13,7 @@ webhooks: actions: # Refuse the request if it's a ServiceAccount that # is placed on the "kube-system" namespace - - condition: - and: - - equal: - - getValue: .metadata.namespace - - const: kube-system - - equal: - - getValue: .kind - - const: ServiceAccount + - condition: .metadata.namespace == "kube-system" && .kind == "ServiceAccount" accept: false ``` @@ -47,7 +40,7 @@ metadata: [...] data: generic-webhook-config: | - apiVersion: generic-webhook/v1alpha1 + apiVersion: generic-webhook/v1beta1 kind: GenericWebhookConfig webhooks: - ... @@ -63,7 +56,7 @@ This file allows the user to configure several webhooks in a single app. In this The [examples](./examples/) directory contains a fair amount of examples to help the user better understand how they can leverage the `GenericWebhookConfig` to write their own Validating or Mutating webhooks. ```yaml -apiVersion: generic-webhook/v1alpha1 +apiVersion: generic-webhook/v1beta1 kind: GenericWebhookConfig webhooks: - # Configuration for the first webhook @@ -114,116 +107,23 @@ The value of the `--config` argument is the path of the `GenericWebhookConfig` c ### Defining a condition -When defining a condition, we can use any of the following operators. - -- [const](#const) -- [getValue](#getvalue) -- [and](#and) -- [or](#or) -- [not](#not) -- [equal](#equal) -- [sum](#sum) -- [forEach](#foreach) - -#### const - -Defines a constant value. It is normally used with the [equal](#equal) operator, in case we want to check that a field in the manifest equals to a value that we define (using the `const` operator). - -```yaml -const: -``` - -#### getValue - -Retrieves a value defined in the manifest. The `` is a `.` (dot) separated path that references a field in the manifest. For example, `.metadata.name`. If the `getValue` is used nested within the [forEach](#foreach) operator, by default it will take as a base context the item that the [forEach](#foreach) is iterating. If you want to use the root of the manifest as the context, then you must add a `$` at the beginning of the path. For example, `$.metadata.name` will always resolve to the metadata defined at the root level independently if the `getValue` is nested in a [forEach](#foreach) or not. - -```yaml -# Refers to the latest context -getValue: .metadata.name - -# Refers always to the root context -getValue: $.metadata.name -``` - -#### and - -It performs an `and` operation on a list of elements. The list of elements can be explicitly defined (an actual yaml list) or implicitly defined. This last case is exemplified when the `and` consumes the result generated by the [forEach](#foreach) operator. +The conditions can be defined using structured operators and/or a simple pseudolanguage. For example, the following condition combines both a structured operator (an `and`) and a couple of lines of this pseudolanguage. ```yaml and: - - - - - - ... - -and: - forEach: - ... -``` - -#### or - -It performs an `or` operation on a list of elements. The list of elements can be explicitly defined (an actual yaml list) or implicitly defined. This last case is exemplified when the `or` consumes the result generated by the [forEach](#foreach) operator. - -```yaml -or: - - - - - - ... - -or: - forEach: - ... -``` - -#### not - -It negates the value of its argument. - -```yaml -not: - - -not: - and: - - ... + - .kind == "Pod" + - .metadata.labels.latencyCritical == true && .metadata.labels.app == "backend" ``` -#### equal - -Compares two elements and returns true if they are equal. - -```yaml -equal: - - const: default - - getValue: .metadata.namespace -``` - -#### sum - -Sums the values of a list of elements. The list of elements can be explicitly defined (an actual yaml list) or implicitly defined. This last case is exemplified when the `sum` consumes the result generated by the [forEach](#foreach) operator. +We can also iterate over lists and nested lists. In the following example, we check that a pod has at least one container called "main". ```yaml -sum: - - const: 1 - - const: 4 - -sum: - forEach: - ... +any: .spec.containers.* -> .name == "main" ``` -#### forEach - -It's like a `map` operation. If executes the operation `op` for each element defined in `elements`. It returns the transformed list of elements. The [getValue](#getvalue) operator that lives within a `forEach` receives as context the current element that the `forEach` is iterating. In this example, `.name` resolves to the name of a container. +The `*` is used to iterate over a list, in that case the list of containers. The `->` operator is like a map. So, assuming the pod has two containers, one named "main" and the other named "foo", the `.spec.containers.* -> .name == "main"` returns `[true, false]`. -```yaml -forEach: - elements: {getValue: .spec.containers} - op: - equal: - - const: my-side-car - - getValue: .name -``` +You can check [operators-reference](./docs/operators-reference.md) to see all the available structured operators. ### Defining a patch diff --git a/docs/operators-reference.md b/docs/operators-reference.md new file mode 100644 index 0000000..20abdc6 --- /dev/null +++ b/docs/operators-reference.md @@ -0,0 +1,110 @@ +When defining a condition, we can use any of the following operators. + +- [const](#const) +- [getValue](#getvalue) +- [and](#and) +- [or](#or) +- [not](#not) +- [equal](#equal) +- [sum](#sum) +- [forEach](#foreach) + +#### const + +Defines a constant value. It is normally used with the [equal](#equal) operator, in case we want to check that a field in the manifest equals to a value that we define (using the `const` operator). + +```yaml +const: +``` + +#### getValue + +Retrieves a value defined in the manifest. The `` is a `.` (dot) separated path that references a field in the manifest. For example, `.metadata.name`. If the `getValue` is used nested within the [forEach](#foreach) operator, by default it will take as a base context the item that the [forEach](#foreach) is iterating. If you want to use the root of the manifest as the context, then you must add a `$` at the beginning of the path. For example, `$.metadata.name` will always resolve to the metadata defined at the root level independently if the `getValue` is nested in a [forEach](#foreach) or not. + +```yaml +# Refers to the latest context +getValue: .metadata.name + +# Refers always to the root context +getValue: $.metadata.name +``` + +#### and + +It performs an `and` operation on a list of elements. The list of elements can be explicitly defined (an actual yaml list) or implicitly defined. This last case is exemplified when the `and` consumes the result generated by the [forEach](#foreach) operator. + +```yaml +and: + - + - + - ... + +and: + forEach: + ... +``` + +#### or + +It performs an `or` operation on a list of elements. The list of elements can be explicitly defined (an actual yaml list) or implicitly defined. This last case is exemplified when the `or` consumes the result generated by the [forEach](#foreach) operator. + +```yaml +or: + - + - + - ... + +or: + forEach: + ... +``` + +#### not + +It negates the value of its argument. + +```yaml +not: + + +not: + and: + - ... +``` + +#### equal + +Compares two elements and returns true if they are equal. + +```yaml +equal: + - const: default + - getValue: .metadata.namespace +``` + +#### sum + +Sums the values of a list of elements. The list of elements can be explicitly defined (an actual yaml list) or implicitly defined. This last case is exemplified when the `sum` consumes the result generated by the [forEach](#foreach) operator. + +```yaml +sum: + - const: 1 + - const: 4 + +sum: + forEach: + ... +``` + +#### forEach + +It's like a `map` operation. If executes the operation `op` for each element defined in `elements`. It returns the transformed list of elements. The [getValue](#getvalue) operator that lives within a `forEach` receives as context the current element that the `forEach` is iterating. In this example, `.name` resolves to the name of a container. + +```yaml +forEach: + elements: {getValue: .spec.containers} + op: + equal: + - const: my-side-car + - getValue: .name +``` diff --git a/examples/check-namespace-sa.yaml b/examples/check-namespace-sa.yaml index f590a4d..3d1273c 100644 --- a/examples/check-namespace-sa.yaml +++ b/examples/check-namespace-sa.yaml @@ -1,4 +1,4 @@ -apiVersion: generic-webhook/v1alpha1 +apiVersion: generic-webhook/v1beta1 kind: GenericWebhookConfig webhooks: - name: check-namespace-sa @@ -6,12 +6,5 @@ webhooks: actions: # Refuse the request if it's a ServiceAccount that # is placed on the "kube-system" namespace - - condition: - and: - - equal: - - getValue: .metadata.namespace - - const: kube-system - - equal: - - getValue: .kind - - const: ServiceAccount + - condition: .metadata.namespace == "kube-system" && .kind == "ServiceAccount" accept: false diff --git a/examples/inject-node-affinity.yaml b/examples/inject-node-affinity.yaml index 5d9c34a..9be7ee2 100644 --- a/examples/inject-node-affinity.yaml +++ b/examples/inject-node-affinity.yaml @@ -1,50 +1,20 @@ -apiVersion: generic-webhook/v1alpha1 +apiVersion: generic-webhook/v1beta1 kind: GenericWebhookConfig webhooks: - name: patch-node-affinity path: /patch-node-affinity actions: # If the pod doesn't have any node affinity that involves the `myorg.io/instance-cost` - # label, then we inject to it a preferred node affinity for the nodes that have + # label, then we inject a preferred node affinity for the nodes that have # the value `cheap` for the `myorg.io/instance-cost` label - condition: and: # We are analysing a Pod - - equal: - - getValue: .kind - - const: Pod - + - .kind == "Pod" # {key: myorg.io/instance-cost} doesn't appear in preferredDuringSchedulingIgnoredDuringExecution - - and: - forEach: - elements: - getValue: .spec.affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution - op: - and: - forEach: - elements: - getValue: .preference.matchExpressions - op: - not: - equal: - - getValue: .key - - const: myorg.io/instance-cost - + - all: .spec.affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution.*.preference.matchExpressions.* -> .key != "myorg.io/instance-cost" # {key: myorg.io/instance-cost} doesn't appear in requiredDuringSchedulingIgnoredDuringExecution - - and: - forEach: - elements: - getValue: .spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms - op: - and: - forEach: - elements: - getValue: .matchExpressions - op: - not: - equal: - - getValue: .key - - const: myorg.io/instance-cost + - all: .spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms.*.matchExpressions.* -> .key != "myorg.io/instance-cost" patch: # Inject a preferred node affinity for the nodes that have the label