capo-cloudcontrol 0.7.0__tar.gz → 0.9.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. {capo_cloudcontrol-0.7.0/src/capo_cloudcontrol.egg-info → capo_cloudcontrol-0.9.0}/PKG-INFO +1 -1
  2. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/pyproject.toml +1 -1
  3. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/__init__.py +6 -0
  4. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/_auth/_providers.py +118 -19
  5. capo_cloudcontrol-0.9.0/src/capo_cloudcontrol/_aws.py +28 -0
  6. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/_operations/cloud_api_service/cancel_resource_request.py +2 -2
  7. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/_operations/cloud_api_service/create_resource.py +19 -19
  8. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/_operations/cloud_api_service/delete_resource.py +19 -19
  9. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/_operations/cloud_api_service/get_resource.py +17 -17
  10. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/_operations/cloud_api_service/get_resource_request_status.py +1 -1
  11. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/_operations/cloud_api_service/list_resources.py +17 -17
  12. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/_operations/cloud_api_service/update_resource.py +19 -19
  13. capo_cloudcontrol-0.9.0/src/capo_cloudcontrol/_services/_pipeline.py +522 -0
  14. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/_services/async_cloud_control.py +155 -10
  15. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/_services/cloud_control.py +155 -10
  16. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/errors/_base.py +8 -3
  17. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/errors/already_exists_exception.py +11 -3
  18. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/errors/client_token_conflict_exception.py +11 -3
  19. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/errors/concurrent_modification_exception.py +9 -3
  20. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/errors/concurrent_operation_exception.py +11 -3
  21. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/errors/general_service_exception.py +11 -3
  22. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/errors/handler_failure_exception.py +11 -3
  23. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/errors/handler_internal_failure_exception.py +9 -3
  24. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/errors/invalid_credentials_exception.py +11 -3
  25. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/errors/invalid_request_exception.py +11 -3
  26. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/errors/network_failure_exception.py +11 -3
  27. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/errors/not_stabilized_exception.py +11 -3
  28. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/errors/not_updatable_exception.py +11 -3
  29. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/errors/private_type_exception.py +11 -3
  30. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/errors/request_token_not_found_exception.py +9 -3
  31. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/errors/resource_conflict_exception.py +11 -3
  32. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/errors/resource_not_found_exception.py +11 -3
  33. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/errors/service_internal_error_exception.py +9 -3
  34. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/errors/service_limit_exceeded_exception.py +9 -3
  35. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/errors/throttling_exception.py +11 -3
  36. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/errors/type_not_found_exception.py +11 -3
  37. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/errors/unsupported_action_exception.py +11 -3
  38. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/types/cancel_resource_request_output.py +12 -1
  39. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/types/create_resource_input.py +8 -1
  40. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/types/create_resource_output.py +12 -1
  41. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/types/delete_resource_output.py +12 -1
  42. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/types/get_resource_output.py +14 -1
  43. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/types/get_resource_request_status_output.py +12 -1
  44. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/types/list_resource_requests_output.py +14 -1
  45. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/types/list_resources_input.py +8 -1
  46. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/types/list_resources_output.py +14 -1
  47. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/types/progress_event.py +8 -1
  48. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/types/resource_description.py +8 -1
  49. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/types/resource_descriptions.py +10 -1
  50. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/types/resource_request_status_summaries.py +9 -1
  51. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/types/update_resource_input.py +8 -1
  52. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/types/update_resource_output.py +12 -1
  53. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0/src/capo_cloudcontrol.egg-info}/PKG-INFO +1 -1
  54. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol.egg-info/SOURCES.txt +1 -0
  55. capo_cloudcontrol-0.7.0/src/capo_cloudcontrol/_services/_pipeline.py +0 -266
  56. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/LICENSE +0 -0
  57. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/README.md +0 -0
  58. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/setup.cfg +0 -0
  59. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/_async.py +0 -0
  60. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/_auth/_identity.py +0 -0
  61. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/_auth/_signers.py +0 -0
  62. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/_auth/_sigv4.py +0 -0
  63. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/_auth/_zapros_handler.py +0 -0
  64. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/_body.py +0 -0
  65. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/_iter.py +0 -0
  66. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/_operations/cloud_api_service/list_resource_requests.py +0 -0
  67. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/_pagination.py +0 -0
  68. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/_protocol/__init__.py +0 -0
  69. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/_protocol/errors.py +0 -0
  70. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/_protocol/eventstream.py +0 -0
  71. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/_protocol/serialize.py +0 -0
  72. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/_protocol/xml.py +0 -0
  73. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/_rule_engine/__init__.py +0 -0
  74. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/_rule_engine/_aws_partition.py +0 -0
  75. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/_rule_engine/_endpoint_rule_set.py +0 -0
  76. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/_rule_engine/_endpoint_runtime.py +0 -0
  77. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/_services/_aws_config.py +0 -0
  78. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/errors/__init__.py +0 -0
  79. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/py.typed +0 -0
  80. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/types/_prelude/blob.py +0 -0
  81. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/types/_prelude/timestamp.py +0 -0
  82. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/types/cancel_resource_request_input.py +0 -0
  83. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/types/client_token.py +0 -0
  84. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/types/delete_resource_input.py +0 -0
  85. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/types/error_message.py +0 -0
  86. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/types/get_resource_input.py +0 -0
  87. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/types/get_resource_request_status_input.py +0 -0
  88. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/types/handler_error_code.py +0 -0
  89. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/types/handler_next_token.py +0 -0
  90. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/types/hook_failure_mode.py +0 -0
  91. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/types/hook_invocation_point.py +0 -0
  92. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/types/hook_progress_event.py +0 -0
  93. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/types/hook_status.py +0 -0
  94. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/types/hook_type_arn.py +0 -0
  95. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/types/hooks_progress_event.py +0 -0
  96. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/types/identifier.py +0 -0
  97. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/types/list_resource_requests_input.py +0 -0
  98. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/types/max_results.py +0 -0
  99. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/types/next_token.py +0 -0
  100. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/types/operation.py +0 -0
  101. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/types/operation_status.py +0 -0
  102. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/types/operation_statuses.py +0 -0
  103. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/types/operations.py +0 -0
  104. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/types/patch_document.py +0 -0
  105. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/types/properties.py +0 -0
  106. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/types/request_token.py +0 -0
  107. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/types/resource_request_status_filter.py +0 -0
  108. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/types/role_arn.py +0 -0
  109. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/types/status_message.py +0 -0
  110. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/types/timestamp.py +0 -0
  111. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/types/type_name.py +0 -0
  112. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol/types/type_version_id.py +0 -0
  113. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol.egg-info/dependency_links.txt +0 -0
  114. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol.egg-info/requires.txt +0 -0
  115. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/src/capo_cloudcontrol.egg-info/top_level.txt +0 -0
  116. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/tests/test_body.py +0 -0
  117. {capo_cloudcontrol-0.7.0 → capo_cloudcontrol-0.9.0}/tests/test_sigv4.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: capo-cloudcontrol
