Skip to content

Commit ed97cc7

Browse files
Update docs to explain how the new pseudolanguage works
The v1beta1 schema version supports defining conditions with a pseudolanguage that makes them more compact with respect to the old structured operators. This commit updates the current docs to reflect this change.
1 parent c43633e commit ed97cc7

File tree

4 files changed

+129
-156
lines changed

4 files changed

+129
-156
lines changed

README.md

+12-112
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,18 @@
22

33
Configurable K8S webhook that can implement multiple validators and mutators using a simple yaml config file.
44

5-
For example, this file configures it to validate that no `serviceaccount` is uses the `kube-system` namespace. This validator can be accessed on `<hostname>:<port>/check-namespace-sa`.
5+
For example, this is the config to validate that no `serviceaccount` uses the `kube-system` namespace. This validator can be accessed on `<hostname>:<port>/check-namespace-sa`.
66

77
```yaml
8-
apiVersion: generic-webhook/v1alpha1
8+
apiVersion: generic-webhook/v1beta1
99
kind: GenericWebhookConfig
1010
webhooks:
1111
- name: check-namespace-sa
1212
path: /check-namespace-sa
1313
actions:
1414
# Refuse the request if it's a ServiceAccount that
1515
# is placed on the "kube-system" namespace
16-
- condition:
17-
and:
18-
- equal:
19-
- getValue: .metadata.namespace
20-
- const: kube-system
21-
- equal:
22-
- getValue: .kind
23-
- const: ServiceAccount
16+
- condition: .metadata.namespace == "kube-system" && .kind == "ServiceAccount"
2417
accept: false
2518
```
2619
@@ -47,7 +40,7 @@ metadata:
4740
[...]
4841
data:
4942
generic-webhook-config: |
50-
apiVersion: generic-webhook/v1alpha1
43+
apiVersion: generic-webhook/v1beta1
5144
kind: GenericWebhookConfig
5245
webhooks:
5346
- ...
@@ -63,7 +56,7 @@ This file allows the user to configure several webhooks in a single app. In this
6356
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.
6457

6558
```yaml
66-
apiVersion: generic-webhook/v1alpha1
59+
apiVersion: generic-webhook/v1beta1
6760
kind: GenericWebhookConfig
6861
webhooks:
6962
- # Configuration for the first webhook
@@ -114,116 +107,23 @@ The value of the `--config` argument is the path of the `GenericWebhookConfig` c
114107

115108
### Defining a condition
116109

117-
When defining a condition, we can use any of the following operators.
118-
119-
- [const](#const)
120-
- [getValue](#getvalue)
121-
- [and](#and)
122-
- [or](#or)
123-
- [not](#not)
124-
- [equal](#equal)
125-
- [sum](#sum)
126-
- [forEach](#foreach)
127-
128-
#### const
129-
130-
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).
131-
132-
```yaml
133-
const: <constant value>
134-
```
135-
136-
#### getValue
137-
138-
Retrieves a value defined in the manifest. The `<path>` 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.
139-
140-
```yaml
141-
# Refers to the latest context
142-
getValue: .metadata.name
143-
144-
# Refers always to the root context
145-
getValue: $.metadata.name
146-
```
147-
148-
#### and
149-
150-
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.
110+
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.
151111

152112
```yaml
153113
and:
154-
- <elem1>
155-
- <elem2>
156-
- ...
157-
158-
and:
159-
forEach:
160-
...
161-
```
162-
163-
#### or
164-
165-
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.
166-
167-
```yaml
168-
or:
169-
- <elem1>
170-
- <elem2>
171-
- ...
172-
173-
or:
174-
forEach:
175-
...
176-
```
177-
178-
#### not
179-
180-
It negates the value of its argument.
181-
182-
```yaml
183-
not:
184-
<operator>
185-
186-
not:
187-
and:
188-
- ...
114+
- .kind == "Pod"
115+
- .metadata.labels.latencyCritical == true && .metadata.labels.app == "backend"
189116
```
190117

191-
#### equal
192-
193-
Compares two elements and returns true if they are equal.
194-
195-
```yaml
196-
equal:
197-
- const: default
198-
- getValue: .metadata.namespace
199-
```
200-
201-
#### sum
202-
203-
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.
118+
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".
204119

205120
```yaml
206-
sum:
207-
- const: 1
208-
- const: 4
209-
210-
sum:
211-
forEach:
212-
...
121+
any: .spec.containers.* -> .name == "main"
213122
```
214123

215-
#### forEach
216-
217-
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.
124+
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]`.
218125

219-
```yaml
220-
forEach:
221-
elements: {getValue: .spec.containers}
222-
op:
223-
equal:
224-
- const: my-side-car
225-
- getValue: .name
226-
```
126+
You can check [operators-reference](./docs/operators-reference.md) to see all the available structured operators.
227127

228128
### Defining a patch
229129

docs/operators-reference.md

