Reverse-specification of python-heatclient — branch stable/2026.2
Branch
stable/2026.2@88120f11830d(5.3.0-2-g88120f1) — repository https://opendev.org/openstack/python-heatclient — analysed on 2026-11-01
- Scope: the whole repository except tests, the 49 OSC commands first, then the Python API they rely on and the legacy
heatshell. The change history (step 7) was not requested, so there is no file 06. The rewrite pack is a client contract. - Dependencies read at fixed versions, from the venv used for the runs: osc-lib 4.7.0, cliff 4.16.0, keystoneauth1 5.17.0, python-openstackclient 10.3.1. They are cited with the prefixes
osc-lib:,cliff:,keystoneauth1:andopenstackclient:. Heat itself is cited asheat:, from the local clone onstable/2026.2@5872396e5; it was read, never run. - References are
file:linefrom the repository root. [observed] marks a behaviour seen by running the real code against a local stub. [inferred] marks a statement not followed to the end. - Observed behaviour says nothing about a real Heat. The stub lives on 127.0.0.1 and answers what the scripts tell it to. It shows what the client sends and what it does with those bytes. No lab, cluster or real cloud was contacted.
Files of this specification
| File | Content |
|---|---|
| 01-cli-commands_stable-2026.2.md | shared mechanics, functional description per family, and a sheet for each of the 49 commands |
| 02-errors_stable-2026.2.md | exit codes, exception classes, client-side messages, and the defects found |
| 03-interactions_stable-2026.2.md | Heat, Keystone, Swift, local files and URLs |
| 04-scenarios_stable-2026.2.md | 5 scenarios with sequence diagrams and diagnostics |
| 05-async-processing_stable-2026.2.md | no background processing; the client-side polling that exists |
| 07-rewrite-pack_stable-2026.2.md | client contract: what the commands require from Heat, test data, proof and limits |
| _verification_stable-2026.2.md | output of the reference checker |
Overview
python-heatclient has three layers:
- OSC plugin (
heatclient/osc/): 49 commands ofopenstack, loaded through entry points (pyproject.toml:42-93). - Python API (
heatclient/v1/,heatclient/common/): one manager per resource, on top of a keystoneauth adapter (heatclient/common/http.py:303). The OSC commands use it for every request. - Legacy shell (
heatclient/shell.py,heatclient/v1/shell.py): theheatcommand, 49do_*functions. They print a deprecation warning that points to theopenstackequivalent (heatclient/v1/shell.py:37-40). The OSC commands do not call this code.
Each command runs in one short-lived process: it reads local templates and environments, authenticates through Keystone, sends REST calls to the orchestration endpoint of the catalog, optionally polls events until a final status, and prints the result. It has no database, bus, cache or server.
Architecture
flowchart LR user[User or script] -->|argv, env, stdin| osc[openstack process] user -->|argv, env| legacy[heat process, deprecated] files[Templates, environments, adopt files: file:// or URL] -->|urllib| osc osc -->|entry points| plugin[heatclient.osc.v1 commands] legacy --> lshell[heatclient.v1.shell do_* functions] plugin --> mgr[heatclient.v1 managers] lshell --> mgr mgr --> http[SessionClient, keystoneauth adapter] http -->|HTTPS, X-Auth-Token| heat[Heat API /v1/tenant] osc -->|HTTPS, password or token| ks[Keystone] lshell -.->|TempURL, deployment-create only| swift[Swift]
Processes
| Process | Entry point | Role | Listens to / consumes |
|---|---|---|---|
openstack (python-openstackclient) | pyproject.toml:42-43 → heatclient/osc/plugin.py:30, commands at pyproject.toml:45-93 | loads the plugin and its 49 commands | argv, environment, stdin; listens on nothing |
heat (legacy, deprecated) | pyproject.toml:40 → heatclient/shell.py:599 | 49 subcommands in heatclient/v1/shell.py | argv, environment; listens on nothing |
External dependencies
| System | Protocol | Library | Configuration | Usage |
|---|---|---|---|---|
| Heat | HTTPS REST | keystoneauth1 through heatclient/common/http.py:303 | catalog, service_type=orchestration (heatclient/osc/plugin.py:42-43) | every command |
| Keystone | HTTPS REST | keystoneauth1; python-openstackclient for stack list --project | --os-auth-url, clouds.yaml | token, catalog, project lookup |
| Swift | HTTPS REST | python-swiftclient (heatclient/common/deployment_utils.py:16) | catalog, object-store | TempURL signal of a deployment; reachable from heat, not from openstack (defect 1) |
| Local files and URLs | file://, http(s):// | urllib.request (heatclient/common/template_utils.py:70) | command arguments, and get_file / type in templates | templates, environments, parameter files |
Key figures
| Item | Count |
|---|---|
| OSC commands | 49 in 1 group (openstack.orchestration.v1), 162 arguments, 0 import failures |
| Formatter plugins | 2 extra namespaces: log for events, dot for resources (pyproject.toml:95-109) |
| Microversions | none: Heat has no microversion, and the client does not negotiate |
| API operations in the surface | 57; 54 sent during the probes, the other 3 in scenarios only |
| Commands sending at least one request | 45 of 49 with the minimal arguments, in both probe modes; 49 of 49 with the 59 option variants |
| Exception classes defined by the project | 29 in the inventory, of which the commands reach about ten (heatclient/exc.py:23-201) |
| Client-side messages reachable from a command | 45 templates at 73 raise sites |
| OSC unit tests | 239 of 239 pass, which give 230 vectors, identical over 2 runs |
| All unit tests | 913 run, 17 failures and 3 errors, all outside the OSC tests; see Blind spots |
| Scenarios with hand-written answers | 74, recorded in _rewrite/scenarios_stable-2026.2.json |
| Defects | 13 groups in 02 § 4; reproduced on the stub, and the parts only read in the code are marked |
Points of attention
- **
openstack software deployment createcrashes with its default options [observed]**:'NoneType' object has no attribute 'get_token', before any request. The plugin gives the HTTP client no auth plugin (heatclient/osc/plugin.py:41-43), while the default signal transport needs one (heatclient/common/deployment_utils.py:135). Unit tests mock this function (heatclient/tests/unit/osc/v1/test_software_deployment.py:120).heat deployment-createworks on the same stub [observed].--signal-transport NO_SIGNALavoids it. - **
stack list --longasks for every project [observed]**: it sendsglobal_tenant=True(heatclient/osc/v1/stack.py:674), the opposite of what its help says. Heat's default policy denies this to everybody (heat:heat/policies/stacks.py:283-284), so the plain--longshould get a 403 on a default deployment [inferred: read in Heat, not run]. - **
stack hook poll <uuid>crashes [observed]**:list index out of range(heatclient/common/event_utils.py:47), although the help says "name or ID". It needs the name. - The client parses event texts: hook detection compares
resource_status_reasonwith literal strings such asStack CREATE started(heatclient/common/event_utils.py:26-37). A rewrite of Heat must keep these texts. - The name lookup is a hard requirement: every stack sub-resource goes through
GET /stacks/{name}answering 302 with aLocationholding/stacks/(heatclient/v1/stacks.py:111-116). Anything else ends inLocation not returned with redirectorlist index out of range[observed]. - The Keystone token can reach a host named by a template [observed]: when
-t <url>fails anonymously, the client retries with the token, and then fetches everyget_fileof that template with the token, wherever it points (heatclient/common/http.py:43-48,heatclient/common/template_utils.py:163-164). See 02 § 4, defect 13. - A missing local template is asked from Heat [observed]:
-t absent.yamlsendsGET <endpoint>/absent.yamlwith the token (heatclient/common/template_utils.py:48-53). - **
stack deletedoes not ask for confirmation when stdin is not a terminal** (heatclient/osc/v1/stack.py:753), which suits scripts and surprises people piping into it. - **
--waithas no time limit**: the polling loop ends only on a final status or an error (heatclient/common/event_utils.py:205-240). - **Commands returning a value from
command.Command: none.** The 11 commands derived from it return nothing, or theNoneof a helper, so the exit-code defect found in python-openstackclient does not exist here. Checked by reading eachtake_action, and seen with exit 0 in scenarios G18, G21, G30 and D15 [observed].
Blind spots / [inferred]
- No real Heat was contacted, and functional tests (
heatclient/tests/functional/) need one, so they were not run. Response bodies in the scenarios were written from Heat's source and from the Heat reverse-spec, not captured. - Unit tests prove the argv → client call mapping only: their clients are mocks.
- 20 unit tests fail on this machine, none of them OSC command tests. 17 failures compare
file://paths and show a resolved macOS path where the test expects/tmpor/home: an effect ofPath.resolveon the macOS symlinks [observed in their messages]. The 3 errors readNonefrom a mockedurlopenkeyed on a URL; the same cause is [inferred]. They were not rerun on Linux. - Legacy shell: only
stack-listanddeployment-createwere run. Its 49 functions are listed, not specified one by one. - **
stack event list --followwas run with a fake clock** that interrupts it after 2 polls, because it never ends.time.sleepis replaced in the polling scenarios; the requested delays are recorded instead. - Not exercised: real TLS options,
osprofilerheaders (heatclient/common/http.py:177-178), and the websocket helperwait_for_events(heatclient/common/event_utils.py:243), which no OSC command calls. - Output formatting by cliff (table, JSON, YAML, CSV, value) was not specified command by command.
- Catalogue extractor limits: it missed the calls of
stack failures listbecause they go throughself.heat_client. They were added by hand in_rewrite/build_contract_stable-2026.2.py.
Source: reverse-spec-python-heatclient/00-summary_stable-2026.2.md. The _rewrite/ and _inventory/ files are not published on the site.