3
- Version: 0.7.0
3
+ Version: 0.9.0
4
4
  Summary: Python SDK for AWS Cloudcontrol.
5
5
  Classifier: Development Status :: 4 - Beta
6
6
  Classifier: Intended Audience :: Developers
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "capo-cloudcontrol"
3
- version = "0.7.0"
3
+ version = "0.9.0"
4
4
  description = "Python SDK for AWS Cloudcontrol."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -16,6 +16,9 @@ from ._auth._providers import (
16
16
  from ._auth._providers import (
17
17
  ChainedProvider as ChainedProvider,
18
18
  )
19
+ from ._auth._providers import (
20
+ CredentialProcessError as CredentialProcessError,
21
+ )
19
22
  from ._auth._providers import (
20
23
  CredentialsProvider as CredentialsProvider,
21
24
  )
@@ -31,6 +34,9 @@ from ._auth._providers import (
31
34
  from ._auth._providers import (
32
35
  MissingDependencyError as MissingDependencyError,
33
36
  )
37
+ from ._auth._providers import (
38
+ ProcessCredentialsProvider as ProcessCredentialsProvider,
39
+ )
34
40
  from ._auth._providers import (
35
41
  ProfileCredentialsProvider as ProfileCredentialsProvider,
36
42
  )
@@ -4,6 +4,7 @@ import configparser
4
4
  import hashlib
5
5
  import json
6
6
  import os
7
+ import subprocess
7
8
  import time
8
9
  from abc import abstractmethod
9
10
  from dataclasses import dataclass
@@ -59,6 +60,10 @@ class IdentityNotFound(Exception):
59
60
  """Raised when a provider cannot resolve an identity. Chain continues."""
60
61
 
61
62
 
63
+ class CredentialProcessError(Exception):
64
+ """Raised when a profile's `credential_process` fails or prints unusable output. Chain stops."""
65
+
66
+
62
67
  class MissingDependencyError(Exception):
63
68
  """Raised when a provider needs the `sso` extra but it is not installed."""
64
69
 
@@ -186,13 +191,27 @@ class ProfileCredentialsProvider(CredentialsProvider):
186
191
  self, credentials_file: Path | None = None, profile: str | None = None
187
192
  ) -> None:
188
193
  self._profile = profile or active_profile()
189
- self._cred_file = credentials_file or Path(
190
- os.environ.get("AWS_SHARED_CREDENTIALS_FILE")
191
- or Path.home() / ".aws" / "credentials"
192
- )
194
+ self._cred_file = credentials_file or _shared_credentials_file()
193
195
 
194
196
  def resolve_identity(self) -> Credentials:
195
- section = self._load_section()
197
+ from_config, _ = _load_profile(self._profile)
198
+ from_credentials_file = _credentials_file_section(
199
+ self._profile, self._cred_file
200
+ )
201
+ section = {**from_config, **from_credentials_file}
202
+ if not section:
203
+ raise IdentityNotFound(
204
+ f"profile {self._profile!r} not found in credentials/config files"
205
+ )
206
+ # botocore's order: keys from the credentials file, then credential_process, then keys from the
207
+ # config file, so a profile with a process only takes its keys from the credentials file
208
+ if "credential_process" in section and not (
209
+ from_credentials_file.get("aws_access_key_id")
210
+ and from_credentials_file.get("aws_secret_access_key")
211
+ ):
212
+ raise IdentityNotFound(
213
+ f"profile {self._profile!r}: credential_process comes before the keys in the config file"
214
+ )
196
215
  ak = section.get("aws_access_key_id")
197
216
  sk = section.get("aws_secret_access_key")
198
217
  if not ak or not sk:
@@ -205,19 +224,94 @@ class ProfileCredentialsProvider(CredentialsProvider):
205
224
  out["session_token"] = token
206
225
  return out
207
226
 
208
- def _load_section(self) -> dict[str, str]:
209
- # config-file profile reuses the loader from _services/_aws_config
210
- merged, _ = _load_profile(self._profile)
211
- if self._cred_file.is_file():
212
- cfg = configparser.ConfigParser(interpolation=None)
213
- cfg.read(self._cred_file)
214
- if cfg.has_section(self._profile):
215
- merged.update(dict(cfg.items(self._profile)))
216
- if not merged:
217
- raise IdentityNotFound(
218
- f"profile {self._profile!r} not found in credentials/config files"
227
+
228
+ class ProcessCredentialsProvider(CredentialsProvider):
229
+ """Run the profile's ``credential_process`` command and read the credentials it prints.
230
+
231
+ This is how helpers such as ``aws-sso-cli`` hand out short-term credentials. The command
232
+ runs through the system shell, as in the Rust, Kotlin and Swift SDKs, so a quoted Windows path
233
+ works as written, and it must print the JSON document the AWS SDKs define (``Version`` 1).
234
+ The key is read from the credentials file as well as the config file. A profile without
235
+ ``credential_process`` is skipped; a command that fails or outlives the timeout stops the chain.
236
+ """
237
+
238
+ def __init__(
239
+ self,
240
+ credentials_file: Path | None = None,
241
+ profile: str | None = None,
242
+ timeout: float = 60.0,
243
+ ) -> None:
244
+ self._profile = profile or active_profile()
245
+ self._cred_file = credentials_file or _shared_credentials_file()
246
+ self._timeout = timeout
247
+
248
+ def resolve_identity(self) -> Credentials:
249
+ section = _load_merged_profile(self._profile, self._cred_file)
250
+ command = section.get("credential_process")
251
+ if not command:
252
+ raise IdentityNotFound(f"profile {self._profile!r}: no credential_process")
253
+ try:
254
+ # the shell is sh -c on POSIX and cmd.exe /c on Windows, which parses the path natively
255
+ result = subprocess.run(
256
+ command,
257
+ shell=True,
258
+ capture_output=True,
259
+ text=True,
260
+ check=False,
261
+ timeout=self._timeout,
262
+ )
263
+ except subprocess.TimeoutExpired:
264
+ raise CredentialProcessError(
265
+ f"profile {self._profile!r}: credential_process did not finish within {self._timeout} seconds"
266
+ ) from None
267
+ except OSError as e:
268
+ raise CredentialProcessError(f"profile {self._profile!r}: {e}") from e
269
+ # stdout carries the secret, so only stderr ever goes into an error message
270
+ if result.returncode != 0:
271
+ raise CredentialProcessError(
272
+ f"profile {self._profile!r}: credential_process exited with {result.returncode}: {result.stderr.strip()}"
273
+ )
274
+ try:
275
+ data = json.loads(result.stdout)
276
+ except ValueError:
277
+ raise CredentialProcessError(
278
+ f"profile {self._profile!r}: credential_process did not print JSON"
279
+ ) from None
280
+ if not isinstance(data, dict) or data.get("Version") != 1:
281
+ raise CredentialProcessError(
282
+ f"profile {self._profile!r}: credential_process output must be a JSON object with Version 1"
219
283
  )
220
- return merged
284
+ try:
285
+ return _credentials_from_json(data, token_key="SessionToken")
286
+ except IdentityNotFound as e:
287
+ raise CredentialProcessError(f"profile {self._profile!r}: {e}") from None
288
+ except ValueError:
289
+ raise CredentialProcessError(
290
+ f"profile {self._profile!r}: credential_process printed an invalid Expiration"
291
+ ) from None
292
+
293
+
294
+ def _shared_credentials_file() -> Path:
295
+ return Path(
296
+ os.environ.get("AWS_SHARED_CREDENTIALS_FILE")
297
+ or Path.home() / ".aws" / "credentials"
298
+ )
299
+
300
+
301
+ def _load_merged_profile(profile: str, credentials_file: Path) -> dict[str, str]:
302
+ """The profile's keys from the config file, overlaid by the credentials file, as the AWS SDKs merge them."""
303
+ # config-file profile reuses the loader from _services/_aws_config
304
+ merged, _ = _load_profile(profile)
305
+ merged.update(_credentials_file_section(profile, credentials_file))
306
+ return merged
307
+
308
+
309
+ def _credentials_file_section(profile: str, credentials_file: Path) -> dict[str, str]:
310
+ if not credentials_file.is_file():
311
+ return {}
312
+ cfg = configparser.ConfigParser(interpolation=None)
313
+ cfg.read(credentials_file)
314
+ return dict(cfg.items(profile)) if cfg.has_section(profile) else {}
221
315
 
222
316
 
223
317
  class EcsContainerCredentialsProvider(CredentialsProvider):
@@ -721,6 +815,7 @@ class AssumeRoleCredentialsProvider(CredentialsProvider):
721
815
  ),
722
816
  SsoCredentialsProvider(self._client, source_profile),
723
817
  ProfileCredentialsProvider(profile=source_profile),
818
+ ProcessCredentialsProvider(profile=source_profile),
724
819
  )
