Skip to content

feat: surface backup state for managed applications (backend + console) #2609

Open

Description

Summary

The backups.cozystack.io/v1alpha1.Backup resource is already produced by the CNPGStrategy (Postgres) and AltinityStrategy (ClickHouse) controllers and persisted per tenant namespace, but a tenant has no first-class way to discover or read this state today: the managed-application CR does not carry a backup summary, no field-selector contract is documented, and the console has no surface to render backups on. This issue tracks closing that gap end-to-end across the platform and the console, as a follow-up surface track for the backups epic #1713 — which covers backup production, not visibility.

Updated 2026-08-03. Two things in this issue have gone stale since filing: the console is no longer a separate repository (cozystack/cozystack-ui is archived — the console was vendored into packages/system/dashboard/images/console/, so section D is a cozystack PR, not a cross-repo one), and part of section D has since landed. Corrected in place below.

Motivation

A tenant has no built-in way to answer questions like "when was my managed Postgres last backed up successfully", "what recoverability point is available right now", "did the last scheduled backup fail and why", or "which backups exist that I can restore from". The underlying data already exists — in Backup CRs, in postgresql.cnpg.io/v1.Cluster.status, in CNPG Prometheus metrics — but it is scattered across resource kinds and is not surfaced on the CR that the tenant actually interacts with. Cozystack targets low-skill operators, so dropping into kubectl get backups.postgresql.cnpg.io or scraping Prometheus is not an acceptable read path.

The console is a pure SPA against the Kubernetes API with no BFF, so the aggregation must live on the platform side; the console then renders whatever the CR exposes.

Current state

Already implemented:

  • Backup CR with +kubebuilder:selectablefield on spec.applicationRef.{apiGroup,kind,name} (api/backups/v1alpha1/backup_types.go:112-114) — server-side filtered listing is wire-ready but undocumented.
  • CNPGStrategy controller mirrors cnpg.io/Backup → cozystack Backup and records driver metadata such as cnpg.io/backup-name and cnpg.io/destination-path (internal/backupcontroller/cnpgstrategy_controller.go).
  • AltinityStrategy controller does the equivalent for ClickHouse (internal/backupcontroller/altinitystrategy_controller.go).
  • BackupJob and RestoreJob action CRs exist for the write path.
  • Console application detail page already has a tab system (Overview, Workloads, Services, Ingresses, Secrets, Events, VNC) ready to host one more tab (packages/system/dashboard/images/console/apps/console/src/routes/detail/ApplicationDetailPage.tsx).
  • A tenant-scoped Backups list surface now exists — routes/BackupResourceListPage.tsx plus the sidebar section listing Plans / Backup Jobs / Backups / Restore Jobs (sidebar-sections.tsx). This is the flat namespace-wide list only; it is not the per-application tab this issue asks for.
  • The tab allow-list already exists as an enum on the CRD (cozystack.io_applicationdefinitions.yaml, dashboard.tabs, currently workloads|ingresses|services|secrets|yaml), which settles the mechanism debated in open question 5 below — it needs a backups value, not a new field.

Missing:

  • No backup summary on managed-app status — kubectl get postgres foo reveals nothing about backups.
  • No documented field-selector contract for clients.
  • No convenience labels on Backup CRs pointing at the parent application (only the applicationRef field, which is awkward for label selectors and dashboards).
  • No printer columns on Backup for kubectl get backups.
  • No per-application console surface — no Backups tab on the application detail page, no backup widget on Overview, no backup-health chip on the app card. (The flat namespace-wide list landed, see above.)

Proposed scope

A. Status enrichment on managed-app CR (platform)

Add a uniform status.backup sub-block to every managed-application CR that supports backups (Postgres, ClickHouse, future drivers). Proposed schema, identical across strategies so the console can render one widget:

status:
  backup:
    health: Healthy | Degraded | Failed | Unknown
    lastSuccessfulAt: <RFC3339>
    lastSuccessfulName: <Backup CR name>
    lastFailedAt: <RFC3339>
    lastError: <truncated message>
    firstRecoverabilityPoint: <RFC3339>   # null when the driver does not report it
    nextScheduledAt: <RFC3339>            # populated only if a Plan targets this app
    totalBackups: <int>
    scheduleEnabled: <bool>

