Reference

3 min read

Wire format

operatorBox:
  onReconcile:
    external:
      - name: healthCheck
        url: "{{ .spec.serviceUrl }}/health"
        method: GET
        body: ""
        token: "$API_TOKEN"
        headers:
          X-Request-ID: "{{ .metadata.name }}"
        timeout: 5s
        expectedStatus: 200
        continueOnError: false
        when:
          - field: status.phase
            notEquals: "Ready"
        anyOf: []
        sleep: ""

Field reference

FieldRequiredDefaultDescription
nameyesIdentifier for accessing the result as .external.<name>.*. Must be a valid Go identifier — camelCase, no hyphens.
urlyesEndpoint URL. Supports template expressions resolved against the current CR.
methodnoGETHTTP method: GET, POST, PUT, PATCH, DELETE.
bodyno""Request body. Supports template expressions. Sets Content-Type: application/json automatically when non-empty.
tokenno""Bearer token for the Authorization header. Use $ENV_VAR syntax — the value is expanded via os.ExpandEnv at call time. Never put raw secrets here.
headersno{}Additional HTTP headers as a string map. Applied after Authorization and Content-Type.
timeoutno10sPer-call timeout. Go duration format: "5s", "1m", "500ms". Set this to a fraction of your resync: period.
expectedStatusno0When set, any response with a different status code is treated as a failure. When 0: 2xx succeeds, 4xx/5xx fails.
continueOnErrornofalsefalse: a failed call halts the reconcile and writes Ready=False. true: failure is logged, .error is set, reconcile continues.
whenno[]AND conditions. If any condition fails, the call is skipped and .called is "false".
anyOfno[]OR conditions. At least one must pass. Combined with when: using AND semantics.
sleepno""Delay injected before this call runs. Go duration format: "2s". Use to pace sequential calls against rate-limited APIs, or to wait for an async side-effect from a previous call before polling. Not for production throttling — use when: conditions instead.

Result context

After a call completes, four fields are available under .external.<name>:

FieldTypeDescription
.statusstringHTTP status code as a string: "200", "404", "503". Empty when the call failed before receiving a response.
.bodystringFirst 4096 bytes of the response body. Truncated silently for larger responses.
.errorstringError message when the call failed. Empty string on success.
.calledstring"true" when the call ran; "false" when skipped because when:/anyOf: conditions failed.

Access in templates:

# Dot-path in a when: condition
- field: external.healthCheck.status
  equals: "200"

# Template expression in a value
value: "{{ .external.appConfig.body }}"

# Template expression in a later call's url or token
token: "{{ .external.tokenFetch.body }}"

Constraints

Body cap — 4096 bytes. Large responses are truncated. If you need more, filter or paginate on the server side before the operator calls it.

Sequential execution. Calls run one at a time in declaration order. There is no parallelism. A later call can reference an earlier call’s result; an earlier call cannot see a later one.

camelCase names. Call names must be valid Go identifiers. Hyphens in names break template access because {{ .external.health-check.status }} does not parse as a field access.

Runs on every reconcile. Unless gated by a when: condition, the call runs every time the CR is reconciled — including on every resync. Design calls to be idempotent or gate them with conditions.

Token expansion via os.ExpandEnv. The token: field is expanded using standard Go environment variable syntax: $VAR and ${VAR}. This happens after template resolution, so token: "${{ .spec.tokenEnvVar }}" does not work — only static $VAR references expand.

No response streaming. The operator reads the full response body (up to 4096 bytes) before processing. Long-running or streaming endpoints are not supported.