725
820
 
726
821
 
@@ -853,7 +948,10 @@ def _parse_iso8601(value: str) -> datetime:
853
948
  return datetime.fromisoformat(value.replace("Z", "+00:00"))
854
949
 
855
950
 
856
- def _credentials_from_json(data: dict[str, object]) -> Credentials:
951
+ def _credentials_from_json(
952
+ data: dict[str, object], *, token_key: str = "Token"
953
+ ) -> Credentials:
954
+ # ECS and IMDS name the session token Token; credential_process names it SessionToken
857
955
  ak = data.get("AccessKeyId")
858
956
  sk = data.get("SecretAccessKey")
859
957
  if not isinstance(ak, str) or not isinstance(sk, str):
@@ -861,7 +959,7 @@ def _credentials_from_json(data: dict[str, object]) -> Credentials:
861
959
  "credentials response missing AccessKeyId/SecretAccessKey"
862
960
  )
863
961
  out: Credentials = {"access_key": ak, "secret_key": sk}
864
- token = data.get("Token")
962
+ token = data.get(token_key)
865
963
  if isinstance(token, str):
866
964
  out["session_token"] = token
867
965
  exp = data.get("Expiration")
@@ -880,6 +978,7 @@ def default_aws_credentials_chain(
880
978
  WebIdentityCredentialsProvider(client),
881
979
  SsoCredentialsProvider(client),
882
980
  ProfileCredentialsProvider(),
981
+ ProcessCredentialsProvider(),
883
982
  EcsContainerCredentialsProvider(client),
884
983
  Ec2InstanceMetadataProvider(client),
885
984
  )