The source of truth is the list of Backup CRs filtered by spec.applicationRef. Driver-specific fields not present on the cozystack Backup (notably firstRecoverabilityPoint from cnpg.io/Cluster.status) are mirrored from the driver-native resource by the same controller that writes the summary.

Decision: a single backup-status aggregator controller, not per-strategy writes. The aggregator watches Backup and managed-app CRs and is the sole writer of <App>.status.backup. This avoids divergence between CNPG and Altinity output and keeps strategy controllers focused on their write path. Open for objection.

B. Field-selector contract and labels (platform)

Document the selectable-fields contract for Backup in api/backups/v1alpha1/DESIGN.md so clients can rely on it. In addition to the existing field selectors, strategy controllers set convenience labels on every Backup they create:

  • backups.cozystack.io/application-name=<name>
  • backups.cozystack.io/application-kind=<kind>
  • backups.cozystack.io/strategy=<strategy-name>

Field selectors are the canonical API; labels make kubectl get backups -l ... and third-party dashboards work without learning the field-selector path.

C. Printer columns (platform)

Add +kubebuilder:printcolumn annotations to Backup (Phase, Application, TakenAt, Age, Size) and to managed-app CRs (LAST BACKUP, BACKUP HEALTH) so kubectl get is useful out of the box.

D. Console — Backups tab and backup widget (in-tree)

Work under packages/system/dashboard/images/console/, all of it driven by data the platform exposes after A–C land:

  • New BackupsTab.tsx under apps/console/src/routes/detail/, registered in the existing tab array in ApplicationDetailPage.tsx. Uses useK8sList<Backup> with the documented field selectors. Table columns: TakenAt, Phase (status badge), Strategy, Plan, Artifact URI with copy, Size, Age, Error (truncated and expandable). Sorted by TakenAt descending. Empty state with a hint when no backups exist.
  • Tab visibility — the tab appears for applications whose ApplicationDefinition.spec.dashboard.tabs includes backups. That enum already exists and is the natural hint, so no new field is needed; the alternative auto-detect approach stays rejected to avoid first-load flicker.
  • OverviewTab.tsx gets a compact backup widget that reads instance.status.backup and shows health, last successful backup (relative + absolute), first recoverability point, next scheduled, and a link to the Backups tab. Hidden silently when status.backup is absent so older platform versions or apps without backups render unchanged.
  • AppCard.tsx gains an inline warning chip when status.backup.health is Failed or Degraded. Healthy and Unknown stay unmarked to avoid visual noise.
  • The console's shared types are extended with the Backup type and the new status.backup shape.

E. Documentation

Update api/backups/v1alpha1/DESIGN.md with the read path and the field-selector contract. Add a "Reading backup state" section to packages/apps/postgres/README.md (and the ClickHouse equivalent) explaining what kubectl get postgres foo and kubectl get backups -l ... return. A docs-site PR in cozystack/website cross-links these from the user-facing manual.

