Feat: CEL filters, and $( ) expressions in action properties
Description of your changes
Adds CEL to kube-trigger, using the $( ) expression engine in kubevela/pkg/cel (kubevela/pkg#143), with one root, context: the same object CUE filters see, so CEL and CUE read the same paths.
Filters
A filter that is a single $( ) is CEL; any other filter is CUE, as before. No working CUE filter has that form, so this needs no switch.
filter: $(context.data.status.readyReplicas == context.data.spec.replicas)
filter: $(has(context.data.metadata.labels) && "tier" in context.data.metadata.labels && context.data.metadata.labels["tier"] == "web")
CEL's && and || skip what cannot change the result, so has() and in can guard fields only some objects carry, which CUE's non-short-circuiting && cannot. A result that is not a bool is an error.
Expressions in action properties
$( ) in action properties is rendered per event. An expression that is a whole property keeps its type; one inside text is joined in; $$( is a literal $(.
action:
type: patch-resource
properties:
resource:
apiVersion: core.oam.dev/v1beta1
kind: Application
name: $(context.data.metadata.labels["app"])
namespace: $(context.data.metadata.namespace)
The job's ID still comes from the properties as written, so jobs from one trigger keep sharing it.
Switches
On by default, and can be turned off:
| Scope | How |
|---|---|
| Standalone kube-trigger | --enable-cel-expressions=false |
| One TriggerService | annotation trigger.oam.dev/cel-expressions: "false" |
| Every TriggerService | manager --enable-cel-expressions=false |
| Opt-in only | manager --require-cel-expression-opt-in; only TriggerServices annotated "true" |
The manager passes its choice to each worker explicitly through the default worker template. The annotation is read when a TriggerService's worker is created.
Breaking change: a literal $( in action properties, such as a shell's $(date) or Kubernetes' $(VAR), is now written $$(. Such a property stops kube-trigger at start with an error naming the trigger and property and saying so. The triggerservice-image-update example is updated.
Validation
Every expression is planned when kube-trigger starts; one that does not compile stops it, naming the trigger and property, instead of failing on every event. Typing is dynamic (any kind can be watched): a typo or a wrong type shows at event time, where the event is dropped and the error logged.
I have:
- Read and followed KubeVela's pull request process.
- All commits are signed off (DCO).
- Related Docs updated properly. kube-trigger documents filters and its flags in its README: CEL filters, expressions in action properties, the switches, and the flag reference.
- Run
make reviewableto ensure this PR is ready for review. (Ranmake lint,make checklicenseandgo mod tidy; notgenerate, as the localcontroller-gendiffers from the pinned version.) - Added
backport release-x.ylabels to auto-backport this PR if necessary. Not needed: kube-trigger has no release branches.
How has this code been tested
Unit tests at each layer, written before the code:
pkg/expression: telling CEL from CUE filters; filter results,has()andinguards, short-circuiting, a missing field and a non-bool result as errors; property rendering (typed whole values, interpolation,$$(); compile errors naming the property with the$$(hint.pkg/eventhandler: CEL filters keep and drop events (including a test that fails if a CEL filter is read as CUE); bad expressions refuse the handler; properties are not read while expressions are off.pkg/config: startup validation reports every bad expression with its trigger; every file inexamples/compiles with expressions on.controllers/triggerservice: the switch and annotation decision table, and the default worker template rendering--enable-cel-expressions=trueand=false.
go test ./pkg/... ./controllers/... passes; golangci-lint and staticcheck are clean. A CEL filter on a 5 KB Deployment evaluates in about 0.056 ms against about 1.05 ms for the CUE equivalent. Not yet run against a live cluster.
Special notes for your reviewer
- Independent of #61, #62 and #63. With #63,
context.changedbecomes readable from CEL with no change here; whichever merges second needs a small rebase in the event handler. contextis evaluated dynamically. Typed checking against the watched kind's schema (as ValidatingAdmissionPolicy does) is a possible follow-up; it needs a TriggerService admission webhook, which kube-trigger does not have.
Summary by cubic
Adds CEL expressions to kube-trigger using the expression engine from kubevela/pkg/cel. A filter that is a single $( ) is evaluated as CEL; any other filter is CUE as before. Both read the same context root, so CEL and CUE read the same paths.
$( )in action properties is rendered per event; an expression that is a whole property keeps its type.- CEL's
&&/||short-circuit, sohas()andincan guard fields only some objects carry; a non-bool filter result is an error. - Every expression is planned at startup, so one that does not compile stops kube-trigger naming the trigger and property, instead of failing per event. This includes a filter that opens with
$(but never closes, which previously only failed per event. - Expression rendering in properties is on by default and can be turned off per worker, per TriggerService via the
trigger.oam.dev/cel-expressionsannotation, or for all TriggerServices; the manager can also require per-TriggerService opt-in.
Breaking change
- A literal
$(in action properties, such as a shell's$(date)or Kubernetes'$(VAR), must now be written$$(; an unescaped one stops kube-trigger at startup. Thetriggerservice-image-updateexample now uses backticks for the shell substitution, so it runs the same with expressions on or off.