@@ -0,0 +1,28 @@
1
+ """Error codes and HTTP statuses that are retried whether or not the model marks them @retryable."""
2
+
3
+ # Transient: retried with the normal backoff.
4
+ KNOWN_RETRYABLE_STATUSES = frozenset({500, 502, 503, 504})
5
+ KNOWN_RETRYABLE_CODES = frozenset(
6
+ {"PriorRequestNotComplete", "RequestTimeout", "RequestTimeoutException"}
7
+ )
8
+
9
+ # Throttling: retried with the longer throttling backoff.
10
+ KNOWN_THROTTLING_STATUSES = frozenset({429})
11
+ KNOWN_THROTTLING_CODES = frozenset(
12
+ {
13
+ "BandwidthLimitExceeded",
14
+ "EC2ThrottledException",
15
+ "LimitExceededException",
16
+ "PriorRequestNotComplete",
17
+ "ProvisionedThroughputExceededException",
18
+ "RequestLimitExceeded",
19
+ "RequestThrottled",
20
+ "RequestThrottledException",
21
+ "SlowDown",
22
+ "ThrottledException",
23
+ "Throttling",
24
+ "ThrottlingException",
25
+ "TooManyRequestsException",
26
+ "TransactionInProgressException",
27
+ }
28
+ )
@@ -32,11 +32,11 @@ def handle_error(response: zapros.Response) -> Never:
32
32
  match code:
