Skip to content

Commit 3807e90

Browse files
committed
feat(pluginpresets): resolve cross-preset ValueFrom.Ref (#1776)
Signed-off-by: Klaudiusz Fabryczny <klaudiusz.fabryczny@sap.com>
1 parent 505bb24 commit 3807e90

28 files changed

Lines changed: 2637 additions & 139 deletions

charts/greenhouse/ci/test-values.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ global:
1515
expressionEvaluationEnabled: false
1616
integrationEnabled: false
1717
ociMirroringEnabled: false
18+
# PluginPreset configuration for Greenhouse.
19+
pluginPreset:
20+
expressionEvaluationEnabled: true
21+
integrationEnabled: true
1822
linkerd_enabled: false
1923
region: greenhouse
2024
registry: ghcr.io/cloudoperators/greenhouse

charts/greenhouse/values.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ global:
2323
expressionEvaluationEnabled: false
2424
integrationEnabled: false
2525
ociMirroringEnabled: false
26+
# PluginPreset configuration for Greenhouse.
27+
pluginPreset:
28+
expressionEvaluationEnabled: true
29+
integrationEnabled: true
2630

2731
postgresqlng:
2832
enabled: true

charts/manager/ci/test-values.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ global:
1313
expressionEvaluationEnabled: false
1414
integrationEnabled: false
1515
ociMirroringEnabled: false
16+
pluginPreset:
17+
expressionEvaluationEnabled: true
18+
integrationEnabled: true
1619

1720
controllerManager:
1821
args:

charts/manager/templates/_helpers.tpl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,3 +117,11 @@ Define postgresql helpers
117117
{{- define "plugin.ociMirroringEnabled" -}}
118118
{{- printf "%t" (required "global.plugin.ociMirroringEnabled missing" .Values.global.plugin.ociMirroringEnabled) }}
119119
{{- end }}
120+
{{/* Render the pluginPreset expression evaluation flag */}}
121+
{{- define "pluginPreset.expressionEvaluationEnabled" -}}
122+
{{- printf "%t" (required "global.pluginPreset.expressionEvaluationEnabled missing" .Values.global.pluginPreset.expressionEvaluationEnabled) }}
123+
{{- end }}
124+
{{/* Render the pluginPreset integration enabled flag */}}
125+
{{- define "pluginPreset.integrationEnabled" -}}
126+
{{- printf "%t" (required "global.pluginPreset.integrationEnabled missing" .Values.global.pluginPreset.integrationEnabled) }}
127+
{{- end }}

charts/manager/templates/manager/feature-flag.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,19 @@ data:
2626
expressionEvaluationEnabled: false / true
2727
integrationEnabled: false / true
2828
ociMirroringEnabled: false / true
29+
# enable pluginPreset features
30+
# expressionEvaluationEnabled allows you to enable or disable CEL expression evaluation in PluginPreset
31+
# when enabled, expressions in PluginPreset.spec.plugin.optionValues are evaluated before creating the Plugin
32+
# integrationEnabled allows you to enable or disable ValueFrom.Ref resolution in PluginPreset
33+
# when enabled, valueFrom.ref references in PluginPreset are resolved before creating the Plugin
34+
pluginPreset: |
35+
expressionEvaluationEnabled: false / true
2936
dex: |
3037
storage: {{ include "dex.backend" $ }}
3138
plugin: |
3239
expressionEvaluationEnabled: {{ include "plugin.expressionEvaluationEnabled" $ }}
3340
integrationEnabled: {{ include "plugin.integrationEnabled" $ }}
3441
ociMirroringEnabled: {{ include "plugin.ociMirroringEnabled" $ }}
42+
pluginPreset: |
43+
expressionEvaluationEnabled: {{ include "pluginPreset.expressionEvaluationEnabled" $ }}
44+
integrationEnabled: {{ include "pluginPreset.integrationEnabled" $ }}

cmd/greenhouse/controllers.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ var knownControllers = map[string]func(controllerName string, mgr ctrl.Manager)
3535

3636
// Plugin controllers.
3737
"plugin": startPluginReconciler,
38-
"pluginPreset": (&plugincontrollers.PluginPresetReconciler{}).SetupWithManager,
38+
"pluginPreset": startPluginPresetReconciler,
3939

4040
"catalog": startCatalogReconciler,
4141
"pluginDefinition": startPluginDefinitionReconciler,
@@ -93,6 +93,13 @@ func startPluginReconciler(name string, mgr ctrl.Manager) error {
9393
}).SetupWithManager(name, mgr)
9494
}
9595

