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

Reverse-spec · Clients

python-barbicanclient — Rewrite pack

Client contract (rewrite pack) — python-barbicanclient — branch stable/2026.2

Branch stable/2026.2 @ 8162006e8677 — repository https://opendev.org/openstack/python-barbicanclient — analysed on 2026-11-01

This pack serves two purposes:

  • Keep this client working against a re-implemented key manager, by stating what it requires from the service.
  • Re-implement the commands, with the machine-readable catalogue.

No replay against the real Barbican was done. The client ran against a local stub only. A stub answering what it is told cannot show that a service conforms: see § 5.

1. Contents of _rewrite/

FileContentUse
[cli-commands_stable-2026.2.json](_rewrite/cli-commands_stable-2026.2.json)the 23 commands: class, purpose, each of the 78 arguments (names, type, default, choices, required, exclusive groups, help), output kindgenerate a parser; compare argument names
[client-contract_stable-2026.2.json](_rewrite/client-contract_stable-2026.2.json)per command: observed requests, static requests with file:line and condition, exit codes on the stub; discovery; Python-API-only operationsrequirements per command
[api-surface_stable-2026.2.md](_rewrite/api-surface_stable-2026.2.md)the 32 operations, with source, headers, service handler, unit test count, and the 26 service operations never calledgap analysis of a service
[unit-vectors_stable-2026.2.json](_rewrite/unit-vectors_stable-2026.2.json)213 tests with their 384 requests: method, path, query, headers, body, mocked status, normalised ({uuid}). report.command_classes_run_by_tests is emptywhat the Python API sends for each case
[wire-probe-404_stable-2026.2.json](_rewrite/wire-probe-404_stable-2026.2.json), [wire-probe-200_stable-2026.2.json](_rewrite/wire-probe-200_stable-2026.2.json)54 runs each: argv, exit, stdout, stderr, identity, discovery and all requests, with every route answering 404 (Barbican error shape) or {}wire reference
[wire-probe-shape_stable-2026.2.json](_rewrite/wire-probe-shape_stable-2026.2.json)the same, with bodies written from the Barbican sourceclient output on plausible answers; not service behaviour
[defect-repro_stable-2026.2.json](_rewrite/defect-repro_stable-2026.2.json)the 86 cases cited in 02, with programmed routes, exit, stderr and requestsevidence; turn them into tests
ScriptRoleChange from the shared tool
[extract_plugin_stable-2026.2.py](_rewrite/extract_plugin_stable-2026.2.py)imports the entry points, dumps parsersfilters calls to private attributes
[probe_plugin_stable-2026.2.py](_rewrite/probe_plugin_stable-2026.2.py)stub, and one real process per runheader-dependent version documents, per-resource URIs, option variants, shape mode, subprocess, discovery logged
[capture_plugin_stable-2026.2.py](_rewrite/capture_plugin_stable-2026.2.py)runs the unit tests with a recorderrecords at the requests-mock adapter, since there is no check_parser; blocks outbound sockets; counts take_action calls
[repro_defects_stable-2026.2.py](_rewrite/repro_defects_stable-2026.2.py)programs the stub per casenew
[build_contract_stable-2026.2.py](_rewrite/build_contract_stable-2026.2.py)merges everythinghand-read static table; checks against the Barbican route table

The generic rewrite_pack.py and conformance_capture.py were not used: they need an in-process service or functional tests, and functionaltests/ needs a live deployment (etc/functional_tests.conf:5).

Versions: python-barbicanclient 7.6.1.dev2 (setup-time version at this commit), python-openstackclient 10.3.1, osc-lib 4.7.0, cliff 4.16.0, keystoneauth1 5.17.0, requests-mock 1.12.1, Python 3.13.

To regenerate, from the report directory, with HOME set to an empty directory and OS_* unset:

