Kargo Stages only display the last Freight promoted through Kargo. When a developer
commits an image-tag bump directly to the GitOps repository and ArgoCD syncs it, the
cluster runs the new version while Kargo keeps showing the stale one — nothing told
Kargo’s bookkeeping to move. There is no observed-state import in Kargo (≤ v1.10), and
the only supported way to move a Stage’s state is to create a Promotion. That
constraint shapes the whole design: the controller detects drift and creates
Promotions; it never touches Stage status directly.
Application resources carrying the
annotation kargo.akuity.io/authorized-stage: "<namespace>:<stage>" — the same
annotation Kargo’s ArgoCD integration already requires before a Stage may manage an
Application. If Kargo promotes to your Applications today, the annotation is already
there, so there is zero additional per-application configuration. An Application can
opt out with kargo-observer.mkutlak.github.io/ignore: "true"..status.summary.images and compares them against the Stage’s current
view, .status.freightHistory[0], restricted to the repositories subscribed to by the
Stage’s requestedFreight origin Warehouses. Equal tags short-circuit as a no-op —
this is what keeps the controller loop-free.Freight in the Stage’s
namespace and looks for one whose images match all of the drifted repo:tag pairs.Promotion (generateName: <stage>-observer-,
labeled app.kubernetes.io/managed-by=kargo-argocd-observer,
kargo-observer.mkutlak.github.io/stage, kargo-observer.mkutlak.github.io/freight).
Kargo’s admission webhook authorizes and executes it like any other Promotion; because
the tag is already live in git, the promotion changes only Kargo’s bookkeeping.ArgoCD Application (kargo.akuity.io/authorized-stage annotation)
│
│ reconcile
▼
compare deployed image tags (.status.summary.images)
against Stage's current Freight (.status.freightHistory[0])
│
├── tags match ────────────────────────────► no-op, already converged
│
└── tags differ (drift)
│
▼
find Freight in the Stage namespace whose images
match ALL drifted repo:tag pairs
│
┌───────┴────────┐
│ │
Freight found no matching Freight
│ │
▼ ▼
create Promotion Event: FreightMissing
<stage>-observer-* kargo_observer_freight_missing = 1
│
▼
Kargo admission webhook checks the `promote` verb,
then executes the Promotion — a git no-op, since the
tag is already deployed; Stage's Freight state updates
Application and Kargo Stage/Freight/
Warehouse/Promotion objects are accessed via unstructured.Unstructured — the
argo-cd and kargo Go modules are deliberately not imported. This keeps the
dependency tree to controller-runtime + client-go + apimachinery and decouples
the controller from Argo/Kargo internal API versioning.The controller needs:
argoproj.io Application resources.kargo.akuity.io Stage, Freight, Warehouse, and Promotion
resources.kargo.akuity.io Promotion resources.promote verb on Stage resources — this is enforced by Kargo’s own
admission webhook via a SubjectAccessReview, not by the Kubernetes API server, and
must be granted separately from the standard create verb on promotions.See the Helm chart’s clusterrole.yaml template for the full
ClusterRole/ClusterRoleBinding.
FreightMissing until a Warehouse discovers it (or a stable-line Warehouse is added).kargo.akuity.io/authorized-stage annotation on Applications — present
wherever Kargo’s ArgoCD integration is in use.