33
33
  case "ConcurrentModificationException":
34
34
  raise capo_cloudcontrol.errors.concurrent_modification_exception.ConcurrentModificationException.from_aws_json_1_0(
35
- data, message
35
+ data, message, response=response
36
36
  )
37
37
  case "RequestTokenNotFoundException":
38
38
  raise capo_cloudcontrol.errors.request_token_not_found_exception.RequestTokenNotFoundException.from_aws_json_1_0(
39
- data, message
39
+ data, message, response=response
40
40
  )
41
41
  case _:
42
42
  raise UnknownServiceError(code=code, message=message, response=response)
@@ -49,79 +49,79 @@ def handle_error(response: zapros.Response) -> Never:
49
49
  match code:
50
50
  case "AlreadyExistsException":
51
51
  raise capo_cloudcontrol.errors.already_exists_exception.AlreadyExistsException.from_aws_json_1_0(
52
- data, message
52
+ data, message, response=response
53
53
  )
54
54
  case "ClientTokenConflictException":
55
55
  raise capo_cloudcontrol.errors.client_token_conflict_exception.ClientTokenConflictException.from_aws_json_1_0(
56
- data, message
56
+ data, message, response=response
57
57
  )
58
58
  case "ConcurrentOperationException":
59
59
  raise capo_cloudcontrol.errors.concurrent_operation_exception.ConcurrentOperationException.from_aws_json_1_0(
60
- data, message
60
+ data, message, response=response
61
61
  )
62
62
  case "GeneralServiceException":
63
63
  raise capo_cloudcontrol.errors.general_service_exception.GeneralServiceException.from_aws_json_1_0(
64
- data, message
64
+ data, message, response=response
65
65
  )
66
66
  case "HandlerFailureException":
67
67
  raise capo_cloudcontrol.errors.handler_failure_exception.HandlerFailureException.from_aws_json_1_0(
68
- data, message
68
+ data, message, response=response
69
69
  )
70
70
  case "HandlerInternalFailureException":
71
71
  raise capo_cloudcontrol.errors.handler_internal_failure_exception.HandlerInternalFailureException.from_aws_json_1_0(
72
- data, message
72
+ data, message, response=response
73
73
  )
74
74
  case "InvalidCredentialsException":
