Expression language

May 16, 2026 · View on GitHub

Portal's default expression engine is expr-lang/expr via internal/expr/exprlang. The engine compiles each rule: expression once at load time and re-runs the compiled program per evaluation.

The engine interface is pluggable (api.ExpressionEngine) — CEL/Rego/starlark are drop-in candidates for v3 without changing the rule schema.

Bound env

The env keys exposed by the pod ContextBuilder (internal/context/pod/builder.go) are:

KeyWhenType
objectalwaysnested map[string]any from unstructured.Unstructured
metadataalwaysshortcut to object.metadata
containerpod-shaped GVKs onlyone container per evaluation pass
specpod-shaped GVKs onlythe pod-shape .spec projection
securityContextpod-shaped GVKs onlythe pod-level .spec.securityContext
requestadmission only{operation, dryRun, userInfo, oldObject}
cluster.<gvk>alwaysinformer-cache-backed accessor
consistentCluster.<gvk>alwaysdirect-API-call accessor

Detailed coverage of the pod-sugar fields is in ../concepts/context-and-pod-sugar.md; the cross-resource helpers are in ../concepts/cross-resource.md.

Operators Portal rules lean on

OperatorExample
Membershipcontainer.image.registry in ["docker.io", "ghcr.io"]
Regex (operator form)object.metadata.name matches "^web-"
Null-safe navcontainer.securityContext?.privileged == true
Null coalescecontainer.image.tag ?? "latest"
String prefixes/suffixesstartsWith(object.metadata.name, "web-"), endsWith(...)

startsWith, endsWith, contains, and the matches operator are part of expr-lang's stdlib.

Custom helpers

HelperSignatureNotes
regexMatchregexMatch(s, pattern string) boolFunction-form alternative to the matches operator for when the operator form is awkward (e.g. dynamic pattern). Pattern cache is global.

The helper is registered in internal/expr/exprlang/engine.go.

Result contract

Every rule: expression must evaluate to bool. A non-bool return surfaces as:

  • a compile-time error (caught at load; written to .status.parseError), or
  • an eval-time error (expression did not produce bool, got %T) which the engine wraps as eval: ....

Errors at eval time mark the evaluation as failed and increment the error counters — they do not bubble up as a deny decision.

Worked snippets

# privileged or escalating
container.securityContext.privileged == true
  || container.securityContext.allowPrivilegeEscalation == true

# every Deployment must have a matching PDB
cluster.poddisruptionbudgets.list(
  object.metadata.namespace,
  object.spec.selector.matchLabels
) | len == 0

# block images outside an allow-list
not (container.image.registry in ["ghcr.io", "registry.k8s.io"])

# require a team label
not ("team" in keys(object.metadata.labels ?? {}))