v0.7.13 — External calls at admission and reconcile time, protocol clients [UNRELEASED]

3 min read

validation.external and mutation.external

External HTTP calls can now be declared under validation: and mutation: in addition to onReconcile:. Calls declared here fire before the corresponding rule loop — at admission webhook time and, by default, at every reconcile.

validation:
  external:
    - name: healthCheck
      url: "{{ .spec.healthCheckUrl }}/health"
      expectedStatus: 200
      continueOnError: true
      fires:
        reconcile: false   # admission-only

  rules:
    - field: "{{ .external.healthCheck.status }}"
      equals: "200"
      action: deny
      message: "health check failed — CR rejected"

fires.reconcile: false marks a call as admission-only. The reconciler skips it on resyncs; the webhook always runs it. When omitted, the call fires at both sites.

Results from validation.external and mutation.external are now propagated back into the main reconcile resolver — status fields, resource templates, and subsequent steps can reference .external.<name>.* just like onReconcile.external results. This holds even on the denial path: when validation denies, status is patched with the enriched resolver, so phase and error fields reflect the external call outcome.

The external call runner is now in pkg/external — shared by the reconciler and the gateway webhook.

include: in external call lists

All external: lists now support per-item include: entries. An item with include: set is replaced in-place by the calls: list from the referenced file. Works in onReconcile.external, onCreate.external, hooks.external, validation.external, and mutation.external.

onReconcile:
  external:
    - include: ./shared/auth-calls.yaml
    - name: healthCheck
      url: "{{ .spec.serviceUrl }}/health"

./shared/auth-calls.yaml:

calls:
  - name: tokenFetch
    url: "{{ .spec.authUrl }}/token"
    method: POST

Protocol clients

external: blocks now support a protocol: field selecting a native client instead of HTTP.

Protocolprotocol: valueurl:query:
HTTP (default)http or omitany URLnot used
PrometheusprometheusPrometheus base URLPromQL expression
Redisredisredis://host:portRedis command (PING, LLEN jobs, …)
PostgreSQLpostgresconnection stringSQL query
MongoDBmongomongodb://host:portdb.collection [filter]
Kafkakafkakafka://broker:9092group/topic for lag, @topic for metadata

All protocols resolve before deployment evaluation and return results at external.<name>.*. continueOnError: true is supported on all of them.

Prometheus notes

Seven template notes for working with Prometheus external results:

  • promValue — extract the first scalar value from a Prometheus instant query result
  • promSum / promMax — aggregate across multiple series
  • promAboveThreshold / promBelowThreshold — boolean threshold checks for when: conditions
  • promSeriesCount — number of series returned
  • promLabelValues — extract a label value from the first series

exists and notExists e2e assertions

- name: field is populated
  kubectl:
    get:
      - kind: MyApp
        name: my-app
        namespace: default
        field: .status.connectionCount
        exists: true

exists: true fails when the field is empty or missing. notExists: true fails when the field is present. Both work in all e2e assertion blocks (kubectl, exec, http, expect).

Runtime RBAC — automatic secrets get for auth.secretRef

When any external: block in a Katalog uses auth.secretRef, the generated runtime RBAC now automatically includes a secrets get rule. No manual RBAC annotation required.

GET /api/v1/query endpoint

The runtime now exposes GET /api/v1/query?expr=<promql> — a passthrough Prometheus query endpoint backed by the operator’s own metrics. Useful for dashboards and autoscaler signals without a separate Prometheus instance.