75
75
  raise capo_cloudcontrol.errors.invalid_credentials_exception.InvalidCredentialsException.from_aws_json_1_0(
76
- data, message
76
+ data, message, response=response
77
77
  )
78
78
  case "InvalidRequestException":
79
79
  raise capo_cloudcontrol.errors.invalid_request_exception.InvalidRequestException.from_aws_json_1_0(
80
- data, message
80
+ data, message, response=response
81
81
  )
82
82
  case "NetworkFailureException":
83
83
  raise capo_cloudcontrol.errors.network_failure_exception.NetworkFailureException.from_aws_json_1_0(
84
- data, message
84
+ data, message, response=response
85
85
  )
86
86
  case "NotStabilizedException":
87
87
  raise capo_cloudcontrol.errors.not_stabilized_exception.NotStabilizedException.from_aws_json_1_0(
88
- data, message
88
+ data, message, response=response
89
89
  )
90
90
  case "NotUpdatableException":
91
91
  raise capo_cloudcontrol.errors.not_updatable_exception.NotUpdatableException.from_aws_json_1_0(
92
- data, message
92
+ data, message, response=response
93
93
  )
94
94
  case "PrivateTypeException":
95
95
  raise capo_cloudcontrol.errors.private_type_exception.PrivateTypeException.from_aws_json_1_0(
96
- data, message
96
+ data, message, response=response
97
97
  )
98
98
  case "ResourceConflictException":
99
99
  raise capo_cloudcontrol.errors.resource_conflict_exception.ResourceConflictException.from_aws_json_1_0(
100
- data, message
100
+ data, message, response=response
101
101
  )
102
102
  case "ResourceNotFoundException":
103
103
  raise capo_cloudcontrol.errors.resource_not_found_exception.ResourceNotFoundException.from_aws_json_1_0(
104
- data, message
104
+ data, message, response=response
105
105
  )
106
106
  case "ServiceInternalErrorException":
107
107
  raise capo_cloudcontrol.errors.service_internal_error_exception.ServiceInternalErrorException.from_aws_json_1_0(
108
- data, message
108
+ data, message, response=response
109
109
  )
110
110
  case "ServiceLimitExceededException":
111
111
  raise capo_cloudcontrol.errors.service_limit_exceeded_exception.ServiceLimitExceededException.from_aws_json_1_0(
112
- data, message
112
+ data, message, response=response
113
113
  )
114
114
  case "ThrottlingException":
115
115
  raise capo_cloudcontrol.errors.throttling_exception.ThrottlingException.from_aws_json_1_0(
116
- data, message
116
+ data, message, response=response
117
117
  )
118
118
  case "TypeNotFoundException":
119
119
  raise capo_cloudcontrol.errors.type_not_found_exception.TypeNotFoundException.from_aws_json_1_0(
120
- data, message
120
+ data, message, response=response
121
121
  )
122
122
  case "UnsupportedActionException":
123
123
  raise capo_cloudcontrol.errors.unsupported_action_exception.UnsupportedActionException.from_aws_json_1_0(
124
- data, message
124
+ data, message, response=response
125
125
  )
126
126
  case _:
127
127
  raise UnknownServiceError(code=code, message=message, response=response)
@@ -49,79 +49,79 @@ def handle_error(response: zapros.Response) -> Never:
49
49
  match code:
50
50
  case "AlreadyExistsException":
51
51
  raise capo_cloudcontrol.errors.already_exists_exception.AlreadyExistsException.from_aws_json_1_0(
52
- data, message
52
+ data, message, response=response
53
53
  )
54
54
  case "ClientTokenConflictException":
55
55
  raise capo_cloudcontrol.errors.client_token_conflict_exception.ClientTokenConflictException.from_aws_json_1_0(
56
- data, message
56
+ data, message, response=response
57
57
  )
58
58
  case "ConcurrentOperationException":
59
59
  raise capo_cloudcontrol.errors.concurrent_operation_exception.ConcurrentOperationException.from_aws_json_1_0(
60
- data, message
60
+ data, message, response=response
61
61
  )
62
62
  case "GeneralServiceException":
63
63
  raise capo_cloudcontrol.errors.general_service_exception.GeneralServiceException.from_aws_json_1_0(
64
- data, message
64
+ data, message, response=response
65
65
  )