+110
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
When defining a condition, we can use any of the following operators.
2+
3+
- [const](#const)
4+
- [getValue](#getvalue)
5+
- [and](#and)
6+
- [or](#or)
7+
- [not](#not)
8+
- [equal](#equal)
9+
- [sum](#sum)
10+
- [forEach](#foreach)
11+
12+
#### const
13+
14+
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).
15+
16+
```yaml
17+
const: <constant value>
18+
```
19+
20+
#### getValue
21+
22+
Retrieves a value defined in the manifest. The `<path>` 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.
23+
24+
```yaml
25+
# Refers to the latest context
26+
getValue: .metadata.name
27+
28+
# Refers always to the root context
29+
getValue: $.metadata.name
30+
```
31+
32+
#### and
33+
34+
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.
35+
36+
```yaml
37+
and:
38+
- <elem1>
39+
- <elem2>
40+
- ...
41+
42+
and:
43+
forEach:
44+
...
45+
```
46+
47+
#### or
48+
49+
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.
50+
51+
```yaml
52+
or:
53+
- <elem1>
54+
- <elem2>
55+
- ...
56+
57+
or:
58+
forEach:
59+
...
60+
```
61+
62+
#### not
63+
64+
It negates the value of its argument.
65+
66+
```yaml
67+
not:
68+
<operator>
69+
70+
not:
71+
and:
72+
- ...
73+
```
74+
75+
#### equal
76+
77+
Compares two elements and returns true if they are equal.
78+
79+
```yaml
80+
equal:
81+
- const: default
82+
- getValue: .metadata.namespace
83+
```
84+
85+
#### sum
86+
87+
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.
88+
89+
```yaml
90+
sum:
91+
- const: 1
92+
- const: 4
93+
94+
sum:
95+
forEach:
96+
...
97+
```
98+
99+
#### forEach
100+
101+
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.
102+
103+
```yaml
104+
forEach:
105+
elements: {getValue: .spec.containers}
106+
op:
107+
equal:
108+
- const: my-side-car
109+
- getValue: .name
110+
```

examples/check-namespace-sa.yaml

+2-9
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,10 @@
1-
apiVersion: generic-webhook/v1alpha1
1+
apiVersion: generic-webhook/v1beta1
22
kind: GenericWebhookConfig
33
webhooks:
44
- name: check-namespace-sa
55
path: /check-namespace-sa
66
actions:
77
# Refuse the request if it's a ServiceAccount that
88
# is placed on the "kube-system" namespace
9-
- condition:
10-
and:
11-
- equal:
12-
- getValue: .metadata.namespace
13-
- const: kube-system
14-
- equal:
15-
- getValue: .kind
16-
- const: ServiceAccount
9+
- condition: .metadata.namespace == "kube-system" && .kind == "ServiceAccount"
1710
accept: false

examples/inject-node-affinity.yaml

+5-35
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,20 @@
1-
apiVersion: generic-webhook/v1alpha1
1+
apiVersion: generic-webhook/v1beta1
22
kind: GenericWebhookConfig
33
webhooks:
44
- name: patch-node-affinity
55
path: /patch-node-affinity
66
actions:
77
# If the pod doesn't have any node affinity that involves the `myorg.io/instance-cost`
8-
# label, then we inject to it a preferred node affinity for the nodes that have
8+
# label, then we inject a preferred node affinity for the nodes that have
99
# the value `cheap` for the `myorg.io/instance-cost` label
1010
- condition:
1111
and:
1212
# We are analysing a Pod
13-
- equal:
14-
- getValue: .kind
15-
- const: Pod
16-
13+
- .kind == "Pod"
1714
# {key: myorg.io/instance-cost} doesn't appear in preferredDuringSchedulingIgnoredDuringExecution
18-
- and:
19-
forEach:
20-
elements:
21-
getValue: .spec.affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution
22-
op:
23-
and:
24-
forEach:
25-
elements:
26-
getValue: .preference.matchExpressions
27-
op:
28-
not:
29-
equal:
30-
- getValue: .key
31-
- const: myorg.io/instance-cost
32-
15+
- all: .spec.affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution.*.preference.matchExpressions.* -> .key != "myorg.io/instance-cost"
3316
# {key: myorg.io/instance-cost} doesn't appear in requiredDuringSchedulingIgnoredDuringExecution
34-
- and:
35-
forEach:
36-
elements:
37-
getValue: .spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms
38-
op:
39-
and:
40-
forEach:
41-
elements:
42-
getValue: .matchExpressions
43-
op:
44-
not:
45-
equal:
46-
- getValue: .key
47-
- const: myorg.io/instance-cost
17+
- all: .spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms.*.matchExpressions.* -> .key != "myorg.io/instance-cost"
4818

4919
patch:
5020
# Inject a preferred node affinity for the nodes that have the label

0 commit comments

Comments
 (0)