Reverse-specification of python-openstackclient — branch stable/2026.2
Branch
stable/2026.2@8bb083b83829(tag10.3.1) — repository https://opendev.org/openstack/python-openstackclient — analysed on 2026-11-01
- Scope: all non-test code of the repository, all command groups. The change history (step 7) was not requested.
- Dependencies read at fixed versions, from the venv built with
requirements/upper-constraints.txt: osc-lib 4.7.0, cliff 4.16.0, openstacksdk 4.20.0, keystoneauth1 5.17.0. They are cited with the prefixesosc-lib:,cliff:andopenstacksdk:. - References are
file:linefrom the repository root. [inferred] marks a statement not followed to the end. [observed] marks a behaviour seen by running the real CLI against the local stub described in 07; the stub is not a real service. [lab] marks a behaviour seen against the OpenKeel lab.
Files of this specification
| File | Content |
|---|---|
| 01-cli-commands_stable-2026.2.md | shared mechanics, functional description per service, and the 799 commands with purpose, class, required arguments, microversion gates and observed requests |
| 02-errors_stable-2026.2.md | exit codes, how failures reach the user, and the 420 error messages with their raise sites |
| 03-interactions_stable-2026.2.md | Keystone, the service APIs, microversion negotiation, the three client stacks, local files and stdin |
| 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 and threads that exist |
| 07-rewrite-pack_stable-2026.2.md | client contract: what the CLI requires from the services, test vectors, proof and limits |
| _verification_stable-2026.2.md | output of the reference checker |
File 06 (change history) is absent because it was not requested.
Overview
python-openstackclient (OSC) provides the single openstack command. It runs in one short-lived process per invocation:
- it parses the global options and reads
clouds.yamlandOS_*variables; - it loads commands from Python entry points;
- it authenticates against Keystone, only when the command needs it;
- it sends REST calls through one of three client stacks;
- it prints the result through cliff formatters.
It has no database, message bus, cache, server or background process. Service plugins extend it through entry points.
Architecture
flowchart LR user[User or script] -->|argv, env, stdin| cli[openstack process] cfg[clouds.yaml, secure.yaml, OS_* variables] --> cli cli --> sdk[openstacksdk Proxy] cli --> ksc[python-keystoneclient] cli --> api[openstackclient.api: object_store_v1, compute_v2] sdk --> ksa[keystoneauth1 session] ksc --> ksa api --> ksa ksa -->|HTTPS, X-Auth-Token| ks[Keystone] ksa -->|HTTPS| nova[Nova] ksa -->|HTTPS| neutron[Neutron] ksa -->|HTTPS| glance[Glance] ksa -->|HTTPS| cinder[Cinder] ksa -->|HTTPS| swift[Swift] plugins[Plugins: manilaclient, heatclient, osc-placement...] -.->|entry points| cli
Processes
| Process | Entry point | Role | Listens to / consumes |
|---|---|---|---|
openstack | pyproject.toml:33 → openstackclient/shell.py:212 | run one command, or an interactive shell when no command is given | argv, environment, stdin; listens on nothing |
External dependencies
| System | Protocol | Library | Configuration | Usage |
|---|---|---|---|---|
| Keystone | HTTPS REST | keystoneauth1 | --os-auth-url, clouds.yaml | token, service catalog, identity commands |
| Nova, Neutron, Glance, Cinder | HTTPS REST | openstacksdk | Keystone catalog | commands of each service |
| Swift | HTTPS REST | openstackclient/api/object_store_v1.py:35 | Keystone catalog | object, container, object store account |
| Manila and other services | HTTPS REST | their plugin | Keystone catalog | only when a plugin is installed |
Key figures
| Item | Count |
|---|---|
Commands declared in pyproject.toml | 799 in 17 groups, 662 loaded with the default API versions, 3,471 arguments in total |
| Client calls found in the code | 2,140, of which 1,920 resolved to a REST route |
| API operations in the surface | 815: 478 observed on the wire, 337 only read in the source |
| Commands sending at least one request to the stub | 615 of 661 probed |
| Microversion gates | 206, on 55 compute and 29 volume microversions |
| Exception classes defined by the project | 0; it raises osc_lib ones, 420 distinct messages at 538 sites |
| Unit tests | 3,250 pass, which give 3,109 vectors |
| OpenKeel lab, 657 commands probed | 219 succeeded, 28 got a 501, 27 hit a route absent from Keystone, no other 5xx; the rest is mostly inconclusive |
Points of attention
- Two commands exit in error on success [observed]:
federation protocol setandblock storage cluster setderive fromcommand.Commandbut return a table (openstackclient/identity/v3/federation_protocol.py:179,openstackclient/volume/v3/block_storage_cluster.py:188). cliff treats any return value as the exit code (cliff:cliff/command.py:196). The update is applied, then the process prints the tuple on stderr and exits 1. A script will retry or fail on a successful change. - **
block storage cleanupwithout filters crashes [observed]**: it drops unset filters (openstackclient/volume/v3/block_storage_cleanup.py:167), and the SDK method then raisesTypeErrorfor whichever ofbinary,hostandcluster_nameis missing. With all three, it does sendPUT /workers/cleanup. The unit tests mock the SDK and do not see it. - **
catalog showexits 0 when the service is unknown [observed]**: it logsservice %s not foundand returns an empty table (openstackclient/identity/v3/catalog.py:108). - **
mapping setwithout--rulesshows a raw Python error [observed]**:expected str, bytes or os.PathLike object, not NoneType, fromopenstackclient/identity/v3/mapping.py:236. - The default API version is not the microversion sent [observed]:
DEFAULT_API_VERSIONfor compute is2.1(openstackclient/compute/client.py:26), yet without--os-compute-api-versionthe SDK negotiates per resource: 2.100 on servers, 2.81 on aggregates, 2.61 on flavors. With the option, every call uses that value. A service must therefore support both the latest microversions and the old ones. - Gates depend on the negotiated version [observed]: with the default compute version set explicitly to 2.1,
--taggives--os-compute-api-version 2.49 or greater is required to support the --tag option. - A list response without its key shows a bare key name [observed]: a
200 {}onGET /servers/detailprints'servers'and exits 1. A compatible service must always send the collection key, even when it is empty. - **
sharecommands in this repository are dead code at run time [observed]**:openstackclient.share.v2is inIGNORED_MODULES(openstackclient/shell.py:34), andopenstackclient/share/client.pyis not inopenstack.cli.base.openstack share listis served by python-manilaclient (manilaclient.osc.v2.share:ListShare), which is a required dependency (requirements.txt:12). - Identity v2 and image v1 commands are reachable only by option: with a v3-only catalog,
--os-identity-api-version 2givesCould not find requested endpoint in Service Catalog.[observed]. - **
project cleanupcan hide failures [observed]**: against the stub,--dry-runexits 0 although the SDK cleanup thread logged aNotFoundExceptiononGET /volume/v3/backups. An empty listing does not prove that the project is empty. - Secrets on the command line and on stdout:
--os-passwordand--passwordare visible in the process list.token issueandapplication credential createprint secrets by design (openstackclient/identity/v3/token.py:187,openstackclient/identity/v3/application_credential.py:73). Debug logs mask passwords throughstrutils.mask_password(osc-lib:osc_lib/shell.py:435). - No project-level timeout:
api_timeoutis not set by default (openstacksdk:openstack/config/cloud_region.py:997), so a stuck service can block a command.--waitloops only stop on a target or error status [inferred for the absence of a limit, fromosc-lib:osc_lib/utils/__init__.py:779]. - Unit tests cannot catch these: they use mocks of the SDK proxies (
openstackclient/tests/unit/compute/v2/fakes.py:49), which is why items 1 to 4 pass their tests.
Blind spots / [inferred]
- No original OpenStack service was contacted. Functional tests need devstack, so they were not run. The CLI was run against the local stub and against the OpenKeel lab (Go services), so nothing here compares OpenKeel with the Python services.
- Probe limits: synthetic arguments often stop a command at a missing parent resource, so many multi-step commands are known statically only. See the compatibility table.
- 46 of the 661 probed commands sent no request, mostly because synthetic arguments failed client-side validation or a file was missing. Their routes come from the source only.
- **
consumer *,access token createand similar OAuth1 commands** needoauthlib, absent from the venv [observed]:To use 'consumers' oauthlib must be installed. - **
server ssh** was never run, because it executes the localsshbinary. - The 137 non-default commands (
identity.v2,image.v1,volume.v2,share.v2) were catalogued and analysed statically, not probed. - Static routes are approximate: the SDK fallback of
find_*was read asGET /resource?name=, while the wire showsGET /servers/detail?name=on compute and block storage. For routes, the observed value wins. - Plugins are out of scope here; each has its own specification: osc-placement, barbican, designate, heat, ironic, manila, masakari, neutron, octavia.
- Output formatting by cliff (table, JSON, YAML, CSV, value) was not specified command by command.
Source: reverse-spec-python-openstackclient/00-summary_stable-2026.2.md. The _rewrite/ and _inventory/ files are not published on the site.