template

package
v0.7.8 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 28, 2026 License: Apache-2.0 Imports: 11 Imported by: 0

Documentation

Overview

pkg/resources/template/resolver.go

pkg/resources/template/resolver_children.go

pkg/resources/template/resolver_data.go

Resolver extensions — context injection methods and data access.

Every With* method returns a NEW Resolver with the original unchanged. This is the immutable extension pattern: safe for concurrent use, no risk of one reconcile's context leaking into another's.

Extension chain (built in this order during reconcileImpl):

  1. NewResolver(ctx, obj) — base: spec, status, metadata
  2. resolver.WithChildren(map) — adds .children.deployment.status.*
  3. resolver.WithItem(val, as) — adds .item / .<as> for forEach loops
  4. resolver.WithExternal(map) — adds .external.<name>.status / .body
  5. resolver.WithCross(map) — adds .cross.<kind>.status.*
  6. resolver.WithMetrics(map) — adds .metrics.queueDepth / .workers / .autoscaleActive …
  7. resolver.WithPrevious(map) — adds .previous.* (rollback path only)

Each extension is a shallow copy of the previous resolver's data map with one new top-level key added. The template engine sees the full accumulated context at execution time.

pkg/resources/template/resolver_normalize.go

pkg/resources/template/resolver_status.go

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ToJSONSafe

func ToJSONSafe(v any) any

ToJSONSafe recursively converts Go values to JSON-safe types. YAML unmarshalling produces int/int64 for integers, but k8s DeepCopyJSONValue only handles float64. This normalises the tree before any SetNestedField call.

func TryCoerceString

func TryCoerceString(s string) any

TryCoerceString attempts to parse a resolved template string as a native Go type so that integer/boolean CRD fields pass Kubernetes API server validation. Only called when the original value contained a template expression ("{{"). Returns float64 for integers and floats (JSON-safe), bool for booleans, and the original string for everything else.

Types

type Resolver

type Resolver struct {
	// contains filtered or unexported fields
}

Resolver evaluates Go text/template expressions against a live CR object.

Any field value containing "{{" is treated as a template expression and evaluated against the CR. Any value without "{{" is returned as-is.

Template context is the CR's full object as map[string]interface{}:

"{{ .metadata.name }}"        → CR name
"{{ .metadata.namespace }}"   → CR namespace
"{{ .spec.image }}"           → spec.image field value
"{{ .spec.replicas }}"        → spec.replicas field value
"{{ .metadata.name }}-app"    → CR name with a static suffix

For *unstructured.Unstructured (unstructured mode) the full CR map including all spec fields is available.

TYPED: For typed objects only metadata fields were accessible — and used Go mode hooks for full spec access. Until 'objectToMap' breakthrough

func NewResolver

func NewResolver(ctx context.Context, obj domain.Object) (*Resolver, error)

NewResolver creates a Resolver from any domain.Object.

func NewResolverFromMap

func NewResolverFromMap(data map[string]interface{}) *Resolver

NewResolverFromMap creates a Resolver from a plain map[string]interface{}. Used by conversion webhooks where we work with unstructured JSON.

func (*Resolver) Data

func (r *Resolver) Data() map[string]interface{}

Data returns the resolver's internal object map.

The map contains the full CR as seen by template expressions:

.spec.*      — all spec fields
.status.*    — current status (from informer cache)
.metadata.*  — name, namespace, labels, annotations, generation
.children.*  — child resources, after WithChildren is called
.external.*  — HTTP call results, after WithExternal is called
.cross.*     — cross-CRD observations, after WithCross is called
.metrics.*   — live operatorbox runtime metrics, after WithMetrics is called
.health.*   — live operatorbox runtime health metrics, after WithHealth is called
.item        — current forEach item, after WithItem is called
.previous.*  — last successfully reconciled spec, after WithPrevious is called (rollback path only)

