Reverse-specification of skyline-console — branch stable/2026.2
- Repository: https://opendev.org/openstack/skyline-console — specified branch:
stable/2026.2@806d200392ee(9.0.0-1-g806d2003) — analysed on: 2026-10-29 - Scope: the whole single-page application: UI modules, pages and actions; outbound REST calls with microversions; error handling; polling; build, packaging and deployment on Kubernetes. Rewrite pack (step 8) delivered as a deployment and connection contract: in OpenKeel the console is kept as is and is not rewritten in Go. Change history (step 7) not requested.
- Other repositories cited:
skyline-apiserverstable/2026.2@4af72bfd4577(prefixskyline-apiserver:),microversion-parse@77ea32a(prefixmicroversion-parse:),cinderstable/2026.2@c117f9cf0(prefixcinder:),novastable/2026.2@8df1922926(prefixnova:). Companion report: [../reverse-spec-skyline-apiserver/](../reverse-spec-skyline-apiserver/00-summary_stable-2026.2.md).
| File | Content |
|---|---|
| 01-ui-modules_stable-2026.2.md | modules, routes, pages, actions and the operations they trigger (replaces the REST API of a server) |
| 02-errors_stable-2026.2.md | how errors are handled and shown; error bodies the console parses |
| 03-interactions_stable-2026.2.md | request layer, URL and microversion rules, calls to skyline-apiserver and to each OpenStack API, browser-side state |
| 04-scenarios_stable-2026.2.md | login, instance creation, state action, volume extension, project switch, session expiry |
| 05-async-processing_stable-2026.2.md | auto refresh, transition polling, session restore |
| 07-rewrite-pack_stable-2026.2.md | build, Kubernetes deployment, connection contract, requirements on the OpenStack APIs, smoke test, checklist |
| _rewrite/ui-catalogue_stable-2026.2.md, _rewrite/outbound-calls_stable-2026.2.md | generated exhaustive tables (every route, page, action and outbound call with file:line) |
Overview
skyline-console is the React 16 / MobX / Ant Design web UI of OpenStack Skyline. It is a pure client: no server process, no database, no bus. It is built with webpack into static files (skyline_console/static/) and shipped as a Python package. skyline-apiserver's container installs that package and serves the files with nginx (skyline-apiserver:container/Dockerfile:23). In the browser it talks only to its own origin:
/api/openstack/skyline/api/v1/…for skyline-apiserver: session, profile, UI policies, aggregated lists, Prometheus, settings;/api/openstack/<region>/<service>/…for the OpenStack APIs: Nova, Neutron, Cinder, Glance, Keystone, Manila, Octavia, Ironic, Heat, Trove, Zun, Magnum, Masakari, Designate, Barbican, Swift, Placement. nginx proxies these to the catalog endpoints, carrying the user's Keystone token.
The token comes from skyline-apiserver's login answer. The console keeps it in localStorage and sends it as X-Auth-Token.
Architecture
flowchart LR user[Browser: skyline-console SPA] -->|HTTPS, same origin| nginx[nginx in the skyline pod] nginx -->|/ static files, SPA fallback| static[(skyline_console/static)] nginx -->|/api/openstack/skyline/ → unix socket| api[skyline-apiserver] nginx -->|/api/openstack/regionone/<service>/<br/>X-Auth-Token + microversion headers| os[OpenStack APIs] api -->|REST| ks[Keystone and services] user -.->|noVNC / Zun console URLs, direct| consoles[console proxies]
Processes
| Process | Entry point | Role | Listens to / consumes |
|---|---|---|---|
| browser application | src/core/index.jsx:65 (session restore, then render) | the whole UI | user events; HTTP responses |
| build (not at runtime) | config/webpack.prod.js:34 | produces skyline_console/static/ | — |
| development server | config/webpack.dev.js:46, config/server.dev.js:23 | hot reload, proxies /api/ to server of config/config.yaml | 0.0.0.0:8088 (config/config.yaml:1) |
| Python package | skyline_console/__init__.py:17 | exposes static_path to skyline-apiserver's nginx generator | — |
External dependencies
| System | Protocol | Library | Configuration | Usage |
|---|---|---|---|---|
| skyline-apiserver | HTTPS (same origin) | axios 0.21.4 | fixed prefix /api/openstack/skyline/api/v1 (src/client/client/constants.js:60) | login, profile, policies, aggregated lists, Prometheus, settings |
| OpenStack APIs (17) | HTTPS through nginx | axios | profile.endpoints (src/client/client/constants.js:46) | everything else |
| Nova noVNC / serial, Zun WebSocket | HTTPS / WSS, direct | window.open, WebSocket | URLs returned by the services | consoles |
| browser storage | cookies, localStorage | — | — | session (time_expired), Keystone token, language |
Key figures
- 251 routes, 274 list/detail pages, 392 actions (95 % resolved statically to their HTTP calls).
- 18 client modules declaring 1 555 operations; 577 calls actually reached: neutron 119, manila 60, keystone 58, cinder 52, nova 50, trove 31, zun 30, ironic 27, octavia 27, skyline 24, glance 19, magnum 16, masakari 14, designate 13, swift 12, heat 11, barbican 10, placement 4.
- Microversions: Nova 2.79 (actions up to 2.93), Cinder 3.59, Placement 1.28, Ironic 1.58, Manila 2.55, Zun 1.40.
- Timers: list refresh 30 s, 10 s during transitions, stops after 20 idle refreshes; no other background work.
- Build: Node 16, 1 min 44 s, 45 files / 16 MB. Smoke test: 35 recorded requests, including a deep link through login to the first lists.
- Cluster run in kind (Keystone
stable/2026.2+ skyline-apiserver container + Neutron stand-in): Cypress login, error and identity specs, 31 of 34 tests passing. The 3 failures need Nova or Cinder, which were not deployed (07 § 6.3).
Points of attention
- Stale bundle committed in
skyline_console/static/(2022, rc1). Installing from git withoutmake packageships an old UI. The apiserver image also downloads the console's master tarball by default (skyline-apiserver:Makefile:20). Build fromstable/2026.2explicitly (07 § 2). - Same origin at the root path only: absolute paths everywhere (
src/client/client/request.js:159,config/webpack.prod.js:46). No sub-path deployment, no separate API host. - Microversion headers are chosen by substring of the URL (
src/client/client/constants.js:150). They depend on the nginx location names, which come from skyline-apiserver'sservice_mapping. A non-default mapping silently drops the headers (03 § 2). - Two case-variant microversion headers are merged by the browser into
compute 2.79, compute 2.42(recorded). Nova takes the last value, Cinder the first: Cinder never receives the 3.60 (os-extend) and 3.29 (/messages) the console intends (cinder:cinder/api/openstack/wsgi.py:292). The Go rewrites of Nova and Cinder must reproduce these two parsings. - Any 401 logs the user out, from any service (
src/client/client/request.js:42). A Go service answering 401 for a policy refusal would log users out. - The token is readable by page scripts (
localStorage,src/stores/root.js:117) and stays usable locally for 10 days, bounded by thetime_expiredcookie. It is compared with the browser clock, so clock skew matters. - UI policy defaults to allow for rules missing from skyline-apiserver's list, and to deny everything when the list is empty (
src/resources/skyline/policy.js:141,src/resources/skyline/policy.js:176). - Consoles bypass Skyline: the noVNC and Zun URLs must be publicly reachable (
src/pages/compute/containers/Instance/actions/Console.jsx:69). - Polling load: each open list re-reads every 30 s. For instances and volumes, each refresh is a skyline aggregation that fans out to several services, and admin lists add per-project Keystone calls (05).
- Nova error body parsed:
forbidden.messagecontainingQuota exceeded(src/pages/compute/containers/Instance/actions/StepCreate/index.jsx:794). - Found in the kind run (07 § 3.1):
- No login without Neutron. Login answers 401 "Endpoint not found" when the catalog has no
networkendpoint (skyline-apiserver:skyline_apiserver/client/openstack/system.py:79). - Admin pages need a system-scope role. Administrator pages require a system-scope
adminorreaderrole (src/stores/root.js:155). - The pod crash-loops while Keystone is down, because nginx is generated from the catalog at start.
- **A probe on
/is too early.** It reports ready while the API still returns 502. - Upstream race on admin deep links. After login, an administrator deep link lands on the overview, because the system-roles request is not awaited (
src/stores/root.js:195).
- No login without Neutron. Login answers 401 "Endpoint not found" when the catalog has no
Blind spots / [inferred]
- The extraction is static (AST). Calls built dynamically or behind conditions are approximated, and 18 actions have no resolved call; the generated catalogue is an upper bound.
- The real skyline-apiserver container ran in kind against Keystone and a Neutron stand-in only. Compute, network and storage screens, and microversions through the real nginx, were checked only against the recording stand-in of the smoke test. Cypress ran the login, error and identity specs, not the compute, network and storage ones. No OpenKeel Go service was in the cluster.
- Header merging was observed in Chromium only; other browsers [inferred] behave the same (Fetch specification).
- Pages of optional services (Trove, Zun, Magnum, Masakari, Designate, Barbican) were catalogued but not described in sheets.
- The development server, i18n and theming are out of scope.
Tool changes made for this report
~/.claude/skills/generate-reverse-spec/scripts/inventory.pygained small, generic JS support:.jsx/.tsxfiles, minified bundles skipped, SPA routes (ui_route), client timers (setTimeout) andXMLHttpRequest/WebSocket/EventSourceoccurrences, and React/Vue/Angular/MobX detection. Files analysed went from 271 to 1 223.- Console-specific tools are kept with the report in
_rewrite/tools/(extract_console.js,build_catalogue.py,smoke_server.py);rewrite_pack.pyandconformance_capture.pyimport a Python project and do not apply to a JavaScript client.
Source: reverse-spec-skyline-console/00-summary_stable-2026.2.md. The _rewrite/ and _inventory/ files are not published on the site.