66
66
  case "HandlerFailureException":
67
67
  raise capo_cloudcontrol.errors.handler_failure_exception.HandlerFailureException.from_aws_json_1_0(
68
- data, message
68
+ data, message, response=response
69
69
  )
70
70
  case "HandlerInternalFailureException":
71
71
  raise capo_cloudcontrol.errors.handler_internal_failure_exception.HandlerInternalFailureException.from_aws_json_1_0(
72
- data, message
72
+ data, message, response=response
73
73
  )
74
74
  case "InvalidCredentialsException":
75
75
  raise capo_cloudcontrol.errors.invalid_credentials_exception.InvalidCredentialsException.from_aws_json_1_0(
76
- data, message
76
+ data, message, response=response
77
77
  )
78
78
  case "InvalidRequestException":
79
79
  raise capo_cloudcontrol.errors.invalid_request_exception.InvalidRequestException.from_aws_json_1_0(
80
- data, message
80
+ data, message, response=response
81
81
  )
82
82
  case "NetworkFailureException":
83
83
  raise capo_cloudcontrol.errors.network_failure_exception.NetworkFailureException.from_aws_json_1_0(
84
- data, message
84
+ data, message, response=response
85
85
  )
86
86
  case "NotStabilizedException":
87
87
  raise capo_cloudcontrol.errors.not_stabilized_exception.NotStabilizedException.from_aws_json_1_0(
88
- data, message
88
+ data, message, response=response
89
89
  )
90
90
  case "NotUpdatableException":
91
91
  raise capo_cloudcontrol.errors.not_updatable_exception.NotUpdatableException.from_aws_json_1_0(
92
- data, message
92
+ data, message, response=response
93
93
  )
94
94
  case "PrivateTypeException":
95
95
  raise capo_cloudcontrol.errors.private_type_exception.PrivateTypeException.from_aws_json_1_0(
96
- data, message
96
+ data, message, response=response
97
97
  )
98
98
  case "ResourceConflictException":
99
99
  raise capo_cloudcontrol.errors.resource_conflict_exception.ResourceConflictException.from_aws_json_1_0(
100
- data, message
100
+ data, message, response=response
101
101
  )
102
102
  case "ResourceNotFoundException":
103
103
  raise capo_cloudcontrol.errors.resource_not_found_exception.ResourceNotFoundException.from_aws_json_1_0(
104
- data, message
104
+ data, message, response=response
105
105
  )
106
106
  case "ServiceInternalErrorException":
107
107
  raise capo_cloudcontrol.errors.service_internal_error_exception.ServiceInternalErrorException.from_aws_json_1_0(
108
- data, message
108
+ data, message, response=response
109
109
  )
110
110
  case "ServiceLimitExceededException":
111
111
  raise capo_cloudcontrol.errors.service_limit_exceeded_exception.ServiceLimitExceededException.from_aws_json_1_0(
112
- data, message
112
+ data, message, response=response
113
113
  )
114
114
  case "ThrottlingException":
115
115
  raise capo_cloudcontrol.errors.throttling_exception.ThrottlingException.from_aws_json_1_0(
116
- data, message
116
+ data, message, response=response
117
117
  )
118
118
  case "TypeNotFoundException":
119
119
  raise capo_cloudcontrol.errors.type_not_found_exception.TypeNotFoundException.from_aws_json_1_0(
120
- data, message
120
+ data, message, response=response
121
121
  )
122
122
  case "UnsupportedActionException":
123
123
  raise capo_cloudcontrol.errors.unsupported_action_exception.UnsupportedActionException.from_aws_json_1_0(
124
- data, message
124
+ data, message, response=response
125
125
  )
126
126
  case _:
127
127
  raise UnknownServiceError(code=code, message=message, response=response)
@@ -47,71 +47,71 @@ def handle_error(response: zapros.Response) -> Never:
47
47
  match code:
48
48
  case "AlreadyExistsException":
49
49
  raise capo_cloudcontrol.errors.already_exists_exception.AlreadyExistsException.from_aws_json_1_0(
50
- data, message
50
+ data, message, response=response
51
51
  )
52
52
  case "GeneralServiceException":
53
53
  raise capo_cloudcontrol.errors.general_service_exception.GeneralServiceException.from_aws_json_1_0(
54
- data, message
54
+ data, message, response=response
55
55
  )