Out of scope

  • Restore UX in the console (creating RestoreJob interactively) — obvious follow-up, reserve the route but do not implement. Note a BackupCreatePage route is already registered and reachable by URL though nothing links to it (see Dashboard shows Add button for Backups page for cluster-admin, leads to blank form #2165); decide whether to drop or reuse it.
  • Triggering ad-hoc backups from the console — same.
  • Plan and BackupClass management UI — same.
  • Backup size / dedupe ratio sourced from Barman cloud metadata or Prometheus — separate ticket once a delivery path is chosen.
  • PVC / ZFS snapshot backup mechanics — covered by epic Backups for managed apps #1713.

Acceptance criteria

  • kubectl get postgres <name> -o jsonpath='{.status.backup}' returns a populated summary on a cluster with at least one completed backup; the same for ClickHouse.
  • kubectl get backups -n <tenant> --field-selector spec.applicationRef.name=<app>,spec.applicationRef.kind=Postgres returns only matching backups, and the equivalent kubectl get backups -l backups.cozystack.io/application-name=<app> works.
  • kubectl get backups -n <tenant> shows useful printer columns out of the box.
  • The aggregator controller is covered by unit tests with success-only, failure-only, and mixed Backup histories, including the "no backups yet" case.
  • Helm-unittest fixtures are updated for every chart whose schema changes.
  • DESIGN.md documents the read path and the field-selector contract.
  • In the console, the Backups tab renders correctly for a Postgres instance with backups, with an empty state for one without, and is hidden for applications whose ApplicationDefinition.spec.dashboard.tabs omits backups.
  • The Overview backup widget appears when status.backup is populated and is silently absent otherwise; the app card warns on Failed / Degraded health only.
  • Console typecheck and lint pass; no React hooks-rule warnings.

Open questions

  1. Aggregator controller vs per-strategy writes — preference?
  2. Should nextScheduledAt be sourced from cozystack Plan only, or also from cnpg.io/ScheduledBackup if a user creates one outside the cozystack framework?
  3. firstRecoverabilityPoint shape — hide the field when the driver does not provide it, or always present with a null value?
  4. Feature gate or default-on?
  5. Tab visibility — confirm the ApplicationDefinition.spec.dashboard.features hint as the mechanism, or pick something else? Answered: the dashboard.tabs enum already exists on the ApplicationDefinition CRD, so the decision is just to add a backups value to it.

Related

  • Parent epic Backups for managed apps #1713 (backup production track)
  • Design doc: api/backups/v1alpha1/DESIGN.md
  • Console: in-tree at packages/system/dashboard/images/console/ (cozystack/cozystack-ui is archived)

Activity

  1. added
    kind/featureCategorizes issue or PR as related to a new feature
    priority/important-soonMust be staffed and worked on either currently, or very soon, ideally in time for the next release
    area/databaseIssues or PRs related to managed databases (postgres, mariadb, redis, etcd, kafka, clickhouse)
    triage/needs-triageIndicates an issue needs triage by a maintainer
    area/apiIssues or PRs related to the cozystack-api aggregated API server
    area/dashboardIssues or PRs related to the dashboard / UI
    on May 12, 2026
  2. androndo commented on Aug 3, 2026

    @androndo
    Contributor

    Refreshed the body after auditing every open backup issue against main (04b742670). Sections A, B, C and E are untouched — they are entirely open, and I verified that: no status.backup block or aggregator controller exists, Backup still carries only the three selectablefield markers with no printcolumn, no convenience labels are written anywhere, and the field-selector contract is still undocumented.

    What I corrected:

    • the cross-repo framing is obsolete. cozystack/cozystack-ui is archived; the console was vendored into packages/system/dashboard/images/console/, so section D is a cozystack PR and its file paths were wrong. Retargeted them.
    • part of section D has landed: a tenant-scoped Backups list page exists (routes/BackupResourceListPage.tsx plus the sidebar section for Plans / Backup Jobs / Backups / Restore Jobs). That is the flat namespace-wide list only — no per-application tab, no Overview widget, no app-card chip — so I narrowed the "missing" bullet rather than removing it.
    • open question 5 is answered by something that already exists: ApplicationDefinition.spec.dashboard.tabs is an enum on the CRD (workloads|ingresses|services|secrets|yaml). Tab visibility needs a backups value added to it, not the new dashboard.features field this issue proposed. Struck the question and updated section D accordingly.
    • dropped the "related issue etcd: surface conditional required fields for backup.* once cozyvalues-gen supports it #2486" pointer: etcd: surface conditional required fields for backup.* once cozyvalues-gen supports it #2486 was closed as obsolete in this same audit, because the etcd v1alpha2 chart dropped the backup.* schema entirely, so nothing here depends on it.
    • noted the orphaned BackupCreatePage route (reachable by URL, nothing links to it) next to the out-of-scope restore UX, since whoever builds this tab should decide whether to drop or reuse it — see Dashboard shows Add button for Backups page for cluster-admin, leads to blank form #2165.
  3. added
    triage/acceptedIndicates an issue is ready to be actively worked on
    and removed
    triage/needs-triageIndicates an issue needs triage by a maintainer
    on Sep 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

area/apiIssues or PRs related to the cozystack-api aggregated API serverarea/dashboardIssues or PRs related to the dashboard / UIarea/databaseIssues or PRs related to managed databases (postgres, mariadb, redis, etcd, kafka, clickhouse)kind/featureCategorizes issue or PR as related to a new featurepriority/important-soonMust be staffed and worked on either currently, or very soon, ideally in time for the next releasetriage/acceptedIndicates an issue is ready to be actively worked on

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions