Errors — python-barbicanclient — branch stable/2026.2
Branch
stable/2026.2@8162006e8677— repository https://opendev.org/openstack/python-barbicanclient — analysed on 2026-11-01
A case marked [observed] was reproduced with a real process against the local stub; its ID is in defect-repro. The stub answers what the case programs. So [observed] proves how the client reads an answer, not that a real Barbican sends it; that part is stated separately.
1. What the user sees
| Exit | Meaning | Source |
|---|---|---|
| 0 | success. cliff turns a None from take_action into 0 | cliff:cliff/command.py:196 |
| 1 | any exception in a command, message on stderr, no traceback without --debug | cliff:cliff/app.py:462 |
| 2 | usage error from argparse, or unknown command | cliff:cliff/app.py:428 |
An HTTP error prints twice [observed]: the client logs 4xx Client error: <message> (barbicanclient/client.py:132), and cliff logs the exception (cliff:cliff/app.py:467). Under barbican, each line prints 3 times (§ 4, D16).
An exception that is not an HTTP error is printed as Python renders it: a KeyError prints only 'title' [observed].
2. HTTP translation
The adapter sets raise_exc=False (barbicanclient/client.py:92) and checks the status itself (barbicanclient/client.py:117):
| Status | Exception | Logged | Source |
|---|---|---|---|
| 401, after one re-authentication by keystoneauth | HTTPAuthError | Auth error: %s | barbicanclient/client.py:120 |
| 500 or more, or no status | HTTPServerError | 5xx Server error: %s | barbicanclient/client.py:125 |
| other 400 or more | HTTPClientError | 4xx Client error: %s | barbicanclient/client.py:131 |
| below 400 | none |
The message comes from the body (barbicanclient/client.py:138):
- JSON with
titleanddescription:<title>: <description>. - JSON with
titleonly:<title>. - JSON without
title: crash, D1. - not JSON: the raw bytes, printed as
b'…'[observed:error-html-502], D15.
Barbican's own errors have this shape (barbican:barbican/api/hooks.py:29). Errors raised before the application, such as keystonemiddleware's, do not (keystonemiddleware:keystonemiddleware/auth_token/__init__.py:682).
Discovery errors do not pass through this code: get_version_data uses the session with exceptions on (keystoneauth1:keystoneauth1/discover.py:112), so they surface as keystoneauth exceptions, except the 406 that the client catches (barbicanclient/v1/client.py:132).
Note that secret delete at 1.2 converts a client error holding the text Secret cannot be deleted as it has consumers. to SecretHasConsumers (barbicanclient/v1/secrets.py:604). The service sends it with status 400 (barbican:barbican/common/exception.py:174).
3. Catalogue
Exception classes (9, from the inventory)
| Class | Base | Situation | Raised at | Caught at |
|---|---|---|---|---|
BarbicanException | Exception | base only | castellan:castellan/key_manager/barbican_key_manager.py:363 | |
PayloadException | BarbicanException | wrong or empty payload, on the client | barbicanclient/v1/secrets.py:349, barbicanclient/v1/secrets.py:352, barbicanclient/v1/secrets.py:401, barbicanclient/v1/secrets.py:410, barbicanclient/v1/secrets.py:541 | |
UnsupportedVersion | BarbicanException | factory failure, see D3 | barbicanclient/client.py:220 | |
SecretHasConsumers | BarbicanException | fixed message; deletion refused | barbicanclient/v1/secrets.py:607, barbicanclient/v1/secrets.py:614 | |
HTTPError | Exception | base, holds status_code | castellan:castellan/key_manager/barbican_key_manager.py:364 | |
HTTPClientError | HTTPError | 4xx | barbicanclient/client.py:133, barbicanclient/v1/secrets.py:609 | barbicanclient/v1/secrets.py:604 |
HTTPServerError | HTTPError | 5xx | barbicanclient/client.py:127 | |
HTTPAuthError | HTTPError | 401; constructor broken, D2 | barbicanclient/client.py:122 | |
ImmutableException | Exception (barbicanclient/base.py:59) | setter or second store() after save | barbicanclient/v1/secrets.py:42, barbicanclient/v1/orders.py:32, barbicanclient/v1/containers.py:33 |
BarbicanException and HTTPError have no common base. A KeyError or TypeError from a malformed response is neither, so castellan's handlers do not catch it [inferred from castellan:castellan/key_manager/barbican_key_manager.py:362].
Client-side messages reachable from a command
These send no business request, though authentication and discovery may already have run.
| Message | Exception | Situation | Raised at | Observed in |
|---|---|---|---|---|
Secret incorrectly specified. (also Container, Order, CA, secret) | ValueError | the last segment of the URI is not a UUID | barbicanclient/base.py:48 | get-not-a-uuid, get-payload-url |
Must supply at least one secret. | ValueError | secret container create without --secret | barbicanclient/barbican_cli/v1/containers.py:135 | base probe |
Invalid container type specified. | ValueError | --type not in generic, rsa, certificate | barbicanclient/barbican_cli/v1/containers.py:100 | container-unknown-type |
One or more of the provided secret_refs could not be retrieved! | ValueError | a ref is not parseable; nothing is retrieved despite the text | barbicanclient/v1/containers.py:103 | container-secret-not-uuid |
ERROR: file already exists. | ValueError | secret get --file on an existing file, after the GET | barbicanclient/barbican_cli/v1/secrets.py:81 | get-file-exists |
Invalid Payload: Cannot Be Empty String | PayloadException | empty --file | barbicanclient/v1/secrets.py:352 | store-empty-file |
Invalid Payload Type | PayloadException | not str or bytes; Python API | barbicanclient/v1/secrets.py:349, barbicanclient/v1/secrets.py:541 | |
Unable to parse date string 'soon' | ValueError from oslo.utils | bad --expiration | barbicanclient/v1/secrets.py:445, barbicanclient/v1/orders.py:128 | store-bad-expiration |
Server does not support secret consumers. Minimum key-manager microversion required: 1.1 | NotImplementedError | consumer commands at 1.0 | barbicanclient/v1/secrets.py:685 | microversion-1.0-consumer-list |
Secret cannot be deleted as it has consumers. | SecretHasConsumers | below 1.2, client-side | barbicanclient/v1/secrets.py:614 | microversion-1.1-delete-with-consumers |
Secret or container URI is not specified. | ValueError | ACL with a bare UUID or other path | barbicanclient/v1/acls.py:418 | acl-bare-uuid |
Secret ACL URI provided. Expecting Secret URI. | ValueError | the URI contains /acl | barbicanclient/v1/acls.py:374 | acl-uri-of-acl |
ACL data for secret is not provided. | ValueError | acl user remove when the GET gave no ACL | barbicanclient/v1/acls.py:309 | acl-remove-without-acl |
Unknown container type "". | TypeError | response type absent or unknown | barbicanclient/v1/containers.py:552 | 200 probe |
Unknown Order type "None" | TypeError | response type unknown, D6 | barbicanclient/v1/orders.py:361 | order-get-unknown-type |
Invalid KeyOrder meta field: […] | TypeError | unknown key in the meta of a key order | barbicanclient/v1/orders.py:353 | order-key-unknown-meta |
Invalid client version v1; must be one of: v1 | UnsupportedVersion | D3 | barbicanclient/client.py:220 | discovery-no-max-version |
Secret does not contain a payload (warning, not an error) | logged | payload fetch raised ValueError, D10 | barbicanclient/v1/secrets.py:238 | get-binary-as-text |
Messages reachable only from the Python API or the legacy shell
| Message | Exception | Raised at |
|---|---|---|
Barbican endpoint url must be provided when not using auth in the Keystone Session. | ValueError | barbicanclient/client.py:199 |
Project ID must be provided when not using auth in the Keystone Session | ValueError | barbicanclient/client.py:202 |
Invalid version: … | ValueError | barbicanclient/client.py:65 |
Invalid microversion …: Microversion requested is not supported by the client | ValueError, masked by D3 | barbicanclient/v1/client.py:85 |
Invalid microversion …: Microversion requested is not supported by the server | ValueError, masked by D3 | barbicanclient/v1/client.py:106 |
Couldn't find a version supported by both client and server | ValueError, masked by D3 | barbicanclient/v1/client.py:100 |
Endpoint does not support selected microversion | ValueError, masked by D3; unreachable, since the method never returns None [inferred] | barbicanclient/v1/client.py:59 |
Invalid or Missing Payload | PayloadException | barbicanclient/v1/secrets.py:401 |
Secret is not yet stored. | LookupError, also raised for a container, with the wrong noun | barbicanclient/v1/secrets.py:403, barbicanclient/v1/secrets.py:424, barbicanclient/v1/containers.py:222 |
Order is not yet stored. | LookupError | barbicanclient/v1/orders.py:220 |
Secret has no encrypted data to decrypt., Must specify decrypt content-type… | ValueError, caught by the payload property | barbicanclient/v1/secrets.py:314, barbicanclient/v1/secrets.py:316 |
Must provide a valid Secret object, A secret with this name already exists! | ValueError, KeyError, turned into the message below when in a constructor | barbicanclient/v1/containers.py:181, barbicanclient/v1/containers.py:183 |
One or more of the provided secrets are not valid Secret objects! | ValueError | barbicanclient/v1/containers.py:110 |
`add()` is not implemented for Typed Containers | NotImplementedError | barbicanclient/v1/containers.py:372, barbicanclient/v1/containers.py:510 |
No Order type provided, Unknown Order type "…" | TypeError | barbicanclient/v1/orders.py:365, barbicanclient/v1/orders.py:368 |
Users value is expected to be provided as list/set. | ValueError | barbicanclient/v1/acls.py:165 |
… href is required., Invalid ref_type: …, Expected secret or container URI is not specified. | ValueError | barbicanclient/v1/acls.py:370, barbicanclient/v1/acls.py:409, barbicanclient/v1/acls.py:512 |
Secret URI is not specified. | ValueError, with a ref_type | barbicanclient/v1/acls.py:380 |
container_ref is required., order_ref is required., secret_ref is required. | ValueError; the last is unreachable, since validation comes first | barbicanclient/v1/containers.py:699, barbicanclient/v1/orders.py:427, barbicanclient/v1/secrets.py:534, barbicanclient/v1/secrets.py:595 |
Container … could not be found., Order … could not be found. | LookupError, behind except AttributeError, which _api.get does not raise for a 404 [inferred] | barbicanclient/v1/containers.py:245, barbicanclient/v1/containers.py:544, barbicanclient/v1/orders.py:338 |
ERROR: argument --os-auth-url/-A: not allowed with argument --no-auth/-N | Exception, legacy | barbicanclient/barbican.py:172 |
ERROR: please specify --endpoint and --os-project-id (or --os-tenant-id) | Exception, legacy | barbicanclient/barbican.py:180 |
ERROR: please specify --os-auth-url | Exception, legacy | barbicanclient/barbican.py:192 |
ERROR: please specify authentication credentials | Exception, legacy | barbicanclient/barbican.py:223 |
ERROR: please specify the following --os-project-id or …, ERROR: please specify --os-tenant-id or … | Exception, legacy | barbicanclient/barbican.py:109 |
Asynchronous errors
None in the client (05). An order that fails on the service shows as Status ERROR, with Error code and Error message taken from error_status_code and error_reason (barbicanclient/v1/orders.py:57).
Key log messages
| Level | Message | Situation | Source |
|---|---|---|---|
| ERROR | 4xx Client error: %s, 5xx Server error: %s, Auth error: %s | any HTTP error | barbicanclient/client.py:121 |
| WARNING | Secret does not contain a payload | D10 | barbicanclient/v1/secrets.py:238 |
| WARNING | DEPRECATION WARNING: Manually setting the payload_content_type… | property setters; no command triggers it [inferred: the commands pass it to the constructor] | barbicanclient/v1/secrets.py:295 |
| WARNING | This Barbican CLI interface has been deprecated… | every barbican start | barbicanclient/barbican.py:382 |
| INFO | Calculated %s uuid ref: %s | visible with -v [inferred] | barbicanclient/base.py:55 |
4. Defects
Only what was reproduced, or read in code on both sides, is listed. Real impact says what is known about a real service; none was run.
| # | Defect | Source | Evidence | Real impact |
|---|---|---|---|---|
| D1 | **A JSON error without top-level title crashes the error path.** KeyError is not caught: only ValueError is. The user reads 'title'; the status and exception type are lost. A JSON list gives list indices must be integers or slices, not str | barbicanclient/client.py:141, barbicanclient/client.py:145 | [observed] error-no-title-404, error-keystonemiddleware-401, error-json-list | keystonemiddleware's 401 body has error.title, not title (keystonemiddleware:keystonemiddleware/auth_token/__init__.py:682), so a rejected token should give this [inferred]. Also any proxy or gateway JSON error |
| D2 | **HTTPAuthError calls the wrong base.** super(HTTPError, self) skips HTTPError.__init__: status_code is never set, and str(e) is the tuple ('Unauthorized: d', 401) | barbicanclient/exceptions.py:62 | [observed] error-401-with-title, error-401-attr-pyapi | every 401 with a title; code reading e.status_code gets AttributeError. castellan reads status_code on HTTPClientError only (castellan:castellan/key_manager/barbican_key_manager.py:706) |
| D3 | The factory hides discovery failures. except (KeyError, ValueError) wraps the whole client constructor, so a version document without max_version, a range below the client's, and a bad microversion= all read Invalid client version v1; must be one of: v1 | barbicanclient/client.py:215 | [observed] discovery-no-max-version, discovery-range-below-client, bad-microversion-pyapi | any service whose version document differs; the shipped service has max_version (barbican:barbican/api/controllers/versions.py:98) |
| D4 | A 406 on negotiation leads to the highest version, not the lowest. (None, None) is returned (barbicanclient/v1/client.py:133), version_between treats no bound as a match (keystoneauth1:keystoneauth1/discover.py:415), and the loop picks 1.2. The following request carries key-manager 1.2 | barbicanclient/v1/client.py:97 | [observed] discovery-406-on-1.1 | not reached with Barbican stable/2026.2, which accepts 1.1 (barbican:barbican/api/middleware/microversion.py:35). The Barbican reverse-spec reads the 406 as "server only supports 1.0": that is the comment's intent, not what happens |
| D5 | A new field in a response is fatal. Secrets, consumers and orders are built with **: Secret.__init__() got an unexpected keyword argument 'new_field'. secret list fails entirely. A secret consumer POST fails after the consumer was created | barbicanclient/v1/secrets.py:679, barbicanclient/v1/secrets.py:717, barbicanclient/v1/secrets.py:767, barbicanclient/v1/orders.py:359 | [observed] secret-list-unknown-field, consumer-create-unknown-field, consumer-list-unknown-field, order-get-unknown-field | blocks any additive evolution of those documents. Containers are tolerant: they pick fields (barbicanclient/v1/containers.py:555) |
| D6 | **The unknown order type message prints None**: it formats the class lookup, not the type. One such order stops the whole secret order list | barbicanclient/v1/orders.py:361 | [observed] order-get-unknown-type, order-list-one-unknown-type | the service validates only key and asymmetric (barbican:barbican/common/validators.py:436). Old certificate orders in a database should trigger it [inferred] |
| D7 | **-f json fails on RSA and certificate containers.** Their formatter returns created as a datetime, where the generic one uses isoformat(). stdout gets partial JSON, exit 1. On secret container create --type rsa -f json, the container was created | barbicanclient/v1/containers.py:294, barbicanclient/v1/containers.py:415, cliff:cliff/formatters/json_format.py:73 | [observed] rsa-container-get-json, certificate-container-get-json, rsa-container-create-json | independent of the service, once created is set. Table and YAML work (certificate-container-get-yaml) but show a different date format; list uses the generic formatter (rsa-container-list-json) |
| D8 | An unnamed secret is re-fetched on each property. The guard is self._secret_ref and not self._name, so a null name never satisfies it. secret get: 10 GET, 11 with an expiration. secret store: 1 POST and 10 GET. secret list of 2: 21 requests. With a name: 1 | barbicanclient/v1/secrets.py:468 | [observed] unnamed-secret-get-requests, unnamed-secret-get-with-expiration, unnamed-secret-store-requests, unnamed-secret-list-requests, named-secret-get-requests, named-secret-list-requests | a name is optional, so the load on a real service follows [inferred]. The same logic applies to CAs until plugin_name is set (barbicanclient/v1/cas.py:173): 8 GET in 200 mode |
| D9 | **secret store --name prints empty fields.** The same guard skips the reload: Created, Status and Content types are None. Without --name they are filled. secret order create always shows Status None, and a generic secret container create shows Created None, because it reads created before the reload | barbicanclient/v1/secrets.py:468, barbicanclient/v1/containers.py:59, barbicanclient/v1/orders.py:206 | [observed] secret-store-named-output, secret-store-unnamed-output, container-secret-without-equals | independent of the service; scripts cannot rely on those fields |
| D10 | An undecodable payload is reported as no payload. UnicodeDecodeError is a ValueError, caught by the property. secret get -p: Payload None, exit 0. With --file: an empty file is created, then a bytes-like object is required, not 'NoneType', exit 1 | barbicanclient/v1/secrets.py:237, barbicanclient/v1/secrets.py:325, barbicanclient/barbican_cli/v1/secrets.py:87 | [observed] get-binary-as-text, get-binary-as-text-to-file | Barbican answers 406 instead for text it cannot decode (barbican:barbican/plugin/util/translations.py:103), so this needs a service with another behaviour |
| D11 | **--file with -t or -e fails on binary content** with a raw codec error, because the "backward compatibility" branch decodes as UTF-8. The user who gives the correct type for a binary file gets the crash; omitting it works | barbicanclient/v1/secrets.py:363 | [observed] store-binary-file-with-content-type | client-side, no request |
| D12 | **--payload without -t is stored as binary**, and the help says -t is required with --payload | barbicanclient/barbican_cli/v1/secrets.py:165, barbicanclient/barbican_cli/v1/secrets.py:196, barbicanclient/v1/secrets.py:370 | [observed] store-text-payload-default-type | content_types shows octet-stream for a text secret. A read with Accept: text/plain still works, since the service decodes (barbican:barbican/plugin/util/translations.py:100) [inferred end to end] |
| D13 | **--secret without exactly one = is dropped silently.** --secret foo and a ref whose query holds = both give "secret_refs": [], exit 0 on the stub. Unexpected names on typed containers vanish likewise | barbicanclient/barbican_cli/v1/containers.py:138, barbicanclient/barbican_cli/v1/containers.py:103 | [observed] container-secret-without-equals, container-secret-with-two-equals, container-rsa-missing-key | on a real service a generic container with no secrets would be created [inferred]; rsa should be refused (barbican:barbican/common/validators.py:732) |
| D14 | **--debug writes secrets to stderr.** The curl-style request holds the payload, the response body is logged for text/plain, and osc-lib's start line holds --payload and --os-password in clear | keystoneauth1:keystoneauth1/session.py:602, keystoneauth1:keystoneauth1/session.py:60, osc-lib:osc_lib/shell.py:434 | [observed] debug-log-payload-store, debug-log-payload-get | any real use of --debug. barbicanclient/v1/secrets.py:389 masks only the client's own line |
| D15 | **Non-JSON errors print as b'<html>…'**, and success responses that are not JSON raise Expecting value: line 1 column 1 (char 0), including after a successful POST or PUT | barbicanclient/client.py:146, barbicanclient/client.py:102, barbicanclient/client.py:107, barbicanclient/v1/acls.py:328 | [observed] error-html-502, post-empty-body, acl-put-empty-body | HTML errors from a proxy; Barbican itself answers JSON on these routes (barbican:barbican/api/controllers/acls.py:181) |
| D16 | Legacy shell output: messages print 3 times (logging.basicConfig, plus configure_logging twice). Without OS_IDENTITY_API_VERSION it writes WARNING: The identity version <> is not in supported versions, with no newline, although 3 is the documented default. The deprecation text names the "O release" | barbicanclient/barbican.py:381, barbicanclient/barbican.py:349, barbicanclient/barbican.py:142, barbicanclient/barbican.py:382 | [observed] legacy-password, legacy-password-error-log, legacy-no-credentials | client-side |
| D17 | **acl * refuses a bare UUID**, although URI works that way everywhere else, and the docstrings accept one; the CLI has no way to give ref_type | barbicanclient/v1/acls.py:412 | [observed] acl-bare-uuid | client-side |
| D18 | **A host name holding acl is refused**: the test is a substring on the whole URL, so http://acl.localhost/v1/secrets/<uuid> gives Secret ACL URI provided | barbicanclient/v1/acls.py:373 | [observed] acl-host-contains-acl | a deployment whose host name or path starts with acl |
| D19 | **acl user add sends duplicates**: ["u1", "u1"] | barbicanclient/barbican_cli/v1/acls.py:199 | [observed] acl-add-duplicate | what the service stores was not followed |
| D20 | **secret order create asymmetric defaults to aes, 256**, inherited from the shared option defaults, and --mode is ignored | barbicanclient/barbican_cli/v1/orders.py:31, barbicanclient/v1/orders.py:272 | [observed] order-asymmetric-defaults, order-asymmetric-mode-ignored | the service accepts it at validation (barbican:barbican/common/validators.py:477); the generation stage should refuse or fail it [inferred] |
| D21 | Missing keys give bare errors. {} on GET /orders/{id} prints 'type'; a key order without meta prints 'meta'; a container without secret_refs prints 'NoneType' object is not iterable; an empty version list via --endpoint prints list index out of range | barbicanclient/v1/orders.py:344, barbicanclient/v1/orders.py:349, barbicanclient/v1/containers.py:614, barbicanclient/v1/client.py:135 | [observed] 200 probe, order-no-meta, container-no-secret-refs, legacy-discovery-empty-list | only with a non-conforming service |
| D22 | **A POST answered 200 {} looks like success**: Secret href None, exit 0 | barbicanclient/v1/secrets.py:393 | [observed] post-200-no-ref | only with a non-conforming service |
| D23 | **ca get, ca list have no route in the service of this branch** | barbicanclient/v1/cas.py:236, barbican:barbican/api/controllers/versions.py:141 | read on both sides; the stub answered as programmed | 404 expected [inferred] |
| D24 | **add_secret_metadata posts to a route the service refuses**: POST /secrets/{id}/metadata/{key} gets pecan.abort(405), and the service's POST is at /metadata | barbicanclient/v1/secrets.py:808, barbican:barbican/api/controllers/secrets.py:104, barbican:barbican/api/controllers/secretmeta.py:85 | request [observed] pyapi-secret-metadata; refusal read in the code | 405 expected [inferred]; no command uses it. Its unit tests pass against a mock of the same wrong route (barbicanclient/tests/v1/test_secrets.py:692) |
| D25 | **ACLManager.total() asks GET /acl**, since it inherits total with the entity acl | barbicanclient/base.py:73, barbicanclient/v1/acls.py:456 | [observed] pyapi-acl-total | no such route in the service; no command uses it |
| D26 | The tests that go through the shell ignore its result. self.barbican.run(argv=argv) returns an exit code, and the test only fails on an exception, which cliff has already caught. test_v2_auth fails with Invalid client version v1 and test_v3_auth with a missing project argument, and both pass. No take_action runs in 254 tests | barbicanclient/tests/keystone_client_fixtures.py:252 | [observed] by wrapping every take_action, and by running these tests | none on a service; it is why D7, D9, D11, D12 and D13 went unseen |
Checked and not found
- **A return value from
command.Command, read by cliff as the exit code**: none of the 7 classes returns (barbicanclient/barbican_cli/v1/secrets.py:38,barbicanclient/barbican_cli/v1/secrets.py:106,barbicanclient/barbican_cli/v1/secrets.py:225,barbicanclient/barbican_cli/v1/secrets.py:249,barbicanclient/barbican_cli/v1/orders.py:69,barbicanclient/barbican_cli/v1/containers.py:33,barbicanclient/barbican_cli/v1/acls.py:92). - List responses without their key: tolerated, by
.get(key, [])(barbicanclient/v1/secrets.py:680,barbicanclient/v1/containers.py:729,barbicanclient/v1/orders.py:448,barbicanclient/v1/cas.py:240): an empty table, exit 0 [observed: 200 probe]. This differs from the SDK-based commands of OSC.
Source: reverse-spec-python-barbicanclient/02-errors_stable-2026.2.md. The _rewrite/ and _inventory/ files are not published on the site.