python -I -B _rewrite/extract_plugin_stable-2026.2.py <repo> python-barbicanclient barbicanclient _rewrite
python -I -B _rewrite/probe_plugin_stable-2026.2.py _rewrite/cli-commands_stable-2026.2.json <dir-404> --mode 404 --repo <repo>
python -I -B _rewrite/probe_plugin_stable-2026.2.py _rewrite/cli-commands_stable-2026.2.json <dir-200> --mode 200 --repo <repo>
python -I -B _rewrite/probe_plugin_stable-2026.2.py _rewrite/cli-commands_stable-2026.2.json <dir-shape> --mode shape --repo <repo>
python -I -B _rewrite/capture_plugin_stable-2026.2.py <repo> _rewrite/cli-commands_stable-2026.2.json <run-1>/c.json <repo>/barbicanclient/tests <repo>
python -I -B _rewrite/capture_plugin_stable-2026.2.py <repo> _rewrite/cli-commands_stable-2026.2.json <run-2>/c.json <repo>/barbicanclient/tests <repo>
python -I -B _rewrite/repro_defects_stable-2026.2.py <repro.json> --repo <repo>
python -I -B _rewrite/build_contract_stable-2026.2.py . <dir-404>/wire-probe_stable-2026.2.json <dir-200>/wire-probe_stable-2026.2.json <dir-shape>/wire-probe_stable-2026.2.json <run-1>/c.json <run-2>/c.json <repro.json> <reverse-spec-barbican>/_rewrite/routes_stable-2026.2.json <generated-dir>

2. What the client requires from a key manager

These are hard requirements on any service that must work with this client unchanged. "Crash" means the client fails with a bare Python message [observed]. Every row is client behaviour; whether a real Barbican satisfies it was read, not run.

Discovery

#RequirementIf notSource
1service type key-manager in the catalog, with a public endpointCould not find requested endpoint or similarbarbicanclient/client.py:28
2GET <catalog URL> without token answers a version list; 300 is finefallback warning, then failures [observed]keystoneauth1:keystoneauth1/discover.py:1500
3GET <catalog URL>/v1 with OpenStack-API-Version: key-manager 1.1 answers {"version": {"status": "CURRENT", "min_version": "1.0", "max_version": "1.2", "links": [...]}}Invalid client version v1 if max_version is missingbarbicanclient/v1/client.py:147
4status STABLE, any case, means 1.0 only; do not send it from a 1.1+ answerconsumers refused locallybarbicanclient/v1/client.py:137
5with --endpoint, the same document at the endpoint as given, as a list {"versions": [...]} or a single versionbarbicanclient/v1/client.py:114
6the requested microversion is accepted in OpenStack-API-Version: key-manager 1.x, up to the announced maximum406 on the first real requestbarbicanclient/v1/client.py:62

Requests to accept

#RequirementSource
7POST with a trailing slash on /secrets/, /orders/, /containers/, /secrets/{id}/consumers/, /containers/{id}/consumers/; GET, PUT and DELETE without itbarbicanclient/client.py:109
8DELETE /secrets/{id}/consumers and /containers/{id}/consumers with a JSON bodybarbicanclient/v1/secrets.py:745, barbicanclient/v1/containers.py:774
9PUT /secrets/{id} with raw body and Content-Type: text/plain or application/octet-streambarbicanclient/v1/secrets.py:536
10?force=1 on DELETE /secrets/{id} as a booleanbarbicanclient/v1/secrets.py:601
11GET /secrets/{id}/payload honouring Accept, default text/plain on the CLIbarbicanclient/v1/secrets.py:319
12list parameters limit, offset, name, alg, mode, bits, secret_type, type, with alg and bits under these namesbarbicanclient/v1/secrets.py:656
13expiration as ISO 8601 with offset: 2030-01-01T00:00:00+00:00 [observed]barbicanclient/v1/secrets.py:344
14no-auth use: X-Project-Idbarbicanclient/client.py:51

Responses

#RequirementIf notSource
15**every JSON error has a top-level title**, optionally description. This includes 401s. A service in front of which keystonemiddleware answers has to live with 'title'; a rewrite should send title at the top level in its 401 body, in addition to errorcrash D1barbicanclient/client.py:141
16the secret deletion refusal holds exactly Secret cannot be deleted as it has consumers. in title: description, with a 4xxplain HTTPClientErrorbarbicanclient/v1/secrets.py:606
17secret documents hold only name, expiration, algorithm, bit_length, mode, payload, payload_content_type, payload_content_encoding, secret_ref, created, updated, content_types, status, secret_type, creator_id, consumerscrash D5barbicanclient/v1/secrets.py:129
18consumer entries hold only service, resource_type, resource_id, created, updated, statuscrash D5barbicanclient/v1/secrets.py:70
19order documents hold type, meta, and only status, created, updated, order_ref, error_status_code, error_reason, sub_status, sub_status_message, creator_id, plus secret_ref on key or container_ref on asymmetric (not the other)crash D5barbicanclient/v1/orders.py:229, barbicanclient/v1/orders.py:272
20meta of a key order holds only name, algorithm, mode, bit_length, expiration, payload_content_type; of asymmetric only those plus passphrase, pass_phraseInvalid KeyOrder meta field, or crashbarbicanclient/v1/orders.py:226, barbicanclient/v1/orders.py:356
21order type is key or asymmetric, in any caseD6barbicanclient/v1/orders.py:344
22container documents hold type in generic, rsa, certificate, any case, and a secret_refs list of {name, secret_ref}; other fields are ignoredcrash D21barbicanclient/v1/containers.py:549, barbicanclient/v1/containers.py:612
23POST answers a JSON object holding secret_ref, order_ref or container_ref. For containers, a non-empty body without the key is KeyErrorD15, D22barbicanclient/v1/secrets.py:394, barbicanclient/v1/orders.py:210, barbicanclient/v1/containers.py:208
24PUT …/acl answers a JSON object; acl_ref is read but not requiredD15barbicanclient/v1/acls.py:328
25GET …/acl answers operations as keys, each an object with optional project-access, users, created, updated. The service answers a default read for no ACL (barbican:barbican/api/controllers/acls.py:200); an empty object breaks acl user removeACL data for secret is not provided.barbicanclient/v1/acls.py:356
26POST /secrets/{id}/consumers/ and POST /containers/{id}/consumers/ answer the parent documentcrashbarbicanclient/v1/secrets.py:717, barbicanclient/v1/containers.py:752
27dates in created, updated, expiration parse with oslo's parse_isotime; a bad one is ValueError at constructionbare messagebarbicanclient/v1/secrets.py:445
28text/plain payloads are valid UTF-8D10barbicanclient/v1/secrets.py:325
29secret_ref and similar end with the UUIDincorrectly specifiedbarbicanclient/base.py:45
30the name of a secret should be returned when set, or each display costs 10 requests. A service cannot fix this for unnamed secretsD8barbicanclient/v1/secrets.py:468

Lists may omit the collection key without failing (barbicanclient/v1/secrets.py:680). total is read only by .total() (barbicanclient/base.py:75).

Decisions for a rewrite of the service

  • Additive changes are not free. Requirements 17 to 20 mean a new field needs a new client, or a microversion that this client never asks for: it asks for at most 1.2.
  • **/cas**: not in Barbican stable/2026.2, so ca get and ca list should already fail; a rewrite need not add it.
  • **POST /secrets/{id}/metadata/{key}** is refused with 405 by Barbican, while the client sends it (D24). Staying faithful keeps the bug; adding the route would be harmless, and make add_secret_metadata work. This needs a decision.
  • Secret delete at 1.2: the 400 with the fixed text is a requirement, including for castellan through the same code.

3. Coverage of the service

From [api-surface_stable-2026.2.md](_rewrite/api-surface_stable-2026.2.md): 32 operations, of which 29 have a handler in the Barbican route table. The client never calls 26 service operations, among them transport keys, quotas, project quotas, secret stores, PATCH on ACLs, GET …/consumers/{id}, POST and DELETE on /containers/{id}/secrets, and container consumer listing. A rewrite that serves only this client could postpone those. castellan and the other callers were not surveyed for them.

4. For a rewrite of the CLI or library

