Operator mode see history edit this page

Talks about: , and

JaaS runs as a Kubernetes operator alongside its HTTP renderer. In this mode it watches custom resources, evaluates the Jsonnet they describe, and publishes the result as a Flux ExternalArtifact that downstream controllers consume. The HTTP renderer keeps running; the operator is an additional set of goroutines, not a separate binary.

Enabling the operator

Set --enable-flux-integration on the binary:

jaas --enable-flux-integration \
  --storage-path=/var/lib/jaas/artifacts \
  --storage-base-url=http://jaas-storage.jaas.svc:8082

--storage-path and --storage-base-url are required in operator mode — they tell the operator where to write artifact tarballs and the public URL prefix downstream consumers fetch them from.

With the Helm chart set operator.enabled: true:

operator:
  enabled: true

The chart wires the storage paths, leader election, RBAC, and the metrics Service for you.

The two custom resources

The operator watches two CRDs in the jaas.metio.wtf/v1 API group. Both are namespaced.

KindScopePurpose
JsonnetSnippetNamespacedA Jsonnet snippet to evaluate and publish as an ExternalArtifact.
JsonnetLibraryNamespacedReusable .libsonnet files that snippets in the same namespace import.

A JsonnetSnippet is the published unit. A JsonnetLibrary carries no artifact of its own — it exists to be imported by snippets. The full field reference for each lives at /api/jsonnetsnippet/ ; the library CRD is covered in Jsonnet libraries .

What the operator produces

Each reconcile of a JsonnetSnippet evaluates the snippet and writes the result into a tar.gz, then upserts a Flux ExternalArtifact CR whose status.artifact.url points at the operator’s storage HTTP server. In the default rendered output mode the archive holds a single rendered.json — the evaluated JSON. The published artifact’s URL is also mirrored onto the snippet’s own status.artifactURL, so kubectl describe jsonnetsnippet answers “where is my rendered output?” without a second lookup.

Any controller that understands Flux’s ExternalArtifact reads the result by pointing a sourceRef at it:

apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: consume-rendered
  namespace: default
spec:
  sourceRef:
    kind: ExternalArtifact
    name: hello-world

Real consumers of the published artifact include:

A minimal snippet

The simplest JsonnetSnippet carries its Jsonnet inline in spec.files and seeds two external variables:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: hello-world-tenant
  namespace: default
---
apiVersion: jaas.metio.wtf/v1
kind: JsonnetSnippet
metadata:
  name: hello-world
  namespace: default
spec:
  serviceAccountName: hello-world-tenant
  entryFile: main.jsonnet
  files:
    main.jsonnet: |
      {
        greeting: 'hello',
        recipient: std.extVar('audience'),
        timestamp: std.extVar('now'),
      }
  externalVariables:
    audience: world
    now: "2026-06-09T12:00:00Z"

spec.serviceAccountName names the ServiceAccount the operator impersonates for every API call this snippet drives — the artifact write, source fetches, library reads. That ServiceAccount’s RBAC, not the operator’s, governs what the snippet can reach. See Tenancy and RBAC for the verbs the tenant ServiceAccount needs.

Lifecycle knobs

Two spec fields control when and whether the operator reconciles a snippet. Both mirror Flux’s source-controller conventions.

spec.suspend

Set spec.suspend: true to pause reconciliation without deleting the snippet. The operator skips the evaluation pipeline, leaves the existing ExternalArtifact in place, and reports Ready=False with reason Suspended. Setting it back to false resumes reconciliation. The published artifact stays available the whole time, so downstream consumers keep reading the last rendered output while the snippet is paused.

spec:
  suspend: true

spec.interval

Set spec.interval to re-render the snippet on a fixed cadence even when no watch event fires:

spec:
  interval: 10m

A JsonnetSnippet re-renders whenever its source, libraries, or referenced Flux sources change. spec.interval adds a steady-state cadence on top of that, so the snippet picks up state outside the watched graph — external-variable environment drift on the operator pod, OCI library refreshes, and similar. The interval is bounded at admission to between 30s and 24h. Failed reconciles still use controller-runtime’s exponential backoff; the interval governs only the steady-state cadence.

Where to go next