Docs · Tower
Architecture
Tower is built as a Backstage frontend module consisting of tab-based UI, data-fetching hooks, and utility components for cloud-native platform visibility.
Module Structure
plugin/src/tower/
├── index.tsx # Route entry point
├── useReleaseContext.ts # Release data context
├── useFleetEnvironments.tsx # Environment state management
├── useConfigData.ts # Config schema and validation
│
├── [Tabs] — Primary Views
│ ├── Overview/ # Fleet status, recent activity, metrics
│ ├── Releases/ # Release records, promotion workflows
│ ├── PRs/ # Pull request CI/CD status
│ ├── Pipelines/ # Tekton pipeline execution
│ └── Config/ # Cluster config inspection
│
├── [Components] — Shared UI
│ ├── PipelineFlow.tsx # DAG-based pipeline visualization
│ ├── ReleaseRecordDetail.tsx # Release record view with promotion
│ ├── ArgoCD*.tsx # ArgoCD integration components
│ ├── EnvPicker.tsx # Environment selector
│ └── ...
│
├── [Utilities]
│ ├── schemaValidate.ts # YAML schema validation
│ ├── YamlBlockEditor.tsx # YAML editing UI
│ ├── PodLogsView.tsx # Pod log streaming
│ └── nicknameCache.ts # Cache for Tekton task names
└── [Hooks]
├── useSlos.ts # SLO tracking
├── useReleaseRecordPersistence.ts # Release persistence
└── useConfigData.ts # Config queries
Data Flow
Release Records
Backstage backend /api/glidepath/release-* (GitHub, release-tracking ConfigMaps)
↓
useReleaseRecords.ts, releaseFlow.ts (draft, mode, sign-off, planned), useReleaseNames.ts
↓
ReleaseRecordPanel: mode bar, Next release card + draft editor, record list, Compare, Changelog
↓
ReleaseRecordDetail: masthead, sign-off, outcome, evidence, after-release notes (saved as a PR)
The backend owns every write and two scheduled tasks (sign-off poll every 2 minutes, record poll hourly) that open record and outcome PRs, set sign-off statuses and publish opted-in GitHub Releases. See Release records.
Cluster State
Kubernetes API
↓
useFleetEnvironments.tsx (cache environments, watch events)
↓
Overview / Config tabs
↓
Live status, SLO metrics, activity feeds
CI/CD Pipelines
Backstage backend /api/glidepath/live-stream (live runs, pushed: a snapshot then each change; /live-runs polled
every 6 s when the stream can't open) + /api/glidepath/pipeline-history (archived runs)
↓ (both answered by glidepath-api)
PipelineRunList (list runs; History range Live / 24h / 7d / 30d)
↓
PipelineDag (render DAG)
↓
TaskRunLogConsole (live: /live-log, the step's pod log; archived: /pipeline-history/log, Tekton Results logs)
Every CI/CD answer comes from glidepath-api, which the backend calls through the control-plane cluster’s API server
(packages/backend/src/glidepathApi.ts in the backstage repo); an API error or timeout goes back to the person, with
no fallback. Live runs are glidepath-api’s listLiveRuns (the app’s -cicd PipelineRuns and TaskRuns now) and
getLiveLog; the Ops Wall’s fleet views use /fleet/live, /fleet/live/taskruns and /fleet/history, and the Ops
Wall and the drift view read the fleet’s release records and their ReleaseStalled/ReleaseDrift Events from
/fleet/releases and /fleet/release-events. Until 2026-10-30 the browser read all of these from the cluster through
the Kubernetes proxy itself.
Tekton deletes a finished run from the cluster about an hour after it completes. Older runs come from Tekton Results
(30-day retention) through glidepath-api’s listRuns, getRun and getRunLog; until P6 (2026-10-30) the backend
queried and cached Results itself. The Release Record’s “What was built” and the cloud deployments view read the same
endpoint. History never reaches back past the creation of the app’s app-<app>-cicd namespace: Results keys runs by
namespace, so an app deleted and created again under the same name would otherwise show the old app’s runs, and its
cloud environments the old app’s deploys (smoke-ecs, 2026-10-17).
Key Design Decisions
Reads in the browser, writes in the backend
Tower reads clusters through Backstage’s Kubernetes proxy (read only), Argo CD through the backend, and CI/CD (runs,
logs, release state, image evidence, tests) from glidepath-api through the backend. Everything that changes something
(Argo CD actions, release PRs, config PRs, release records, Tekton re-runs) goes through routes in Backstage’s backend
(packages/backend/src in the backstage repo), which check Tower’s permissions and audit the write. Tekton writes
(Re-run, Cancel, Flight promote and rollback runs) are then glidepath-api’s: the backend asks it, and it creates the
run. The browser never holds a cluster, Argo CD or GitHub credential. See Security model.
Release Records as Git State
Release records (what changed, what was built, sign-offs, notes, outcome) are persisted as files in git, through PRs, not in a database; the next release’s draft and the release modes are files too. This ensures:
- Auditability (full git history)
- No additional persistence layer
- GitOps alignment with infrastructure management
Context + Hooks over Redux
State management uses React Context (for release data) and custom hooks (for cluster state), not Redux. Justification:
- Simpler onboarding
- Avoids action/reducer boilerplate
- Still supports derived state and memoization
Integration Points
ArgoCD
- Fetch Application status, sync state, last sync time
- Refresh and hard refresh; Sync, which follows the Application’s own prune policy (environment Applications
auto-prune); Force sync, confirmed with the out-of-sync resources it replaces (Ground owners, Flight admins);
Terminate a running sync (owners). The backend’s
/argo/capabilitiessays which the user may run; the write routes decide either way. - An environment with nothing live still has its Application (
<app>-<env>), so these stay available after its workload is turned off. - Application-to-namespace mapping for environment visibility
Tekton (through glidepath-api)
- List TaskRuns and PipelineRuns, live and archived
- Read a running step’s pod log for live task debugging
- Render pipeline DAG from Run status
- Re-run and cancel, which glidepath-api performs
External Secrets Operator
- Show secret sync status, last fetch time
- Detect stale credentials
Crossplane
- Show composed resources per application
- Track readiness, synced condition
- Alert on composition errors
Performance Considerations
Caching Strategy
- Environments cached with 30-second TTL
- Release records cached during session
- Watch connections for live updates where available
Pagination
- Release records paginated (default 20 per page)
- Pipeline runs use cursor pagination
Lazy Loading
- Tabs load data on-demand, not all at once
- Pod logs loaded on-request
- Config inspection deferred until user navigates to tab