TopicRuleSource
Namessecret_order_create → secret order createsetup.cfg:37
Python callerscastellan uses Client(version, session, endpoint), the managers, and the exception families. A CLI rewrite would not replace the librarycastellan:castellan/key_manager/barbican_key_manager.py:183, castellan:castellan/key_manager/barbican_key_manager.py:432
Plugin couplingmake_client reads instance.session, instance._region_name, instance.interfacebarbicanclient/osc_plugin.py:28
Do not copyD1 to D26 in 02 § 4, unless compatibility with scripts demands it: D9 and D12 change visible output
Legacy shelldeprecated, with no reason to re-implement it; functionaltests/cli/v1/behaviors/base_behaviors.py:30 still drives the CLI tests through itbarbicanclient/barbican.py:382

Recommendation [inferred]: keep the Python client, and make the service conform. The CLI is thin, but castellan needs the library, and its strictness on fields is what constrains the service. If the client can be patched, D5 (ignore unknown fields) and D1 (.get('title')) are the two fixes that buy the most.

5. Conformance: what was proved

EvidenceResultWhat it provesWhat it does not prove
Parser dump23 of 23 classes imported, 0 failuresthe catalogue is complete for the declared entry points
Wire probes, 3 modes, 54 runs each23 of 23 commands sent a request in each mode; 52, 82 and 72 requests; base exit 0 for 0, 19 and 22 commandsreal routes, headers, bodies, negotiation through the real shellanything about a service: the stub is not one
Defect repros86 cases, 1 process eachhow the client reads each answerthat a real service sends it
Unit tests with recorder254 of 254 pass in two runs; 213 tests hold requests, 384 requests, no outbound connection attemptedrequests of the Python APIthe CLI: 0 of 23 take_action ran (D26). The answers are test fixtures
Comparison of the two runs213 of 213 identical, with bodies and headersdeterminism, after UUID normalisation
Routes against the Barbican reverse-spec29 of 32 have a handlerthe client's routes exist in that tablemethods, bodies and statuses at run time

"Replay is mandatory" is not met. Nothing here ran against Barbican. The barbican: cross-checks are readings of source code.

To close the gap: run probe_plugin_stable-2026.2.py with a real deployment in place of the stub, or run functionaltests/ against both the Python service and a rewrite. Those tests need a real deployment (etc/functional_tests.conf:5). Turn the shape mode into assertions only after that comparison.

6. Gaps found while building the pack

  • 7 operations of the Python API are reached by no command. They ran only through a small Python driver, in the pyapi-* cases.
  • GET /secrets/{id}/consumers has 0 unit vectors: its tests mock _api.get (barbicanclient/tests/v1/test_consumers.py:133).
  • Not every option combination ran: --os-key-manager-api-version other than 1 and the listed variants did, TLS options did not.
  • The stub has no state, so a read after a write returns the fixed document. acl-add-duplicate shows the body sent, not a stored ACL.

7. Non-functional requirements

  • Latency: each process sends 2 identity and 2 discovery requests before its first business request [observed]. Discovery documents are on the hot path.
  • Request volume: unnamed secrets multiply reads by 10 (D8). A service with many unnamed secrets sees secret list as up to about 100 GET for one page of 10 [inferred from the 21 for 2].
  • Secrets in transit: the payload travels in the POST body, base64 or plain; TLS comes from the deployment. --debug logs it (D14).

8. Checklist for a compatible service

  • [ ] key-manager in the catalog, public interface.
  • [ ] Version documents depend on the requested microversion: stable for 1.0 or no header, CURRENT with min_version and max_version otherwise; 300 on the root.
  • [ ] POST routes accept the trailing slash.
  • [ ] DELETE on consumers reads a JSON body.
  • [ ] Every 4xx and 5xx JSON body has title, and description; 401 included.
  • [ ] The refusal to delete holds the exact text; force=1 works at 1.2.
  • [ ] No field beyond the lists of § 2 in secrets, consumers, orders and meta.
  • [ ] POST answers JSON with the ref; PUT …/acl answers JSON.
  • [ ] GET …/acl gives a default read when nothing is set.
  • [ ] Decision taken on /cas and on POST /secrets/{id}/metadata/{key}.
  • [ ] The probe, pointed at the service, gives the same routes, and exit 0 with real resources.

Source: reverse-spec-python-barbicanclient/07-rewrite-pack_stable-2026.2.md. The _rewrite/ and _inventory/ files are not published on the site.