The returned map is the live internal map — do not mutate it. Used by:

  • resolveStatusFields — condition evaluation on status.fields when: blocks
  • runProviders — condition evaluation on provider declaration when: blocks
  • filterProviderDeclarations — same
  • EvaluateConditions — any code needing the full object context

func (*Resolver) OwnerName

func (r *Resolver) OwnerName() string

OwnerName returns the CR's metadata.name. Convenience accessor — avoids resolving "{{ .metadata.name }}" for callers that only need the owner name for labelling child resources.

func (*Resolver) OwnerNamespace

func (r *Resolver) OwnerNamespace() string

OwnerNamespace returns the CR's metadata.namespace.

func (*Resolver) Profiles added in v0.7.8

func (r *Resolver) Profiles() orktypes.ProfileRegistry

Profiles returns the user-defined profile registry attached to this resolver.

func (*Resolver) RenderString

func (r *Resolver) RenderString(tmpl string) (string, bool)

RenderString evaluates a single template expression against the resolver's current data map. Returns (result, true) on success; ("", false) on error. Used by the when: TemplateEvaluator so note functions are available in when: field expressions.

func (*Resolver) Resolve

func (r *Resolver) Resolve(value string) (string, error)

Resolve evaluates a single field value against the CR.

If value contains "{{" it is a template expression — evaluated against the full CR map. Otherwise returned as-is (static value, no cost). Missing CR fields resolve to "" (missingkey=zero — no error on absent keys).

func (*Resolver) ResolveClusterRoleBindingTemplate added in v0.7.8

ResolveClusterRoleBindingTemplate resolves all template expressions in a ClusterRoleBindingTemplateSource.

func (*Resolver) ResolveClusterRoleTemplate added in v0.7.8

ResolveClusterRoleTemplate resolves all template expressions in a ClusterRoleTemplateSource.

func (*Resolver) ResolveConfigMapTemplate

func (r *Resolver) ResolveConfigMapTemplate(src orktypes.ConfigMapTemplateSource) (orktypes.ConfigMapTemplateSource, error)

ResolveConfigMapTemplate resolves all template expressions in a ConfigMapsTemplateSource. Returns a new ConfigMapTemplateSource with all expressions evaluated — safe to pass directly to configmaps.Resolve().

func (*Resolver) ResolveCronJobTemplate

func (r *Resolver) ResolveCronJobTemplate(src orktypes.CronJobTemplateSource) (orktypes.CronJobTemplateSource, error)

ResolveCronJobTemplate resolves all template expressions in a CronJobTemplateSource.

func (*Resolver) ResolveCustomResourceTemplate

ResolveCustomResourceTemplate resolves all template expressions in a CustomResourceTemplateSource. It: - resolves simple string fields via r.Resolve - resolves labels/annotations via r.ResolveLabels / r.ResolveAnnotations - walks spec/status/other maps and resolves any string values - preserves non-string typed values

The returned value is the same concrete template type with all string templates evaluated. Caller is expected to call orkcust.Resolve(...) to convert the resolved template into a ResolvedCustomResourceSpec.

func (*Resolver) ResolveDeploymentTemplate

ResolveDeploymentTemplate resolves all template expressions in a DeploymentTemplateSource. Returns a new DeploymentTemplateSource with all expressions evaluated — safe to pass directly to deployments.Resolve().

func (*Resolver) ResolveHPATemplate

func (r *Resolver) ResolveHPATemplate(src orktypes.HPATemplateSource) (orktypes.HPATemplateSource, error)

ResolveHPATemplate resolves all template expressions in an HPATemplateSource.

func (*Resolver) ResolveIngressTemplate

func (r *Resolver) ResolveIngressTemplate(src orktypes.IngressTemplateSource) (orktypes.IngressTemplateSource, error)

ResolveIngressTemplate resolves all template expressions in an IngressTemplateSource.

func (*Resolver) ResolveJobTemplate