56
56
  case "HandlerFailureException":
57
57
  raise capo_cloudcontrol.errors.handler_failure_exception.HandlerFailureException.from_aws_json_1_0(
58
- data, message
58
+ data, message, response=response
59
59
  )
60
60
  case "HandlerInternalFailureException":
61
61
  raise capo_cloudcontrol.errors.handler_internal_failure_exception.HandlerInternalFailureException.from_aws_json_1_0(
62
- data, message
62
+ data, message, response=response
63
63
  )
64
64
  case "InvalidCredentialsException":
65
65
  raise capo_cloudcontrol.errors.invalid_credentials_exception.InvalidCredentialsException.from_aws_json_1_0(
66
- data, message
66
+ data, message, response=response
67
67
  )
68
68
  case "InvalidRequestException":
69
69
  raise capo_cloudcontrol.errors.invalid_request_exception.InvalidRequestException.from_aws_json_1_0(
70
- data, message
70
+ data, message, response=response
71
71
  )
72
72
  case "NetworkFailureException":
73
73
  raise capo_cloudcontrol.errors.network_failure_exception.NetworkFailureException.from_aws_json_1_0(
74
- data, message
74
+ data, message, response=response
75
75
  )
76
76
  case "NotStabilizedException":
77
77
  raise capo_cloudcontrol.errors.not_stabilized_exception.NotStabilizedException.from_aws_json_1_0(
78
- data, message
78
+ data, message, response=response
79
79
  )
80
80
  case "NotUpdatableException":
81
81
  raise capo_cloudcontrol.errors.not_updatable_exception.NotUpdatableException.from_aws_json_1_0(
82
- data, message
82
+ data, message, response=response
83
83
  )
84
84
  case "PrivateTypeException":
85
85
  raise capo_cloudcontrol.errors.private_type_exception.PrivateTypeException.from_aws_json_1_0(
86
- data, message
86
+ data, message, response=response
87
87
  )
88
88
  case "ResourceConflictException":
89
89
  raise capo_cloudcontrol.errors.resource_conflict_exception.ResourceConflictException.from_aws_json_1_0(
90
- data, message
90
+ data, message, response=response
91
91
  )
92
92
  case "ResourceNotFoundException":
93
93
  raise capo_cloudcontrol.errors.resource_not_found_exception.ResourceNotFoundException.from_aws_json_1_0(
94
- data, message
94
+ data, message, response=response
95
95
  )
96
96
  case "ServiceInternalErrorException":
97
97
  raise capo_cloudcontrol.errors.service_internal_error_exception.ServiceInternalErrorException.from_aws_json_1_0(
98
- data, message
98
+ data, message, response=response
99
99
  )
100
100
  case "ServiceLimitExceededException":
101
101
  raise capo_cloudcontrol.errors.service_limit_exceeded_exception.ServiceLimitExceededException.from_aws_json_1_0(
102
- data, message
102
+ data, message, response=response
103
103
  )
104
104
  case "ThrottlingException":
105
105
  raise capo_cloudcontrol.errors.throttling_exception.ThrottlingException.from_aws_json_1_0(
106
- data, message
106
+ data, message, response=response
107
107
  )
108
108
  case "TypeNotFoundException":
109
109
  raise capo_cloudcontrol.errors.type_not_found_exception.TypeNotFoundException.from_aws_json_1_0(
110
- data, message
110
+ data, message, response=response
111
111
  )
112
112
  case "UnsupportedActionException":
113
113
  raise capo_cloudcontrol.errors.unsupported_action_exception.UnsupportedActionException.from_aws_json_1_0(
114
- data, message
114
+ data, message, response=response
115
115
  )
116
116
  case _:
117
117
  raise UnknownServiceError(code=code, message=message, response=response)
@@ -32,7 +32,7 @@ def handle_error(response: zapros.Response) -> Never:
32
32
  match code:
33
33
  case "RequestTokenNotFoundException":
34
34
  raise capo_cloudcontrol.errors.request_token_not_found_exception.RequestTokenNotFoundException.from_aws_json_1_0(
35
- data, message
35
+ data, message, response=response
36
36
  )
37
37
  case _:
38
38
  raise UnknownServiceError(code=code, message=message, response=response)