96+
func startPluginPresetReconciler(name string, mgr ctrl.Manager) error {
97+
return (&plugincontrollers.PluginPresetReconciler{
98+
ExpressionEvaluationEnabled: featureFlags.IsPresetExpressionEvaluationEnabled(),
99+
IntegrationEnabled: featureFlags.IsPresetIntegrationEnabled(),
100+
}).SetupWithManager(name, mgr)
101+
}
102+
96103
func startPluginDefinitionReconciler(name string, mgr ctrl.Manager) error {
97104
return (&plugindefinitioncontroller.PluginDefinitionReconciler{
98105
OCIMirroringEnabled: featureFlags.IsOCIMirroringEnabled(),

dev-env/dev.values.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ global:
99
expressionEvaluationEnabled: true
1010
integrationEnabled: true
1111
ociMirroringEnabled: true
12+
pluginPreset:
13+
expressionEvaluationEnabled: true
14+
integrationEnabled: true
1215
alerts:
1316
enabled: false
1417
certManager:

docs/reference/components/pluginpreset.md

Lines changed: 279 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ spec:
3333
optionValues:
3434
- name: perses.sidecar.enabled
3535
value: true
36+
- name: perses.ingress.host
37+
expression: |
38+
"perses.${global.greenhouse.clusterName}.example.com"
3639
pluginDefinitionRef:
3740
kind: ClusterPluginDefinition
3841
name: perses
@@ -86,8 +89,283 @@ spec:
8689

8790
`.spec.deletionPolicy` is an optional field that specifies the behaviour when a PluginPreset is deleted. The possible values are `Delete` and `Retain`. If set to `Delete` (the default), all Plugins created by the PluginPreset will also be deleted when the PluginPreset is deleted. If set to `Retain`, the Plugins will remain after the PluginPreset is deleted or if the Cluster stops matching the selector.
8891

92+
## CEL Expressions in OptionValues
93+
94+
PluginPresets support CEL (Common Expression Language) expressions in `optionValues`. Expressions are evaluated during PluginPreset reconciliation, and the resulting Plugin contains only the resolved values with no expression fields remaining.
95+
96+
Expressions use the `${...}` syntax to reference dynamic values:
97+
98+
```yaml
99+
spec:
100+
plugin:
101+
optionValues:
102+
- name: app.hostname
103+
expression: |
104+
"myapp.${global.greenhouse.clusterName}.example.com"
105+
```
106+
107+
When this PluginPreset creates a Plugin for a cluster named `cluster-a`, the Plugin will contain:
108+
109+
```yaml
110+
spec:
111+
optionValues:
112+
- name: app.hostname
113+
value: "myapp.cluster-a.example.com"
114+
```
115+
116+
### Available Variables
117+
118+
| Variable | Description | Example Value |
119+
|--------------------------------------|----------------------------|------------------------------|
120+
| `global.greenhouse.clusterName` | Name of the target cluster | `cluster-a` |
121+
| `global.greenhouse.organizationName` | Organization namespace | `my-org` |
122+
| `global.greenhouse.clusterNames` | List of all cluster names | `["cluster-a", "cluster-b"]` |
123+
| `global.greenhouse.teamNames` | List of all team names | `["team-1", "team-2"]` |
124+
| `global.greenhouse.baseDomain` | Base DNS domain | `greenhouse.example.com` |
125+
| `global.greenhouse.metadata.*` | Cluster metadata labels | `eu-de-1` |
126+
127+
> :information_source: `global.greenhouse.metadata.*` values are derived from cluster labels prefixed with `metadata.greenhouse.sap/`. For example, the label `metadata.greenhouse.sap/region: eu-de-1` becomes available as `global.greenhouse.metadata.region`.
128+
129+
### Examples
130+
131+
**Hostname per cluster:**
132+
133+
```yaml
134+
- name: ingress.host
135+
expression: |
136+
"service.${global.greenhouse.clusterName}.example.com"
137+
# Result for cluster "cluster-a": "service.cluster-a.example.com"
138+
```
139+
140+
**Using cluster metadata:**
141+
142+
```yaml
143+
- name: ingress.host
144+
expression: |
145+
"service.${global.greenhouse.metadata.region}.example.com"
146+
# Result: "service.eu-de-1.example.com"
147+
# Requires label metadata.greenhouse.sap/region on the cluster
148+
```
149+
150+
**Combining variables:**
151+
152+
```yaml
153+
- name: app.fqdn
154+
expression: |
155+
"${global.greenhouse.clusterName}-${global.greenhouse.organizationName}"
156+
# Result for cluster "cluster-a" in org "my-org": "cluster-a-my-org"
157+
```
158+
159+
### Expressions in ClusterOptionOverrides
160+
161+
Expressions can also be used in `clusterOptionOverrides`. Overrides are merged before expression evaluation, so override expressions are also resolved:
162+
163+
```yaml
164+
spec:
165+
plugin:
166+
optionValues:
167+
- name: app.mode
168+
value: "standard"
169+
clusterOptionOverrides:
170+
- clusterName: special-cluster
171+
overrides:
172+
- name: app.hostname
173+
expression: |
174+
"special.${global.greenhouse.metadata.region}.example.com"
175+
```
176+
177+
> :information_source: Expressions are only evaluated in PluginPresets.
178+
179+
> :warning: CEL expressions on standalone Plugins are deprecated and will be removed in a future release. Use PluginPresets for expression evaluation.
180+
181+
182+
## Feature Flag
183+
184+
CEL expression evaluation in PluginPresets is enabled only when the feature flag `pluginPreset.expressionEvaluationEnabled` is set to `true` in the Greenhouse feature flags ConfigMap.
185+
186+
By default, this flag is `false` if it is unset or invalid, and expressions are passed through as literal values without evaluation.
187+
188+
```yaml
189+
# greenhouse-feature-flags ConfigMap
190+
apiVersion: v1
191+
kind: ConfigMap
192+
metadata:
193+
name: greenhouse-feature-flags
194+
namespace: greenhouse
195+
data:
196+
pluginPreset: |
197+
expressionEvaluationEnabled: true
198+
```
199+
200+
## ValueFrom References Between PluginPresets
201+
202+
PluginPresets can reference option values from other PluginPresets using `valueFrom.ref`.
203+
This enables one PluginPreset to use resolved values from another, including values generated by CEL expressions.
204+
205+
### Reference by Name
206+
207+
```yaml
208+
# Source PluginPreset - generates a hostname per cluster
209+
apiVersion: greenhouse.sap/v1alpha1
210+
kind: PluginPreset
211+
metadata:
212+
name: backend-preset
213+
spec:
214+
plugin:
215+
pluginDefinitionRef:
216+
name: backend-service
217+
optionValues:
218+
- name: backend.hostname
219+
expression: |
220+
"backend.${global.greenhouse.clusterName}.example.com"
221+
clusterSelector:
222+
matchLabels:
223+
env: production
224+
---
225+
# Consumer PluginPreset - references the backend hostname
226+
apiVersion: greenhouse.sap/v1alpha1
227+
kind: PluginPreset
228+
metadata:
229+
name: frontend-preset
230+
spec:
231+
plugin:
232+
pluginDefinitionRef:
233+
name: frontend-service
234+
optionValues:
235+
- name: frontend.backendUrl
236+
valueFrom:
237+
ref:
238+
kind: PluginPreset
239+
name: backend-preset
240+
expression: |
241+
${spec.optionValues.filter(v, v.name == "backend.hostname")[0].value}
242+
clusterSelector:
243+
matchLabels:
244+
env: production
245+
```
246+
247+
The resulting Plugin will contain the resolved value. For example:
248+
249+
```yaml
250+
# If the matching cluster is named "production-eu":
251+
spec:
252+
optionValues:
253+
- name: frontend.backendUrl
254+
value: "backend.production-eu.example.com"
255+
```
256+
257+
### Reference by Label Selector
258+
When multiple PluginPresets need to be referenced, use a label selector.
259+
The CEL expression is evaluated against each matching PluginPreset and results are collected into an array.
260+
261+
262+
```yaml
263+
# Multiple source PluginPresets with shared label
264+
apiVersion: greenhouse.sap/v1alpha1
265+
kind: PluginPreset
266+
metadata:
267+
name: selector-source-a
268+
namespace: demo
269+
labels:
270+
e2e.greenhouse.sap/selector-test: "true"
271+
spec:
272+
plugin:
273+
pluginDefinitionRef:
274+
name: perses
275+
releaseName: perses-sel-source-a
276+
releaseNamespace: kube-monitoring
277+
optionValues:
278+
- name: source.endpoint
279+
expression: |
280+
"endpoint-a.${global.greenhouse.clusterName}.example.com"
281+
clusterSelector:
282+
matchLabels:
283+
greenhouse.sap/cluster: kind-greenhouse-remote
284+
---
285+
apiVersion: greenhouse.sap/v1alpha1
286+
kind: PluginPreset
287+
metadata:
288+
name: selector-source-b
289+
namespace: demo
290+
labels:
291+
e2e.greenhouse.sap/selector-test: "true"
292+
spec:
293+
plugin:
294+
pluginDefinitionRef:
295+
name: perses
296+
releaseName: perses-sel-source-b
297+
releaseNamespace: kube-monitoring
298+
optionValues:
299+
- name: source.endpoint
300+
expression: |
301+
"endpoint-b.${global.greenhouse.clusterName}.example.com"
302+
clusterSelector:
303+
matchLabels:
304+
greenhouse.sap/cluster: kind-greenhouse-remote
305+
---
306+
apiVersion: greenhouse.sap/v1alpha1
307+
kind: PluginPreset
308+
metadata:
309+
name: selector-consumer
310+
namespace: demo
311+
spec:
312+
plugin:
313+
pluginDefinitionRef:
314+
name: perses
315+
releaseName: perses-sel-consumer
316+
releaseNamespace: kube-monitoring
317+
optionValues:
318+
- name: consumer.endpoints
319+
valueFrom:
320+
ref:
321+
kind: PluginPreset
322+
selector:
323+
matchLabels:
324+
e2e.greenhouse.sap/selector-test: "true"
325+
expression: |
326+
${spec.optionValues.filter(v, v.name == "source.endpoint")[0].value}
327+
clusterSelector:
328+
matchLabels:
329+
greenhouse.sap/cluster: kind-greenhouse-remote
330+
```
331+
332+
The consumer Plugin will receive an array of all resolved values:
333+
334+
```yaml
335+
spec:
336+
optionValues:
337+
- name: consumer.endpoints
338+
value: [ "endpoint-a.kind-greenhouse-remote.example.com",
339+
"endpoint-b.kind-greenhouse-remote.example.com"]
340+
```
341+
342+
### CEL Expression Syntax for References
343+
The expression field in valueFrom.ref supports multiple syntax styles:
344+
345+
#### New simplified syntax
346+
```expression: spec.optionValues.filter(v, v.name == "my.value")[0].value```
347+
348+
#### With ${...} wrapper
349+
```expression: ${spec.optionValues.filter(v, v.name == "my.value")[0].value}```
350+
351+
#### Legacy syntax (backward compatible)
352+
```expression: object.spec.optionValues.filter(v, v.name == "my.value")[0].value```
353+
354+
355+
> :warning: ValueFrom references in PluginPresets only support referencing other PluginPresets (kind: PluginPreset). Referencing standalone Plugins is not supported.
356+
357+
### Feature Flags
358+
Expression evaluation and ValueFrom.Ref resolution in PluginPresets are controlled by feature flags:
359+
360+
```yaml
361+
# greenhouse-feature-flags ConfigMap
362+
pluginPreset: |
363+
expressionEvaluationEnabled: true
364+
integrationEnabled: true
365+
```
366+
89367
## Next Steps
90368

91369
- [Managing Plugins for multiple clusters](./../../../user-guides/plugin/plugin-management)
92370
- [Plugin reference](./../plugin)
93-
- [PluginDefinition reference](./../plugindefinition)
371+
- [PluginDefinition reference](./../plugindefinition)

0 commit comments

Comments
 (0)