22
33The platform resolution endpoint answers with a closed set of error codes.
44``CREDENTIAL_INCOMPATIBLE`` is detected by the injector itself, when the
5- resolved credential does not satisfy the contract field. Each code carries a fixed, human-readable message meant to be reported as is
6- in the inject execution trace. Messages only ever mention the credential
7- reference id, never a secret value, and the raw HTTP request and response
8- bodies are deliberately never kept on the error.
5+ resolved credential does not satisfy the contract field, and
6+ ``CREDENTIAL_RESOLUTION_UNAVAILABLE`` when the platform could not be reached or
7+ failed to answer. Each code carries a fixed, human-readable message meant to be
8+ reported as is in the inject execution trace. Messages only ever mention the
9+ credential reference id, never a secret value, and the raw HTTP request and
10+ response bodies are deliberately never kept on the error.
11+
12+ A message is made of a summary (what happened) followed by a guidance (what to
13+ do). Both parts are exposed through ``credential_error_summary`` and
14+ ``credential_error_guidance`` so injectors that report them separately reuse
15+ the exact same texts.
916"""
1017
1118from enum import Enum
@@ -20,6 +27,7 @@ class CredentialErrorCode(str, Enum):
2027 CREDENTIAL_INACTIVE = "CREDENTIAL_INACTIVE"
2128 CREDENTIAL_ACCESS_DENIED = "CREDENTIAL_ACCESS_DENIED"
2229 CREDENTIAL_INCOMPATIBLE = "CREDENTIAL_INCOMPATIBLE"
30+ CREDENTIAL_RESOLUTION_UNAVAILABLE = "CREDENTIAL_RESOLUTION_UNAVAILABLE"
2331
2432
2533_PLATFORM_CREDENTIAL_ERROR_CODES = {
@@ -28,43 +36,84 @@ class CredentialErrorCode(str, Enum):
2836 CredentialErrorCode .CREDENTIAL_ACCESS_DENIED ,
2937}
3038
39+ UNKNOWN_REFERENCE = "unknown reference"
3140
32- _CREDENTIAL_ERROR_MESSAGES = {
41+ _CREDENTIAL_ERROR_SUMMARIES = {
3342 CredentialErrorCode .CREDENTIAL_NOT_FOUND : (
3443 "The credential configured on this inject ({reference}) no longer exists "
35- "at the moment of execution. Select an existing credential on the inject, "
36- "then run it again."
44+ "at the moment of execution."
3745 ),
3846 CredentialErrorCode .CREDENTIAL_INACTIVE : (
39- "The credential {reference} is inactive at the moment of execution. "
40- "Update or replace this credential, then run the inject again."
47+ "The credential {reference} is inactive at the moment of execution."
4148 ),
4249 CredentialErrorCode .CREDENTIAL_ACCESS_DENIED : (
4350 "This execution is not entitled to use the credential configured on this "
44- "inject. Contact your Cloud platform administrator "
51+ "inject."
4552 ),
4653 CredentialErrorCode .CREDENTIAL_INCOMPATIBLE : (
47- "The credential {reference} is not compatible with this inject. Select a "
48- "credential of type {type} on the inject, then run it again."
54+ "The credential {reference} is not compatible with this inject."
55+ ),
56+ CredentialErrorCode .CREDENTIAL_RESOLUTION_UNAVAILABLE : (
57+ "The credential {reference} could not be resolved: the OpenAEV platform "
58+ "was unavailable at the moment of execution."
59+ ),
60+ }
61+
62+ _CREDENTIAL_ERROR_GUIDANCES = {
63+ CredentialErrorCode .CREDENTIAL_NOT_FOUND : (
64+ "Select an existing credential on the inject, then run it again."
65+ ),
66+ CredentialErrorCode .CREDENTIAL_INACTIVE : (
67+ "Update or replace this credential, then run the inject again."
68+ ),
69+ CredentialErrorCode .CREDENTIAL_ACCESS_DENIED : (
70+ "Contact your Cloud platform administrator"
71+ ),
72+ CredentialErrorCode .CREDENTIAL_INCOMPATIBLE : (
73+ "Select a credential of type {type} on the inject, then run it again."
74+ ),
75+ CredentialErrorCode .CREDENTIAL_RESOLUTION_UNAVAILABLE : (
76+ "Check that the injector can reach the platform, then run the inject again."
4977 ),
5078}
5179
5280# Used when the expected credential type is not known where the error is raised
5381# (for example an unknown secret type returned by the platform).
54- _CREDENTIAL_INCOMPATIBLE_UNKNOWN_TYPE_MESSAGE = (
55- "The credential {reference} is not compatible with this inject. Select a "
56- "credential of the type expected by the inject, then run it again."
82+ _CREDENTIAL_INCOMPATIBLE_UNKNOWN_TYPE_GUIDANCE = (
83+ "Select a credential of the type expected by the inject, then run it again."
5784)
5885
5986
87+ def credential_error_summary (
88+ code : CredentialErrorCode , reference : Optional [str ] = None
89+ ) -> str :
90+ """Return what happened, mentioning only the reference id."""
91+ return _CREDENTIAL_ERROR_SUMMARIES [CredentialErrorCode (code )].format (
92+ reference = reference or UNKNOWN_REFERENCE
93+ )
94+
95+
96+ def credential_error_guidance (
97+ code : CredentialErrorCode , expected_type : Optional [CredentialType ] = None
98+ ) -> str :
99+ """Return what the operator should do about ``code``."""
100+ code = CredentialErrorCode (code )
101+ if code == CredentialErrorCode .CREDENTIAL_INCOMPATIBLE and expected_type is None :
102+ return _CREDENTIAL_INCOMPATIBLE_UNKNOWN_TYPE_GUIDANCE
103+ return _CREDENTIAL_ERROR_GUIDANCES [code ].format (
104+ type = CredentialType (expected_type ).value if expected_type else None
105+ )
106+
107+
60108def credential_error_code_from_http (
61109 response_code : Optional [int ], error_message : Optional [str ]
62110) -> CredentialErrorCode :
63111 """Map a failed resolution call to its credential error code.
64112
65113 The code sent by the platform in the response ``message`` wins. A 404
66114 without a known code (the inject itself does not exist) is reported as
67- ``CREDENTIAL_NOT_FOUND``. Any other failure falls back to
115+ ``CREDENTIAL_NOT_FOUND``, and a server error as
116+ ``CREDENTIAL_RESOLUTION_UNAVAILABLE``. Any other failure falls back to
68117 ``CREDENTIAL_ACCESS_DENIED`` so that nothing about the credential leaks.
69118 """
70119 if error_message :
@@ -76,16 +125,19 @@ def credential_error_code_from_http(
76125 return code
77126 if response_code == 404 :
78127 return CredentialErrorCode .CREDENTIAL_NOT_FOUND
128+ if response_code is not None and response_code >= 500 :
129+ return CredentialErrorCode .CREDENTIAL_RESOLUTION_UNAVAILABLE
79130 return CredentialErrorCode .CREDENTIAL_ACCESS_DENIED
80131
81132
82133class CredentialResolutionError (OpenAEVError ):
83134 """A credential reference could not be resolved for the current execution.
84135
85- ``message`` holds the fixed human-readable message of ``code``. The HTTP
86- response body is never stored, so it cannot surface in ``str()``.
87- ``expected_type`` is the credential type the inject expects, only used by
88- the ``CREDENTIAL_INCOMPATIBLE`` message.
136+ ``message`` holds the fixed human-readable message of ``code``, which is
137+ ``summary`` followed by ``guidance``. The HTTP response body is never
138+ stored, so it cannot surface in ``str()``. ``expected_type`` is the
139+ credential type the inject expects, only used by the
140+ ``CREDENTIAL_INCOMPATIBLE`` message.
89141 """
90142
91143 def __init__ (
@@ -100,18 +152,12 @@ def __init__(
100152 self .expected_type = (
101153 CredentialType (expected_type ) if expected_type is not None else None
102154 )
103- template = _CREDENTIAL_ERROR_MESSAGES [self .code ]
104- if (
105- self .code == CredentialErrorCode .CREDENTIAL_INCOMPATIBLE
106- and self .expected_type is None
107- ):
108- template = _CREDENTIAL_INCOMPATIBLE_UNKNOWN_TYPE_MESSAGE
109- message = template .format (
110- reference = reference or "unknown reference" ,
111- type = self .expected_type .value if self .expected_type else None ,
112- )
155+ self .summary = credential_error_summary (self .code , reference )
156+ self .guidance = credential_error_guidance (self .code , self .expected_type )
113157 super ().__init__ (
114- error_message = message , response_code = response_code , response_body = None
158+ error_message = f"{ self .summary } { self .guidance } " ,
159+ response_code = response_code ,
160+ response_body = None ,
115161 )
116162
117163 @property
@@ -144,9 +190,12 @@ def __init__(self, reference: Optional[str] = None) -> None:
144190
145191
146192__all__ = [
193+ "UNKNOWN_REFERENCE" ,
147194 "CredentialErrorCode" ,
148195 "CredentialResolutionError" ,
149196 "InvalidResolvedSecretError" ,
150197 "UnsupportedSecretTypeError" ,
151198 "credential_error_code_from_http" ,
199+ "credential_error_guidance" ,
200+ "credential_error_summary" ,
152201]
0 commit comments