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):
- NewResolver(ctx, obj) — base: spec, status, metadata
- resolver.WithChildren(map) — adds .children.deployment.status.*
- resolver.WithItem(val, as) — adds .item / .<as> for forEach loops
- resolver.WithExternal(map) — adds .external.<name>.status / .body
- resolver.WithCross(map) — adds .cross.<kind>.status.*
- resolver.WithMetrics(map) — adds .metrics.queueDepth / .workers / .autoscaleActive …
- 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 ¶
- func ToJSONSafe(v any) any
- func TryCoerceString(s string) any
- type Resolver
- func (r *Resolver) Data() map[string]interface{}
- func (r *Resolver) OwnerName() string
- func (r *Resolver) OwnerNamespace() string
- func (r *Resolver) RenderString(tmpl string) (string, bool)
- func (r *Resolver) Resolve(value string) (string, error)
- func (r *Resolver) ResolveConfigMapTemplate(src orktypes.ConfigMapTemplateSource) (orktypes.ConfigMapTemplateSource, error)
- func (r *Resolver) ResolveCronJobTemplate(src orktypes.CronJobTemplateSource) (orktypes.CronJobTemplateSource, error)
- func (r *Resolver) ResolveCustomResourceTemplate(src orktypes.CustomResourceTemplateSource) (orktypes.CustomResourceTemplateSource, error)
- func (r *Resolver) ResolveDeploymentTemplate(src orktypes.DeploymentTemplateSource) (orktypes.DeploymentTemplateSource, error)
- func (r *Resolver) ResolveHPATemplate(src orktypes.HPATemplateSource) (orktypes.HPATemplateSource, error)
- func (r *Resolver) ResolveIngressTemplate(src orktypes.IngressTemplateSource) (orktypes.IngressTemplateSource, error)
- func (r *Resolver) ResolveJobTemplate(src orktypes.JobTemplateSource) (orktypes.JobTemplateSource, error)
- func (r *Resolver) ResolveLabels(labels []orktypes.ResourceLabel) ([]orktypes.ResourceLabel, error)
- func (r *Resolver) ResolveName(tmpl string) string
- func (r *Resolver) ResolveNamespace(tmpl, ownerNamespace string) string
- func (r *Resolver) ResolveNamespaceTemplate(src orktypes.NamespaceTemplateSource) (orktypes.NamespaceTemplateSource, error)
- func (r *Resolver) ResolvePDBTemplate(src orktypes.PDBTemplateSource) (orktypes.PDBTemplateSource, error)
- func (r *Resolver) ResolvePVCTemplate(src orktypes.PVCTemplateSource) (orktypes.PVCTemplateSource, error)
- func (r *Resolver) ResolvePVTemplate(src orktypes.PVTemplateSource) (orktypes.PVTemplateSource, error)
- func (r *Resolver) ResolvePodTemplate(src orktypes.PodTemplateSource) (orktypes.PodTemplateSource, error)
- func (r *Resolver) ResolveReplicaSetTemplate(src orktypes.ReplicaSetTemplateSource) (orktypes.ReplicaSetTemplateSource, error)
- func (r *Resolver) ResolveRoleBindingTemplate(src orktypes.RoleBindingTemplateSource) (orktypes.RoleBindingTemplateSource, error)
- func (r *Resolver) ResolveRoleTemplate(src orktypes.RoleTemplateSource) (orktypes.RoleTemplateSource, error)
- func (r *Resolver) ResolveSecretTemplate(src orktypes.SecretTemplateSource) (orktypes.SecretTemplateSource, error)
- func (r *Resolver) ResolveSelectors(selectors map[string]string) (map[string]string, error)
- func (r *Resolver) ResolveServiceAccountTemplate(src orktypes.ServiceAccountTemplateSource) (orktypes.ServiceAccountTemplateSource, error)
- func (r *Resolver) ResolveServiceTemplate(src orktypes.ServiceTemplateSource) (orktypes.ServiceTemplateSource, error)
- func (r *Resolver) ResolveStatefulSetTemplate(src orktypes.StatefulSetTemplateSource) (orktypes.StatefulSetTemplateSource, error)
- func (r *Resolver) ResolveStatusFields(fields []orktypes.StatusFieldSpec) (map[string]interface{}, error)
- func (r *Resolver) ResolveStringSlice(values []string) ([]string, error)
- func (r *Resolver) TemplateEvaluator() orktypes.TemplateEvaluator
- func (r *Resolver) WithChildren(children map[string]interface{}) *Resolver
- func (r *Resolver) WithCross(data map[string]interface{}) *Resolver
- func (r *Resolver) WithDocker(data map[string]interface{}) *Resolver
- func (r *Resolver) WithExternal(results map[string]interface{}) *Resolver
- func (r *Resolver) WithGit(data map[string]interface{}) *Resolver
- func (r *Resolver) WithHealth(health map[string]interface{}) *Resolver
- func (r *Resolver) WithItem(value interface{}, as string, index int) *Resolver
- func (r *Resolver) WithItemAndValue(key interface{}, value interface{}, as string, index int) *Resolver
- func (r *Resolver) WithMetrics(metrics map[string]interface{}) *Resolver
- func (r *Resolver) WithNormalizeChanges(changes []orktypes.NormalizeChange) *Resolver
- func (r *Resolver) WithPrevious(previous map[string]interface{}) *Resolver
- func (r *Resolver) WithSpecOverride(spec map[string]interface{}) *Resolver
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func ToJSONSafe ¶
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 ¶
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 ¶
NewResolver creates a Resolver from any domain.Object.
func NewResolverFromMap ¶
NewResolverFromMap creates a Resolver from a plain map[string]interface{}. Used by conversion webhooks where we work with unstructured JSON.
func (*Resolver) Data ¶
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 ¶
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 ¶
OwnerNamespace returns the CR's metadata.namespace.
func (*Resolver) RenderString ¶
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 ¶
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) 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 ¶
func (r *Resolver) ResolveCustomResourceTemplate(src orktypes.CustomResourceTemplateSource) (orktypes.CustomResourceTemplateSource, error)
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 ¶
func (r *Resolver) ResolveDeploymentTemplate(src orktypes.DeploymentTemplateSource) (orktypes.DeploymentTemplateSource, error)
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) ResolveName ¶
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 ¶
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) 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 ¶
func (r *Resolver) ResolveReplicaSetTemplate(src orktypes.ReplicaSetTemplateSource) (orktypes.ReplicaSetTemplateSource, error)
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) ResolveRoleBindingTemplate ¶
func (r *Resolver) ResolveRoleBindingTemplate(src orktypes.RoleBindingTemplateSource) (orktypes.RoleBindingTemplateSource, error)
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 ¶
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 ¶
func (r *Resolver) ResolveServiceAccountTemplate(src orktypes.ServiceAccountTemplateSource) (orktypes.ServiceAccountTemplateSource, error)
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 ¶
func (r *Resolver) ResolveStatefulSetTemplate(src orktypes.StatefulSetTemplateSource) (orktypes.StatefulSetTemplateSource, error)
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 ¶
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 ¶
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 ¶
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 ¶
───────────────────────────────────────────────────────────────────────────── 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 ¶
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 ¶
───────────────────────────────────────────────────────────────────────────── 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 ¶
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 ¶
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 ¶
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 ¶
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) WithSpecOverride ¶
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.