func (r *Resolver) ResolveJobTemplate(src orktypes.JobTemplateSource) (orktypes.JobTemplateSource, error)

ResolveJobTemplate resolves all template expressions in a JobTemplateSource.

func (*Resolver) ResolveLabels

func (r *Resolver) ResolveLabels(labels []orktypes.ResourceLabel) ([]orktypes.ResourceLabel, error)

ResolveLabels evaluates template expressions in label and annotation values. Keys are never template expressions — only values are resolved.

func (*Resolver) ResolveLimitRangeTemplate added in v0.7.8

ResolveLimitRangeTemplate resolves all template expressions in a LimitRangeTemplateSource.

func (*Resolver) ResolveName

func (r *Resolver) ResolveName(tmpl string) string

ResolveName resolves a name template expression. Returns empty string on error — callers use it for DeleteIfOwned and guard checks where an empty name is handled gracefully downstream.

func (*Resolver) ResolveNamespace

func (r *Resolver) ResolveNamespace(tmpl, ownerNamespace string) string

ResolveNamespace resolves a namespace template expression and falls back to ownerNamespace when the result is empty.

This is called before ResolveXTemplate to get the actual target namespace for guard checks and conditional cleanup. ResolveXTemplate will resolve namespace again — the double resolution is intentional and cheap.

func (*Resolver) ResolveNamespaceTemplate

func (r *Resolver) ResolveNamespaceTemplate(src orktypes.NamespaceTemplateSource) (orktypes.NamespaceTemplateSource, error)

ResolveNamespaceTemplate resolves all template expressions in a NamespaceTemplateSource.

func (*Resolver) ResolveNetworkPolicyTemplate added in v0.7.8

ResolveNetworkPolicyTemplate resolves all template expressions in a NetworkPolicyTemplateSource.

func (*Resolver) ResolvePDBTemplate

func (r *Resolver) ResolvePDBTemplate(src orktypes.PDBTemplateSource) (orktypes.PDBTemplateSource, error)

ResolvePDBTemplate resolves all template expressions in a PDBTemplateSource.

func (*Resolver) ResolvePVCTemplate

func (r *Resolver) ResolvePVCTemplate(src orktypes.PVCTemplateSource) (orktypes.PVCTemplateSource, error)

ResolvePVCTemplate resolves all template expressions in a PVCTemplateSource.

func (*Resolver) ResolvePVTemplate

func (r *Resolver) ResolvePVTemplate(src orktypes.PVTemplateSource) (orktypes.PVTemplateSource, error)

ResolvePVTemplate resolves all template expressions in a PVTemplateSource.

func (*Resolver) ResolvePodTemplate

func (r *Resolver) ResolvePodTemplate(src orktypes.PodTemplateSource) (orktypes.PodTemplateSource, error)

ResolvePodTemplate resolves all template expressions in a PodTemplateSource. Returns a new PodTemplateSource with all expressions evaluated — safe to pass directly to pods.Resolve().

Defaults applied when fields are empty after resolution:

Name      → ownerName + "-pod"      (applied later in pods.Resolve)
Namespace → ownerNamespace          (applied here so downstream has it)

func (*Resolver) ResolveReplicaSetTemplate

ResolveReplicaSetTemplate resolves all template expressions in a ReplicaSetTemplateSource. Returns a new ReplicaSetTemplateSource with all expressions evaluated — safe to pass directly to replicasets.Resolve().

func (*Resolver) ResolveResourceQuotaTemplate added in v0.7.8

ResolveResourceQuotaTemplate resolves all template expressions in a ResourceQuotaTemplateSource.

func (*Resolver) ResolveRoleBindingTemplate

ResolveRoleBindingTemplate resolves all template expressions in a RoleBindingTemplateSource.

func (*Resolver) ResolveRoleTemplate

func (r *Resolver) ResolveRoleTemplate(src orktypes.RoleTemplateSource) (orktypes.RoleTemplateSource, error)

