ADR 0053 - Day 2 Operations With AppCat
Author |
Mike Ditton |
|---|---|
Owner |
Schedar |
Reviewers |
Schedar |
Date Created |
2026-04-23 |
Date Updated |
2026-04-30 |
Status |
draft |
Tags |
framework,framework2,operations,maintenance,crossplane,helm |
|
Summary
Logic that renders service objects from desired values belongs in the Helm chart. Logic that reacts to observed cluster state, tracks progress, or reflects status on the claim belongs in the composition function. The motivating case is appcat-charts#29: a post-major-version-upgrade backup Job, which belongs in the composition function. |
Context
appcat-charts#29 proposed adding a post-major-version-upgrade backup Job to the CNPG Helm chart. After a major version upgrade, pre-upgrade base backups are unusable, so a fresh backup must be taken. The chart rendered the Job conditionally when values showed a version mismatch.
The PR was blocked: team could not agree whether this kind of reactive, one-off operation belongs in the chart or in the composition function. This ADR establishes the rule.
Related:
Scope
This ADR applies to the current AppCat architecture (Framework 1.0) using Crossplane composition functions and Helm charts. It establishes a conceptual boundary (where does logic belong?) not a technical implementation plan.
In scope:
-
Deciding whether new reactive or lifecycle-driven operations go in the Helm chart or an appropriate Crossplane feature
-
The motivating case: post-major-version-upgrade backup Job in appcat-charts#29
-
All services using the current composition function + Helm chart model
Out of scope:
-
Framework 2.0 and Crossplane Operations—covered in a future workshop and separate ADRs
-
Refactoring existing services to conform to this boundary
-
Upstream or third-party Helm charts and their forking strategy
Decision
The dividing line: Desired values vs. observed state
The key question is: does the logic depend only on desired values, or does it need to observe current cluster state?
The Helm chart renders the service from desired values (what the user asked for). The composition function observes what is actually running and reacts to it.
| Helm chart | Composition function | |
|---|---|---|
Inputs |
Desired values from the claim |
Desired values + observed cluster state |
Renders |
Service objects (cluster, config, credentials, networking) |
Orchestration objects (Jobs, CronJobs, status patches) |
Re-renders when |
Values change |
Values change or observed state changes |
Suitable for |
Object shape, configuration, service structure |
Lifecycle events, reactive operations, progress tracking |
When logic belongs in the Helm chart
Put logic in the chart when it:
-
Derives from desired values, using
lookupat most to read existing cluster state for rendering—never to track progress or trigger reactions -
Is part of the service steady state—always present or always conditionally present based on values
-
Is best expressed as a template (labels, resource limits, conditional sections, config rendering)
Example: rendering a PostgresConfig with user-supplied parameters, enabling or disabling a sidecar based on a feature flag, setting resource requests based on instance size.
When a chart needs to read observed cluster state at render time (for example an existing secret or status field), use Helm’s lookup function rather than having the composition function patch the chart-managed resource to inject that value.
This keeps ownership clear: the chart owns the resource, the chart reads the state it needs.
When logic belongs in the composition function
Put logic in the composition function when it:
-
Needs to observe current cluster state to decide what to do
-
Represents a one-off reaction to a lifecycle event (upgrade completed, restore requested, version mismatch detected)
-
Must be reflected on the claim status (the user or external tooling needs to know it happened)
-
Has a distinct start, progress, and completion—a state machine, not a template
-
Does not modify resources managed by the Helm chart—dual ownership of a resource leads to conflicts and is hard to debug
Example: detecting that a major version upgrade just completed and triggering a backup Job; monitoring a restore Job and updating claim status; scheduling a restart after a config change.
Applied to appcat-charts#29
The post-upgrade backup Job was proposed in the chart because Helm can detect a version mismatch from values. However the Job is not part of the service steady state—it fires once after an upgrade and must not re-fire on the next reconcile. Its completion must be tracked so the claim reflects whether a valid post-upgrade backup exists. That requires observing state beyond values.
Decision: the post-upgrade backup Job belongs in the composition function, following the existing maintenance pattern from ADR 0047.