OpenKeel: the sovereign French Cloud solution of the inherent group, with a European reach Discover inherent

Reverse-spec · Clients

python-heatclient — Summary

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 heat shell. 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: and openstackclient:. Heat itself is cited as heat:, from the local clone on stable/2026.2 @ 5872396e5; it was read, never run.
  • References are file:line from 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

FileContent
01-cli-commands_stable-2026.2.mdshared mechanics, functional description per family, and a sheet for each of the 49 commands
02-errors_stable-2026.2.mdexit codes, exception classes, client-side messages, and the defects found
03-interactions_stable-2026.2.mdHeat, Keystone, Swift, local files and URLs
04-scenarios_stable-2026.2.md5 scenarios with sequence diagrams and diagnostics
05-async-processing_stable-2026.2.mdno background processing; the client-side polling that exists
07-rewrite-pack_stable-2026.2.mdclient contract: what the commands require from Heat, test data, proof and limits
_verification_stable-2026.2.mdoutput of the reference checker

Overview

python-heatclient has three layers:

  1. OSC plugin (heatclient/osc/): 49 commands of openstack, loaded through entry points (pyproject.toml:42-93).
  2. 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.
  3. Legacy shell (heatclient/shell.py, heatclient/v1/shell.py): the heat command, 49 do_* functions. They print a deprecation warning that points to the openstack equivalent (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

ProcessEntry pointRoleListens to / consumes
openstack (python-openstackclient)pyproject.toml:42-43 → heatclient/osc/plugin.py:30, commands at pyproject.toml:45-93loads the plugin and its 49 commandsargv, environment, stdin; listens on nothing
heat (legacy, deprecated)pyproject.toml:40 → heatclient/shell.py:59949 subcommands in heatclient/v1/shell.pyargv, environment; listens on nothing

External dependencies

SystemProtocolLibraryConfigurationUsage
HeatHTTPS RESTkeystoneauth1 through heatclient/common/http.py:303catalog, service_type=orchestration (heatclient/osc/plugin.py:42-43)every command
KeystoneHTTPS RESTkeystoneauth1; python-openstackclient for stack list --project--os-auth-url, clouds.yamltoken, catalog, project lookup
SwiftHTTPS RESTpython-swiftclient (heatclient/common/deployment_utils.py:16)catalog, object-storeTempURL signal of a deployment; reachable from heat, not from openstack (defect 1)
Local files and URLsfile://, http(s)://urllib.request (heatclient/common/template_utils.py:70)command arguments, and get_file / type in templatestemplates, environments, parameter files

Key figures

ItemCount
OSC commands49 in 1 group (openstack.orchestration.v1), 162 arguments, 0 import failures
Formatter plugins2 extra namespaces: log for events, dot for resources (pyproject.toml:95-109)
Microversionsnone: Heat has no microversion, and the client does not negotiate
API operations in the surface57; 54 sent during the probes, the other 3 in scenarios only
Commands sending at least one request45 of 49 with the minimal arguments, in both probe modes; 49 of 49 with the 59 option variants
Exception classes defined by the project29 in the inventory, of which the commands reach about ten (heatclient/exc.py:23-201)
Client-side messages reachable from a command45 templates at 73 raise sites
OSC unit tests239 of 239 pass, which give 230 vectors, identical over 2 runs
All unit tests913 run, 17 failures and 3 errors, all outside the OSC tests; see Blind spots
Scenarios with hand-written answers74, recorded in _rewrite/scenarios_stable-2026.2.json
Defects13 groups in 02 § 4; reproduced on the stub, and the parts only read in the code are marked

Points of attention

  1. **openstack software deployment create crashes 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-create works on the same stub [observed]. --signal-transport NO_SIGNAL avoids it.
  2. **stack list --long asks for every project [observed]**: it sends global_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 --long should get a 403 on a default deployment [inferred: read in Heat, not run].
  3. **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.
  4. The client parses event texts: hook detection compares resource_status_reason with literal strings such as Stack CREATE started (heatclient/common/event_utils.py:26-37). A rewrite of Heat must keep these texts.
  5. The name lookup is a hard requirement: every stack sub-resource goes through GET /stacks/{name} answering 302 with a Location holding /stacks/ (heatclient/v1/stacks.py:111-116). Anything else ends in Location not returned with redirect or list index out of range [observed].
  6. 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 every get_file of 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.
  7. A missing local template is asked from Heat [observed]: -t absent.yaml sends GET <endpoint>/absent.yaml with the token (heatclient/common/template_utils.py:48-53).
  8. **stack delete does 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.
  9. **--wait has no time limit**: the polling loop ends only on a final status or an error (heatclient/common/event_utils.py:205-240).
  10. **Commands returning a value from command.Command: none.** The 11 commands derived from it return nothing, or the None of a helper, so the exit-code defect found in python-openstackclient does not exist here. Checked by reading each take_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 /tmp or /home: an effect of Path.resolve on the macOS symlinks [observed in their messages]. The 3 errors read None from a mocked urlopen keyed on a URL; the same cause is [inferred]. They were not rerun on Linux.
  • Legacy shell: only stack-list and deployment-create were run. Its 49 functions are listed, not specified one by one.
  • **stack event list --follow was run with a fake clock** that interrupts it after 2 polls, because it never ends. time.sleep is replaced in the polling scenarios; the requested delays are recorded instead.
  • Not exercised: real TLS options, osprofiler headers (heatclient/common/http.py:177-178), and the websocket helper wait_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 list because they go through self.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.