ResolveRoleTemplate resolves all template expressions in a RoleTemplateSource.

func (*Resolver) ResolveSecretTemplate

func (r *Resolver) ResolveSecretTemplate(src orktypes.SecretTemplateSource) (orktypes.SecretTemplateSource, error)

ResolveSecretTemplate resolves all template expressions in a SecretTemplateSource. Returns a new SecretTemplateSource with all expressions evaluated — safe to pass directly to secrets.Resolve().

func (*Resolver) ResolveSelectors

func (r *Resolver) ResolveSelectors(selectors map[string]string) (map[string]string, error)

ResolveSelectors evaluates template expressions in selector maps Keys are never template expressions — only values are resolved.

Example:

name: {{ .metadata.name }}
app: {{ .metadata.labels.app }}

func (*Resolver) ResolveServiceAccountTemplate

ResolveServiceAccountTemplate resolves all template expressions in a ServiceAccountTemplateSource.

func (*Resolver) ResolveServiceTemplate

func (r *Resolver) ResolveServiceTemplate(src orktypes.ServiceTemplateSource) (orktypes.ServiceTemplateSource, error)

ResolveServiceTemplate resolves all template expressions in a ServiceTemplateSource.

func (*Resolver) ResolveStatefulSetTemplate

ResolveStatefulSetTemplate resolves all template expressions in a StatefulSetTemplateSource.

func (*Resolver) ResolveStatusFields

func (r *Resolver) ResolveStatusFields(fields []orktypes.StatusFieldSpec) (map[string]interface{}, error)

ResolveStatusFields evaluates template expressions in declarative status field declarations and returns a map[string]interface{} ready to merge into the CR's status patch.

Paths are relative to status and support dot-notation for nesting:

"phase"          → { "phase": "Running" }
"database.host"  → { "database": { "host": "..." } }
"ready"          → { "ready": "true" }

The returned map is the body of the status subresource patch — it is merged into the existing status rather than replacing it. Fields not declared here are untouched.

Errors in individual field expressions are collected and returned together so the caller sees all problems in one reconcile, not one at a time.

func (*Resolver) ResolveStringSlice

func (r *Resolver) ResolveStringSlice(values []string) ([]string, error)

ResolveStringSlice resolves template expressions in each element of a string slice. Each element is resolved independently — one failing element does not affect others. Used for toNamespaces where each namespace may be a template expression.

Example:

input:  ["{{ .metadata.namespace }}", "monitoring", "{{ .spec.extraNamespace }}"]
output: ["my-app", "monitoring", "platform"]

func (*Resolver) TemplateEvaluator

func (r *Resolver) TemplateEvaluator() orktypes.TemplateEvaluator

TemplateEvaluator returns a TemplateEvaluator bound to this resolver. Pass the returned func to types.EvaluateWhen at call sites where template expressions in when: fields should be evaluated against live CR data.

func (*Resolver) WithChildren

func (r *Resolver) WithChildren(children map[string]interface{}) *Resolver

WithChildren returns a new Resolver that includes child resource state in the template context under the "children" key.

This is the Layer 3 extension. The resolver is rebuilt with the children map so that status field expressions can reference child resource status:

{{ .children.deployment.status.readyReplicas }}
{{ .children.service.status.loadBalancer.ingress }}
{{ (index .children.deployments "my-site-api").status.readyReplicas }}

The original resolver's data map is copied — the original is not modified. The returned resolver is used only for status field resolution.

children is the map returned by ReadChildren — a nested structure of resource type → name → full object map.

func (*Resolver) WithCross

func (r *Resolver) WithCross(data map[string]interface{}) *Resolver

WithCross returns a new Resolver with cross-CRD observation data injected under the "cross" key. Used after ReadCross completes.

Each observed CR is keyed by the "as" name from the cross: declaration:

cross:
  - crd: database
    selector:
      name: "{{ .metadata.name }}"
    as: database

Results accessible in subsequent expressions and when: conditions:

{{ .cross.database.status.phase }}      → Database CR's status.phase
{{ .cross.database.status.endpoint }}   → Database CR's status.endpoint
{{ .cross.database.spec.storageGb }}    → Database CR's spec.storageGb
{{ .cross.database.found }}             → "true" if CR was found

The cross data is read from the target CRD's informer cache — zero API server calls for same-binary CRDs. HTTP fallback for cross-binary/cluster.

func (*Resolver) WithDocker

func (r *Resolver) WithDocker(data map[string]interface{}) *Resolver

───────────────────────────────────────────────────────────────────────────── WithDocker — Docker hook result injection ─────────────────────────────────────────────────────────────────────────────

WithDocker returns a new Resolver with Docker hook results injected under the "docker" key. Used after runDocker completes.

The Docker block is a generic bag of fields produced by the Docker hook. Typical fields:

.docker.image           — fully qualified image reference (registry/repo:tag)
.docker.buildSucceeded  — "true" if build completed successfully
.docker.error           — error message if build/push failed
.docker.called          — "true" if the Docker hook ran at least once

Katalogs can gate behavior and status on these fields:

when:
  - field: docker.buildSucceeded
    equals: "true"

status:
  - path: image
    value: "{{ .docker.image }}"

func (*Resolver) WithExternal

func (r *Resolver) WithExternal(results map[string]interface{}) *Resolver

WithExternal returns a new Resolver with HTTP call results injected under the "external" key. Used after runExternal completes.

Each call result is keyed by the call's name from the Katalog:

external:
  - name: healthCheck
    url: "{{ .spec.serviceUrl }}/health"

Results accessible in subsequent template expressions and when: conditions:

{{ .external.healthCheck.status }}    → HTTP status code as string
{{ .external.healthCheck.body }}      → response body (first 4KB)
{{ .external.healthCheck.error }}     → error message if call failed

Resource declarations that follow in runTemplateReconcile can gate on these:

when:
  - field: external.healthCheck.status
    equals: "200"

func (*Resolver) WithGit

func (r *Resolver) WithGit(data map[string]interface{}) *Resolver

───────────────────────────────────────────────────────────────────────────── WithGit — Git hook result injection ─────────────────────────────────────────────────────────────────────────────

WithGit returns a new Resolver with Git hook results injected under the "git" key. Used after runGit completes.

The Git block is a generic bag of fields produced by the Git hook. Typical fields:

.git.commit    — current HEAD commit hash
.git.changed   — "true" if commit changed since last reconcile
.git.path      — local working directory path
.git.error     — error message if the Git operation failed
.git.called    — "true" if the Git hook ran at least once

Katalogs can gate behavior on these fields:

when:
  - field: git.changed
    equals: "true"

status:
  - path: lastCommit
    value: "{{ .git.commit }}"

func (*Resolver) WithHealth

func (r *Resolver) WithHealth(health map[string]interface{}) *Resolver

WithHealth returns a new Resolver with live operatorbox runtime health injected under the "health" key. Makes the following available in templates:

{{ .health.healthy }}                 — boolean: overall health
{{ .health.state }}                   — "healthy" / "degraded" / "error"
{{ .health.started }}                 — runtime started flag
{{ .health.pending }}                 — pending startup
{{ .health.startedAt }}               — RFC3339 timestamp
{{ .health.uptime }}                  — human‑readable uptime
{{ .health.queueDepth }}              — current queue depth
{{ .health.errorRate }}               — reconcile error rate
{{ .health.consecutiveFails }}        — consecutive reconcile failures
{{ .health.totalReconciles }}         — total reconciles since start
{{ .health.resourceCount }}           — number of managed CRs
{{ .health.lastError }}               — last reconcile error (string)
{{ .health.lastReconcile }}           — timestamp of last reconcile
{{ .health.hasUnhealthyDependencies }}— dependency health flag

Useful in status.fields to surface live runtime health into CR status.

func (*Resolver) WithItem

func (r *Resolver) WithItem(value interface{}, as string, index int) *Resolver

WithItem returns a new Resolver with a forEach item injected into the template context. Used by expandForEach when iterating over a list field.

The item is injected under two keys:

  • "item" — always available as {{ .item }} regardless of as
  • <as> — the name declared in forEach.as, e.g. "region", "namespace"

Both resolve to the same value. "item" is the canonical accessor; <as> is the semantic name that makes the Katalog more readable:

forEach:
  field: spec.regions
  as: region

# In expressions, both work:
name: "{{ .metadata.name }}-{{ .item }}"
name: "{{ .metadata.name }}-{{ .region }}"

index is the 0-based position in the list, available as {{ .index }}.

func (*Resolver) WithItemAndValue

func (r *Resolver) WithItemAndValue(key interface{}, value interface{}, as string, index int) *Resolver

WithItemAndValue is the map-forEach variant of WithItem. Used when the forEach field resolves to a map[string]interface{} instead of a list.

.item  / .<as> → the map key (string)
.value          → the map value (object or string) — access nested fields as .value.replicas
.index          → 0-based iteration order (keys sorted alphabetically)

func (*Resolver) WithMetrics

func (r *Resolver) WithMetrics(metrics map[string]interface{}) *Resolver

WithMetrics returns a new Resolver with live operatorbox runtime metrics injected under the "metrics" key. Makes the following available in templates:

{{ .metrics.queueDepth }}             — current workqueue depth
{{ .metrics.workers }}                — current effective worker count
{{ .metrics.autoscaleActive }}        — "true"/"false" — override active
{{ .metrics.workersBusyPercent }}     — worker utilisation %
{{ .metrics.workersIdlePercent }}     — idle worker %
{{ .metrics.errorRatePercent }}       — reconcile error rate %
{{ .metrics.reconcileDurationP95Ms }} — P95 reconcile latency (ms)

Useful in status.fields to surface live runtime state into CR status.

func (*Resolver) WithNormalizeChanges

func (r *Resolver) WithNormalizeChanges(changes []orktypes.NormalizeChange) *Resolver

WithNormalizeChanges returns a new Resolver that includes normalize audit data under the "_normalizeChanges" key. Status field templates can reference this when normalize.audit: true is declared:

  • path: normalizeChanges value: "{{ toJson ._normalizeChanges }}"

When changes is empty, the original resolver is returned unchanged.

func (*Resolver) WithPrevious

func (r *Resolver) WithPrevious(previous map[string]interface{}) *Resolver

WithPrevious returns a new Resolver with the previous spec injected under the "previous" key. Used by runRollback when applying onRollback templates.

The previous spec is the last successfully reconciled spec, captured in the orkestra.orkspace.io/previous-spec annotation before each spec change.

Templates in onRollback: declarations can reference:

{{ .previous.spec.image }}        — previous image
{{ .previous.spec.replicas }}     — previous replica count
{{ .previous.metadata.name }}     — CR name (same across generations)

The previous map is injected as-is from the decoded annotation — it mirrors the CR's .spec.* structure exactly.

func (*Resolver) WithProfiles added in v0.7.8

func (r *Resolver) WithProfiles(reg orktypes.ProfileRegistry) *Resolver

WithProfiles attaches a user-defined profile registry to the resolver. Call this after NewResolver when the katalog declares a profiles: block.

func (*Resolver) WithSpecOverride

func (r *Resolver) WithSpecOverride(spec map[string]interface{}) *Resolver

WithSpecOverride returns a new Resolver with the spec key replaced by the provided map. Used by the rollBackOnError: true path to run derived rollback templates against the previous spec without requiring .previous.spec.* syntax.

The caller's templates use {{ .spec.image }} as written — during a derived rollback run, that expression resolves to the previous spec's image value. All other keys (.metadata, .status, .external, .cross) remain current.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL