Repository navigation
CloudNativePG: incident workflows, guarded operations and guided recovery - #1922
Conversation
PR Summary by QodoAdd CloudNativePG actions, live runtime, and a shared GitOps write guard
AI Description
Diagram
High-Level Assessment
Files changed (44)
|
Code Review by Qodo
1. Restricted users can download cluster reports
|
âĶworkspace (#1969) ## Summary The CloudNativePG workspace (#1921, #1922) carried a generic layer under CNPG names: the facts and problem list on every summary, per-kind coverage, the reviewed-action contract, grants, Prometheus series attribution, and screen layout borrowed from Capacity's internals. This PR moves that layer into shared, integration-neutral modules, so the next workspace-style integration (Velero is the likely one) imports it instead of copying CNPG. Nothing here is released yet, so the three CNPG wire shapes that every future workspace would copy are fixed now: - grants are structured objects, not sentences; - "not cached by Radar" is told apart from "no access"; - each object's GitOps manager comes from the server. It also adds the version-skew gate the CNPG endpoints were missing. Only mechanisms whose interface is already evident in today's code are shared: each has two or more consumers, or is a contract every workspace must follow. The rest is listed below as not shared yet. ## What changed ### Shared UI (`@skyhook-io/k8s-ui`) - **`components/facts/`**, for any surface that shows observed values (single-kind renderers included): - `Fact`, with `FactGrid`/`FactRow`/`FactValue`/`FactSource` - `CertaintyGlyph`, moved from Capacity; `CapacityCertainty` stays as the same type - `ManagedByText` - **`components/problems/`**: `WorkspaceProblem<Category>` and `ProblemCallout`/`ProblemList`/`ProblemMeta`, which take the workspace's `rootKind` instead of a hard-coded `'Cluster'`, plus `OpenIssueContext`. - **`ui/FoldSection`**: `SectionHeading`, `FoldSection`, `FoldSummary`. - **Elsewhere in k8s-ui**: - `ui/RefLink` now uses the existing `ResourceRef`. - `toneTextClass` and `worseTone` live in `ui/status-tone`. - `utils/grant` adds `Grant`, `formatGrant` and `grantParts`. - `issueReasonTitle` gives a reason one title on both the Issues page and the workspace. - **Kept CNPG-specific:** categories, ordering, wording and builders. CNPG binds `WorkspaceProblem` to its own categories. - **Badges:** CNPG badges now use `healthToSeverity` like the rest of the app. - **Buttons:** secondary buttons use a new `.btn-secondary` class (documented in DESIGN.md) instead of ten hand-rolled class strings. - **Sidebar:** `SidebarCategoryDestination.countLowerBound` renders a count over partly read data as `âĨN`, and its zero as unknown rather than none. ### App (`web/`) - **`components/workspace/`** holds the screen primitives Capacity and CNPG share: `ScreenBody`, `ScreenEmptyState`, `Notice`, `Segments`, `FilterChips`, `SectionTable` and its table classes, `RefreshFailedNotice`, `GrantText`, and the text helpers. CNPG no longer imports from `capacity/shared.tsx`. - **`api/actions.ts`** is the client half of the action contract: - the `ActionCapability` and `ActionRequest` types - `actionErrorCode`, which an integration widens with its own codes - `actionOutcomeLocked` and `actionCompleted` - `capabilityReason`, now the one "why is this disabled" wording - **Utilities:** `utils/drawer-trail.ts` holds the `?drawer=` codec, and `utils/page-links.ts` the back label and the subject-filtered Issues link. - **Kind table:** CNPG's detail kinds derive kind and group from the workspace kind table. ### Server - **`internal/server/actions.go`**: the reviewed-action contract. - `ActionCapability`, `ActionRequest`, `decodeActionRequest` and `decodeActionParams` - refusals: `changedAction`, `blockedAction`, `partialAction` and `writeActionError` - the permission decision: `grantPermission` and `capabilityVerdict`, with a real SelfSubjectAccessReview in local mode - `mergePatchAtVersion` - **Grants:** `Grant` lives in `internal/auth` and carries `In(ns)` and `String()`. - **Kind access:** `kind_access.go` covers per-kind access and coverage (`readWorkspaceKind`, `typedKindScope`, `KindCoverage`); `cache_scope.go` has `namespacesWithinCache`. - **Other shared helpers:** - `read_source.go`: one `ReadSource` shape for HA, recovery and storage - `fanout.go`: the bounded per-namespace fan-out - `internal/prometheus/series_scope.go`: `SeriesIsolation`, already used by the PVC usage charts - **Kept CNPG-specific:** facts, guards, runners and the extra refusal codes. - **Exported manager detection:** `pkg/topology` now exports `ManagedByFromMeta`. This is additive. ### Wire changes (CNPG endpoints, unreleased) - **`grant`** is `{verb, group?, resource, subresource?, namespace?}` (no namespace means cluster-wide) on every capability, read source, chart and report item. The wording is unchanged and comes from `Grant.String()` / `formatGrant`. - **Kind coverage** gains: - a state `uncached`, for a scope Radar's cache does not cover at all; - `uncachedNamespaces`, named under the same rule as `deniedNamespaces`. Before, a namespace the caller can read but Radar does not cache was reported as denied, and the UI said "No access". It now says "Radar does not cache Pods in db". - **`/api/cnpg/workspace`** gains `managedBy`, keyed `Kind/ns/name`. - "Declared in" now uses the server's manager detection and links to the Argo CD application or Flux object. - Before, the client parsed labels itself. That misnamed Argo CD applications living outside Argo CD's namespace (`ns_app`) and ignored the Flux namespace label. ### Version skew New `FeatureCapabilities` flags: `cnpgWorkspace` (every `/api/cnpg/*` route except the two image-catalog lookups that predate it) and `gitopsWriteEvidence`. Each has a `radarFeatures` entry. Every CNPG query, mutation, log stream, report download and the write-evidence query goes through `useRadarFeature`. Mutations are never retried. On a Radar without the workspace: - the sidebar offers no workspace destinations; - `/workload` links and drawer Expand stay on the standard views; - a Cluster's Logs tab shows its Pods' logs; - a workspace screen opened directly says it needs a newer Radar. ### Vocabulary On screen these areas are named by their subject. The block above a sidebar category's kinds reads **Views**, and the copy says "CloudNativePG views" and "CloudNativePG data", never "workspace", because Radar Hub uses "workspace" for the customer's account. "Workspace" remains the internal term for an integration with several kinds and its own screens (the integration guide, `SidebarCategoryWorkspace`, `/api/cnpg/workspace`). ### Docs - **DESIGN.md:** the rules for unknown, partial and denied values are written once. `capacity.md` and `cnpg.md` link to them and keep their per-value tables. - **`docs/INTEGRATION_GUIDE.md`:** a new "Workspace integrations" chapter covering when a workspace is warranted, the pieces to import, and what is not shared yet. CLAUDE.md points to it. ## Not shared yet These have one consumer, and their interface should come from the second: - the workspace registry / App wiring - the operation tracker - the generic action runner - the fixed-path `pods/proxy` reader - the merged log stream - the report bundle - operator diagnosis - a TTL-memo helper Rollouts' capabilities answer "allowed" in local mode without asking the apiserver. That is a real bug, but Rollouts returns bare booleans and hides denied actions, so it needs its own PR. ## Public surface - **k8s-ui:** exports that exist on main are unchanged. Renamed exports were all added in #1921/#1922. `CapacityCertainty` is kept. - **`pkg/`:** one additive export (`topology.ManagedByFromMeta`). - **`pkg/capacityapi` v1alpha1:** unchanged. - **radar-app:** surface unchanged. ## Testing - **Go:** `go build ./...`; `go test ./...` in both modules. The `cmd/desktop` env test is flaky and passes alone. `gofmt -l` is clean. - **Frontend:** - type checks pass for web and k8s-ui; - vitest: k8s-ui 236 files / 4,369 tests, web 182 files / 2,028 tests; - eslint: 0 errors, and no new warnings. - **Live on the kind CloudNativePG demo, full access and a view-only identity:** - every CNPG page rendered with structured grants and no "[object Object]" or "undefined"; - a namespaced Argo CD tracking ID on a demo Cluster showed "Declared in: Argo CD application argocd/payments", linked; - with the capability flags removed from `/api/capabilities` (an older Radar behind Radar Hub), the sidebar workspace, redirects, composed summaries and actions fell back to the standard views, and `/cnpg` showed the upgrade note. Stacked on #1922 (which is stacked on #1921). Merge order: #1921, #1922, then this PR, before a release ships `/api/cnpg/*`. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Touches CNPG API shapes, RBAC/coverage semantics, and cluster write paths via the shared action layer; behavior is intended to be equivalent with clearer grant and cache messaging. > > **Overview** > Extracts the generic workspace layer that CloudNativePG had under CNPG-specific names into shared modules, so future integrations (e.g. Capacity-style screens) import one contract instead of copying. > > **Server:** Adds `internal/auth.Grant` (structured RBAC on the wire), `internal/server/actions.go` (reviewed `ActionRequest`, capabilities, 409/partial errors, `mergePatchAtVersion`), `cache_scope.go` / `kind_access` patterns, and `prometheus/series_scope.go` (shared Prometheus isolation). CNPG handlers now use these; history/PVC denial fields expose `*Grant` instead of strings. `FeatureCapabilities` adds `cnpgWorkspace` and `gitopsWriteEvidence`. CNPG workspace coverage gains `uncached` / `uncachedNamespaces` (distinct from denied) and `managedBy` from server-side GitOps detection. > > **UI:** New k8s-ui `facts/`, `problems/`, fold sections, `formatGrant`, and app `components/workspace/` plus `api/actions.ts`. CNPG is rewired to structured grants and shared action types; secondary buttons use `.btn-secondary`. > > **Docs:** DESIGN.md documents unknown/partial/denied values once; INTEGRATION_GUIDE adds a workspace-integrations chapter; CLAUDE.md points builders at it. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 5af6ed1. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
The CNPG work in #1922 changes shared resource-page and navigation code used throughout Radar. This PR isolates that frontend blast radius so resource browsing, drawers, logs and Capacity can be reviewed independently while keeping the existing user journeys. Resource hosts own renderer overrides and resource-specific summaries, logs and destinations. The application composes the same 31 existing renderer wrappers once; the current CNPG adapter carries its already-shipped callbacks, group collision rules, redirects and drawer trail. Workspace route metadata owns titles, navigation placement, namespace-view scope and context-switch query policy. Capacity forwards only its existing resource-open callback and remains cluster-wide. Shared workspace notices, empty states and table styles now have neutral ownership rather than living under Capacity. The accessible certainty glyph and labels move into k8s-ui with the same value/tooltip semantics. The additive `ResourcesView.renderKindView` seam keeps the sidebar and inspected resource while replacing one kind's table; null/false uses the ordinary table. Row/search/bulk shortcuts, column preferences and table sorting/filter aggregation are inactive during a replacement; sidebar kind navigation stays available. Bulk dialogs/selections/comparison picks are cleared when a replacement arrives for the same kind. The review-sensitive boundaries are exact API-group identity, the stable renderer map, URL state and context pins, inherited detail tabs, and the table-to-custom-view transition. Existing server, RBAC and endpoint contracts are retained. Validation: both frontend type checks; full web suite (2,043 tests), final focused integration/Capacity/route tests (146), final k8s-ui suite (4,523 passed, one skipped); full `make build` including embed and Go binary. A same-kind bulk-dialog regression fails before the fix and passes afterwards. Live validation used the locally built application against existing AWS test clusters: normal resource tables/drawers/full pages, real Pod and Deployment log streams, parent navigation, all four Capacity screens including missing-pool recovery and node-removal presentation, and existing CNPG detail redirects/context pins. Actual CSS widths 1280, 1920 and 2560 were measured and captured without page overflow. Screenshots below show the preserved shared surfaces. Packed app/UI tarballs were installed into a private real Hub copy without source aliases. Its Vite production bundle and GET-only embedded resource, logs, Capacity and CNPG navigation journeys pass. Full Hub typechecking fails on an existing obsolete `NavCustomization.chrome` field; an independent exact-main packed control fails identically. Hub adoption must also remove its obsolete chrome/slot entries; no compatibility mode was added here.  
âĶction (#2079) CNPG setup needs to return from YAML review with edits intact, create new objects safely, open a chosen executable, and select a chart interval. Those capabilities belong in Radar's existing shared components. This PR extracts them from #1922 so changes to ordinary Radar workflows receive independent review; CNPG screens and operations remain there. ### Shared contracts - `CreateResourceDialog` adds initial/locked modes and a draft-preserving Back action. Successful callbacks receive the first result plus the exact submitted YAML after close; dry runs leave the dialog open. Partial creation retains persisted-object history through retries, review Back and file imports, and keeps recovery visible above the footer. Unlocked recovery switches to Apply when preview is available; direct creation stays in Create until the user chooses Apply. - Apply results include the persisted object's optional UID on real writes. Dry runs omit it. Existing fields and first-result callback semantics remain compatible. - Terminals accept an executable, title and context note. Explicit commands retain their chosen container, distinguish sessions when reusing tabs, refresh supplied metadata without reconnecting, and show command-specific error guidance. Error overlays follow the active theme; the terminal canvas and existing exec transport remain unchanged. - `AreaChart` adds optional shaded intervals, controlled selection and pointer/keyboard range callbacks. Range bands remain visible above opaque area fills. Ordinary charts retain touch-tap inspection. `AlertBanner` accepts an action beside its title while preserving its no-action markup. The shared package owns presentation and interaction; the host owns data and transport. These options add no generic navigation or new endpoints. Current main is merged normally. The only conflict was additive shared-package documentation; both the merged resource-kind/certainty guidance and these optional-flow contracts are retained. All runtime feature files are unchanged. The existing `>=1.17.0` peer minimum is retained. ### Validation All nine CI checks pass on the merged head: full Go/pkg tests, frontend typecheck/lint/build, 4,561 shared UI tests (one existing skip), 2,061 web tests, 140 Settings browser journeys, Helm and both CodeQL analyses. Both package typechecks, 91 focused shared-component/resource-kind-view tests and the Go persisted-UID/dry-run regression also pass locally. The cancel-import regression fails before its one-line fix and passes afterward. Test thresholds and existing assertions are unchanged. Real AWS fixture calls verified dry-run absence, create/update/partial-write UIDs and persisted values after refresh. Browser checks exercised default and guided creation, reviewed writes, Back retaining edits, three partial-recovery paths, chart pointer/keyboard/gap behavior, light/dark banners, an actual shell, direct `cat`, and a missing executable. Optional modes use a temporary local harness importing production components and the real backend; it adds no product route. A private copy of the real Hub source bundled the packed companion packages successfully. Its full TypeScript check reports the same existing `NavCustomization.chrome` error against exact main and these packages; this PR does not fix or claim to pass that inherited consumer drift. ### Rendered examples Actual shared components in the temporary harness, using the disposable AWS namespace:   
Bugbot needs on-demand usage enabledBugbot uses usage-based billing for this team and requires on-demand usage to be enabled. A team admin can enable on-demand usage in the Cursor dashboard. |
Bugbot needs on-demand usage enabledBugbot uses usage-based billing for this team and requires on-demand usage to be enabled. A team admin can enable on-demand usage in the Cursor dashboard. |
Cluster transitions replace clients and informer caches in separate steps. A request that copies them independently can pair dependencies from different generations. The existing client snapshot protects the client/config pair, but does not also capture caches and the operation cancellation context. `CaptureClusterReads` gives readers a short, nonblocking capture window around the process's context-operation lock. It refuses active or queued context operations and allows simultaneous captures. Writers retain their existing exclusive locking. Callbacks only copy dependencies; I/O and context/configuration operations belong after the capture. This guarantees consistency with context switching, authentication recovery and configuration operations. It does not establish startup/shutdown readiness: callers must still check the connection and nil dependencies. `TryClusterConfiguration` waits at most 50 ms for brief captures instead of refusing an unrelated settings update; an active or queued context transition is still refused immediately. Sustained contention retains the existing busy error. This independent carve-out from #1922 adds the primitive without changing an HTTP handler on main. Its concrete CNPG consumers capture clients, caches and cancellation together in that feature. Validation: the complete `internal/k8s` suite, including `go test -race -p 2 ./internal/k8s`; deterministic active/queued writer, concurrent-reader and replacement-serialization tests; `make build`; ten read-only API checks on AWS `radar-test-nonprod`. Live context transitions are covered by deterministic lifecycle tests rather than switching a user's cluster. Visual testing is not applicable. Design direction: if more callers need this capture seam, consider one coherent connection generation containing clients, caches and cancellation, published together. An atomic pointer alone does not establish informer readiness or queued-transition behavior; those lifecycle guarantees must survive that change. This PR keeps the bounded lock-and-copy seam and does not introduce a new lifecycle framework.
Workspace summaries need consistent ways to show what is known, why a value is unread, where a finding came from, and what an operator is being asked to change. Keeping those primitives under CNPG forces another integration to import that domain or copy its presentation. This promotes the existing facts, resource links, section headings and problem presentation into shared k8s-ui modules. CNPG summaries use them through compatibility wrappers that retain their public signatures. Generic findings use the existing non-healthy HealthLevel tones, including canonical orange alert and gray unknown; CNPG maps its own critical/warning/posture severity at that boundary. Issue, audit and measurement provenance form a discriminated union, and subjects derive from ResourceRef. WorkspaceProblemCallout has the same name at its definition and package export. ActionConfirmDialog is additive. It takes one result object for an ordinary error, an unknown outcome or a partially completed action; unknown and partial outcomes lock repeat submission. Hosts classify transport errors and retain the result across dismissal until they review current state again. The component reuses the existing dialog and disclosure primitives and owns no transport or authorization policy. Facts, links, headings and problem presentation have existing CNPG summary consumers on main. The dialog, grant display and ManagedByText have concrete callers in #1922; they are new APIs in this carve-out. FoldSection is used internally for original problem evidence. No package version, peer dependency, route or setting changes. The reviewed-action HTTP contract and host classifier stay in #1922. Validation: make tsc, package type checking, the previously completed full shared UI suite, focused public-export/provenance/CNPG summary/action-interaction regressions, and make build. Ten read-only AWS checks on radar-test-nonprod cover ordinary Radar paths and CNPG not-installed; AWS has no CNPG installation. Playwright now renders the fixture and verifies reference/Issues navigation, disclosure, typed confirmation, pending dismissal locks and distinct unknown/partial result locks. Sixteen complete screenshots were captured with measured 1280, 1920 and 2560 CSS-pixel viewports and inspected in both themes; the summaries and review/error/pending dialogs read correctly. Browser zoom required corrected screenshot bounds, with no application or preference changes. Synthetic fixture checks are not live CNPG actions.
8648f65 to
42dc651
Compare
âĶload Writes - Restart-instance and fence refuse the target of an in-flight switchover or failover, as destroy already did; capabilities and the POST agree. - A rolling restart binds the Cluster generation, so a changed primary update strategy is refused as changed rather than applied unreviewed. - A 503 without a code is an uncertain outcome (a proxy may have forwarded the write); Radar's own refusals before any write carry `unavailable`. Reads and reports - The report bundle withholds pgaudit statements, deadlock DETAIL queries, postImportApplicationSQL and the cnpg.io/podPatch annotation. - The scheduled-run issue evaluates only lists synced in the namespace. - The history memo no longer keeps cancelled or failed chart reads. - Recovery Jobs report their cnpg.io/jobRole. Follow-through and fleet - The tracker waits for the first read after a request instead of reading "cannot be followed"; a long backup no longer reads stalled; a restart keeps instances fenced after the request in its completion criteria; records are scoped to the API base for Radar Hub; unreadable readiness stays unknown. - The restore panel counts only recovery Jobs and stays completed once a restored instance is ready. - The switchover dialog no longer flags a streaming standby whose reported checkpoint timeline lags a promotion. - A single instance and a missing backup destination are posture on the Cluster page, as in the fleet. - The Resources sidebar reads disk and lag only on CloudNativePG views. - Connect copies over plain HTTP. Cleanup: drop the unused grant table, match cold snapshots with BackupMatchesCluster, reuse readscope.SelectNamespaces in MCP, correct doc comments that named pre-rename identifiers.
âĶrestore points Writes - Unfence refuses when a cold VolumeSnapshot backup can't be ruled out: the Cluster declares snapshots and its Backups could not be listed. - Pooler pause/resume and ScheduledBackup suspend/resume bind the reviewed generation, so a retargeted Pooler or schedule is refused as changed. - The remaining pre-write refusals (destroy preflight, missing exec client) carry the `unavailable` code and stay retryable. - The fence dialog disables an instance the server would refuse, with the reason. Restore - A VolumeSnapshot Backup offers only the end of its snapshot: a later point needs a WAL archive source the dialog does not configure. - Recovery evidence counts only what the selected source holds (that archive's backups and recovery window, or that Backup), so another method's newer backup can't make an archive look current. - The restore panel completes on a completed recovery Job too, and its footer states the rule the code applies. Health and counts - A replica cluster's replication never reads healthy without evidence about its upstream feed, on the Cluster page and in the fleet. - Outside the CloudNativePG views the Clusters badge is a lower bound, and its zero reads as unknown with the reason. Follow-through - A fence supersedes an instance restart only when it covers that instance; a stale identity with failing reads reads unobservable; destroyInstance follows runtime for its replacement. Doc comments that still named pre-rename identifiers are corrected.
âĶre completion - Restart-instance and fencing refuse a Pod labelled primary while the Cluster status names another: the standby path would delete or stop a primary. Fence runs now read the instance Pods to see the label. - The restore panel counts only a full recovery Job as proof the restore finished: a snapshot recovery Job without a WAL source only puts files in place, and PostgreSQL first starts on the instance. - A replica cluster's fleet replication carries the upstream caveat even before it reports a primary, and a single-instance replica cluster no longer reads calm.
Without the Pod there is no role label to compare with the Cluster status, so the mislabelled-primary refusal could not fire and a named fence could stop a primary the status does not name yet. Fencing every instance does not depend on the role and stays available.
The note lives on the Cluster, where Radars of other versions read it. Both readers now skip a format version they do not know instead of reading it with v1 meaning, and the server reader also skips a note recorded on an earlier Cluster of the same name, as the client already did. The integration guide now states the evolution rules a shipped workspace flag implies: responses only grow, new routes, actions, required parameters or bound facts need a new flag, and an action error code means nothing was written.
docs/cnpg.md drops repeated sections, tuning constants, UI breakpoints, copy strings and symbol names that live in the code, and keeps each rule with its reason. The API section becomes one row per route with its gate and handler. Fixes the Actions table that a blank line split, a stale claim that reports call handlers, and paragraphs filed under the wrong section; adds the operator-status qualifier, the unfence escape hatch and the Pooler runtime gate. INTEGRATION_GUIDE.md files the reviewed-write hook under Actions; integrations.md drops a function name.
âĶ2143) Moves helpers that several handlers each carried into shared packages. No behavior change is intended. `prom.NormalizeOrigin` is removed: Radar Hub does not import `pkg/prom`, so nothing outside this repo calls it. It is split out of #1922 so the parts that reach every namespaced read can be reviewed on their own, and so they stay revertable and bisectable apart from the CloudNativePG work. ## What moved | New home | Was | Callers | |---|---|---| | `internal/readscope.NoNamespaceAccess` | `noNamespaceAccess` in `server.go` and a copy in `internal/upgrade/evidence.go` | Every namespaced REST read gate | | `internal/readscope.SelectNamespaces` | `intersectNamespaces` (search) and `intersectAllowedNamespaces` (MCP) | Search, MCP, capacity, server namespace selection | | `internal/readscope.ListPodsScoped` | `listPodsScoped` in `node_metrics.go` | Vitals, dashboard, capacity, CNPG workspace | | `internal/imageutil.ImageTag` | `imageTag` in `applications.go` | Applications | | `pkg/urlutil.NormalizeOrigin` | `prom.NormalizeOrigin`, re-wrapped as `normalizeOrigin` in `internal/server`; both wrappers are gone | Cross-site origin checks, CORS, Prometheus redirects and connections, Argo CD and Kubecost connection checks | | `topology.ManagedByFromMeta` (exported) | `detectManagedByFromMeta` | Topology | ## Reviewing Each call-site change is a rename to the shared helper. Semantics to check are unchanged: a `nil` namespace list means all namespaces, a non-nil empty list means no access, and `SelectNamespaces` with an empty request returns the allowed set. `internal/readscope/scope_test.go` pins those cases. ## Verification - `go build ./...` (root and `pkg` modules) - `go test ./internal/readscope/... ./internal/upgrade/... ./internal/mcp/... ./internal/server/...` - `go test ./urlutil/... ./prom/... ./topology/... ./resourcecontext/... ./subject/...` in `pkg` (`urlutil` gets a table test pinning the origin key) - `go test ./internal/app/... ./internal/connections/...` ## Follow-up #1922 merges `main` after this lands; these files then drop out of its diff. This replaces the closed #2102.
Conflicts came only from the helper moves this branch also carried. Non-CNPG files take main's version; the four CNPG server files keep this branch's, which already call the shared helpers.
Adds two light-mode screenshots to the CloudNativePG section of integrations.md, which syncs to the public docs, and rewrites its summary paragraph for readers rather than implementers.
Bugbot needs on-demand usage enabledBugbot uses usage-based billing for this team and requires on-demand usage to be enabled. A team admin can enable on-demand usage in the Cursor dashboard. |
The docs sync copies images into one flat folder by file name, so the names need to be unique across integrations.
Builds on the CloudNativePG fleet workspace merged in #1921. Operators can move from an affected PostgreSQL Cluster to deciding evidence, review a bounded intervention, and follow what the controller or database actually observed.
User flow
CNPG stays under Resources. Fleet views cover Clusters, Backups, Declarations, Pooling and Operator. The exact CNPG Cluster kind list uses the fleet presentation without changing its existing URL or drawer navigation; Cluster APIâs colliding kind remains a generic table. Sidebar destination badges count affected clusters; outside the CloudNativePG views the Clusters badge reads the cheap workspace data only, so it is a lower bound (disk use and replication lag are counted on the views).
Cluster tabs are organized by task: Overview, Replication, Storage, Performance, Backups, Activity, Logs and Configuration/YAML. Serving availability, controller phase, operator health, backup success and restore validation remain separate facts. Unknown, denied and partial evidence never becomes zero, healthy or an empty inventory. A single instance or a Cluster with no backup destination is posture, stated without a problem mark on every surface; a replica cluster's replication never reads healthy without evidence about its upstream feed.
Architecture and dependency boundary
This branch is based on current main and does not require #2042 or the alternative foundation #2124. Dependency capture and shared UI extractions from #2103/#2104 are already on main.
internal/cnpg/read_scope.goowns the small CNPG collector assessment, using existing cache readiness and namespace APIs. It performs no authorization, API calls or waiting. The HTTP adapter supplies the caller-authorized target; CNPG preserves useful partial rows and distinguishes denied, uncached, syncing and failed reads. Instance and Pooler inventories require all typed sources before reporting absence. A missing cache cannot establish completeness; configured exclusions remain distinguishable before sync.There is no new public cache assessment API, generic coverage wire schema, MCP response change or Findings change in this PR. CNPG keeps its existing coverage/status response structure, including the additive
uncachedstate and optionaluncachedNamespaces. Newer UI continues to accept older Radar CNPG responses; new endpoints remain capability-gated. Moving to a shared assessment later can replace this local implementation without changing the CNPG callers or response contract.The shared helpers this branch uses (
internal/readscopenamespace selection and scoped Pod listing,internal/imageutil,pkg/urlutilorigin normalization,topology.ManagedByFromMeta) landed separately in #2143. This PR does not implement a competing collector assessment. Capacity/Vitals retain current-main collector behavior.internal/cnpgowns runtime/storage/HA/history/inspection/report orchestration, preflight and conditional actions.internal/serveradapts HTTP and caller identity.internal/integrationcarries domain coverage, bounded fan-out and reviewed-action errors; pure CNPG interpretation lives in the sharedpkgmodule. Hosts own fetching, navigation and workflow state. The workspace shares one dynamic-kind wait budget; other read callbacks retain their bounded budgets.Correctness boundaries
Writes bind reviewed context, object UID and relevant facts; resourceVersion conflicts refuse stale changes. Rolling restart, Pooler pause/resume and ScheduledBackup suspend/resume also bind the reviewed generation. Restart-instance, fencing and destroy refuse the target of an in-flight switchover or failover, restart-instance and fencing refuse a Pod labelled primary while the Cluster status names another (fencing a named instance whose Pod cannot be read is refused too, since its role cannot be checked), and unfence refuses while a cold VolumeSnapshot backup cannot be ruled out. A 503 without a code is an uncertain outcome (a proxy may have forwarded the write); Radar's own refusals before any write carry
unavailableand stay retryable. Caller clients retain Kubernetes impersonation. Clients, cache and cancellation come from one captured connection state. Failed client construction cannot fall back to the server identity.In-flight and ambiguous results are retained outside the query cache, bound to their original API target/context, and block duplicate operational writes until inspected. Backup/recovery evidence binds Cluster incarnations, recorded archive/server identities and source declarations. Fenced-standby replacement binds Pod/PVC/Job identities and reports completed steps on partial failure. Metrics preserve stale/ambiguous evidence separately from query failure. SQL inspection is fixed, bounded and read-only; backend signals bind PID and start time. Report bundles withhold query text (including pgaudit records and deadlock DETAIL) unless opted in, and always withhold initialization and import SQL and serialized pod patches.
Validation
make tsc; all Go packages (go test ./...and thepkgmodule); k8s-ui (4,988 tests) and web (vitest run src, 2,718 tests). After merging main (Extract shared read-scope, image-tag, origin and managed-by helpers #2143's helper extraction):make tsc, and the Go tests forinternal/cnpg,internal/issues,internal/server,internal/mcp,internal/readscope,internal/integrationand the touchedpkgpackages; CI covers the rest./api/cnpg/workspace(no Prometheus-backed fleet reads).Small fixtures do not establish large-fleet performance.
Release and consumer notes
CNPGCertificateExpiring,CNPGCertificateExpired,CNPGScheduledRunNoBackup,CNPGScheduleDestinationMissing,CNPGInstanceReadinessMismatch,CNPGPrimaryLabelMismatch. CloudNativePG certificate expiry files under the existingcertificate_not_readycategory, and a scheduled run that produced no Backup files underbackup_failed. Nothing is renamed or removed.cnpgWorkspaceandcnpgProtectionSetupflags, so a cluster on an earlier Radar shows the workspace as "needs a newer Radar".cnpgGitOpsSourceandCNPGGitOpsSourceare removed (the workspace now carriesmanagedByrefs); no known consumer imports them.components/cnpgexports more.@skyhook-io/k8s-uipeer floor name that version. Today's floor,>=1.17.1, is unpublished (npm has 1.17.0) and must not be satisfied by a k8s-ui built without this branch.radar.skyhook.io/restore-validationon a restored Cluster) is formatversion: 1, and readers skip formats they do not know. The detached-PVC provenance annotation keeps its scalar UID meaning.Limits and remaining review
Known and deliberately left for follow-up:
/api/issues, Diagnose) have no persistence window, so a switchover can briefly raise a readiness mismatch.localCanIMemo, the runtime memo); one exec semaphore is shared by all users.uncachedNamespacesdoes not distinguish namespaces still syncing from ones the cache cannot read.codeas a refusal before any write. The integration guide states the rule; the client does not enforce it.ObjectStore and Cluster declarations cannot be atomically locked together; visible declarations do not prove remote archive ownership or credential validity. Cloud installations need the updated chart read grants; changing only the image does not update roles. Store/credential provisioning, plugin installation, retention migration, arbitrary SQL and durable shared operation history remain outside scope.