qontract-reconcile 0.10.2.dev125__py3-none-any.whl → 0.10.2.dev127__py3-none-any.whl

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qontract-reconcile
3
- Version: 0.10.2.dev125
3
+ Version: 0.10.2.dev127
4
4
  Summary: Collection of tools to reconcile services with their desired state as defined in the app-interface DB.
5
5
  Project-URL: homepage, https://github.com/app-sre/qontract-reconcile
6
6
  Project-URL: repository, https://github.com/app-sre/qontract-reconcile
@@ -144,10 +144,10 @@ reconcile/automated_actions/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQe
144
144
  reconcile/automated_actions/config/integration.py,sha256=cf1VxolcjlGP-xO7IIH5A00Qr4znNJTT7a_sU18ABig,10755
145
145
  reconcile/aws_account_manager/README.md,sha256=_XFM3GZNHUzv--e_navqJuaUWpjC6QrHfulreHynFf0,262
146
146
  reconcile/aws_account_manager/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
147
- reconcile/aws_account_manager/integration.py,sha256=v-l4fdWTgX_vSYiBKAfDvT_g0_ZKNPDQnDKUyhrSzIs,15143
147
+ reconcile/aws_account_manager/integration.py,sha256=o-6NCzub7apFgpCGaH4uIg5wJKMT3flBEfFlq45G1AQ,15207
148
148
  reconcile/aws_account_manager/merge_request_manager.py,sha256=q8t-YwD4y_4UpxdoG9TQrEbDpjlzasOQIynFoCjP2OE,3947
149
149
  reconcile/aws_account_manager/metrics.py,sha256=YB10ea4kIGwJfs5N14RF-RoXPb-QQWaDBz1jLZ3YWE0,917
150
- reconcile/aws_account_manager/reconciler.py,sha256=8mwwcWVVNoUzmttzxgnLyePqN823v1t_dQCNCrx1mG0,15035
150
+ reconcile/aws_account_manager/reconciler.py,sha256=A3pYZiLX8QZTyGqQLuVvTCL1AmVQvNpor5qPIE-npiA,17025
151
151
  reconcile/aws_account_manager/utils.py,sha256=iYPPOtbZ7FiKkz9v5f1YXRIHw5YFOtSavUkF8oMwfJY,1439
152
152
  reconcile/aws_ami_cleanup/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
153
153
  reconcile/aws_ami_cleanup/integration.py,sha256=KG7g9NpbKmoaveDD3oi9SinqUE29NaM-4lGo-6YuHlM,9302
@@ -227,7 +227,7 @@ reconcile/glitchtip_project_alerts/integration.py,sha256=BgMx-NyV9mTuv7Sotb2OioC
227
227
  reconcile/glitchtip_project_dsn/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
228
228
  reconcile/glitchtip_project_dsn/integration.py,sha256=2iugub-kHYkHNK33n0v9_TeWonuxCPah_VkoTPvaajE,8077
229
229
  reconcile/gql_definitions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
230
- reconcile/gql_definitions/introspection.json,sha256=eHbVnokhdU5AjF1vnm2I_zHzU7-eH4rozjv45iZgHPs,2282580
230
+ reconcile/gql_definitions/introspection.json,sha256=AK7hGfvHjHL6Nqg3KNL4dmOiy4rOPkDSZ8zAWeA5kqU,2283098
231
231
  reconcile/gql_definitions/acs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
232
232
  reconcile/gql_definitions/acs/acs_instances.py,sha256=L91WW9LbhJbBSrECqShQpFtjoBOsmNIYLRpMbx1io5o,2181
233
233
  reconcile/gql_definitions/acs/acs_policies.py,sha256=bN5i4mks10Z23KJSj7jqp966Osq2dps4d-sPH9gjxEA,7008
@@ -243,7 +243,7 @@ reconcile/gql_definitions/app_sre_tekton_access_revalidation/users.py,sha256=XdV
243
243
  reconcile/gql_definitions/automated_actions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
244
244
  reconcile/gql_definitions/automated_actions/instance.py,sha256=D50v0exLeK5hvjQKvxQ5V-6DnYZo_L6cX_Gaf3PGREs,5491
245
245
  reconcile/gql_definitions/aws_account_manager/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
246
- reconcile/gql_definitions/aws_account_manager/aws_accounts.py,sha256=YnS_CDwKMNCxE5ySaKC_72pG9_WmW3nz8ZyyCzX1yV4,5119
246
+ reconcile/gql_definitions/aws_account_manager/aws_accounts.py,sha256=vF51KrY2gwX0J9vESiaRMPQqdAMEtz9f_tBq52bInp0,5148
247
247
  reconcile/gql_definitions/aws_ami_cleanup/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
248
248
  reconcile/gql_definitions/aws_ami_cleanup/aws_accounts.py,sha256=jIgOa888MYLLvVsn1ir3nbkhWLG5T6dBg7oDnp1q8BI,4108
249
249
  reconcile/gql_definitions/aws_saml_idp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -314,7 +314,7 @@ reconcile/gql_definitions/endpoints_discovery/apps.py,sha256=aBWRAwDUJQ32ghJS4cP
314
314
  reconcile/gql_definitions/external_resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
315
315
  reconcile/gql_definitions/external_resources/aws_accounts.py,sha256=XR69j9dpTQ0gv8y-AZN7AJ0dPvO-wbHscyCDgrax6Bk,2046
316
316
  reconcile/gql_definitions/external_resources/external_resources_modules.py,sha256=JViHtDWEBwjStBUo_bUdm_sxdpjCHcoATeFvwFRLQpU,3009
317
- reconcile/gql_definitions/external_resources/external_resources_namespaces.py,sha256=qGslq_4LdKd0jt8-TYySBU9c6rTTPVxG613xLSDFxBQ,44622
317
+ reconcile/gql_definitions/external_resources/external_resources_namespaces.py,sha256=4omzQPYGofq1HgCrmh_ToouVCIX_Cz967H_8wpZjOBc,44740
318
318
  reconcile/gql_definitions/external_resources/external_resources_settings.py,sha256=WBkJqnoyYCe1Vimwbp_Pa0RdyTdmWNf6oEWyA749QzA,3589
319
319
  reconcile/gql_definitions/external_resources/fragments/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
320
320
  reconcile/gql_definitions/external_resources/fragments/external_resources_module_overrides.py,sha256=T_qWCRtzU8F9frebBXG9TkeQdrKGt3R9YinSngPoFqM,1262
@@ -323,7 +323,7 @@ reconcile/gql_definitions/fleet_labeler/fleet_labels.py,sha256=XPk1YFmiCtGlwrldx
323
323
  reconcile/gql_definitions/fragments/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
324
324
  reconcile/gql_definitions/fragments/aus_organization.py,sha256=uBKbTuBa3CZmTXR5HOcGhRcu2U9kM93KbYmoWTxcpB0,4767
325
325
  reconcile/gql_definitions/fragments/aws_account_common.py,sha256=3-7ZAP6GSff7Z2Syz2VQCLY4IySqBOSVmceaRiVNQpw,2385
326
- reconcile/gql_definitions/fragments/aws_account_managed.py,sha256=sM5J4TczyqpzhBFkX0IIQifRYvYpDIMY7pOhEDOUXCY,1789
326
+ reconcile/gql_definitions/fragments/aws_account_managed.py,sha256=V_9pH0wVza2sPyq8ckkabNMYIRZt6VW4Nixw_jAxaMc,1892
327
327
  reconcile/gql_definitions/fragments/aws_account_sso.py,sha256=ITR3PLz4Iq1SiWAoYGWPDuHJnAmTyZ0QQqs2Zsi8pxA,979
328
328
  reconcile/gql_definitions/fragments/aws_infra_management_account.py,sha256=uAmALVRF2gBM3p_Dmez_ew6KVAtetamwOPkRIPZAlGc,1254
329
329
  reconcile/gql_definitions/fragments/aws_vpc.py,sha256=T2egTwi2Rb0IRBBmsyag8xKpu_m6GbIAy80fhZNZwk8,1434
@@ -663,7 +663,7 @@ reconcile/utils/acs/notifiers.py,sha256=nHVw9C_2-K4nv5zq26jlOTSw1roY6TKlovi1sOfp
663
663
  reconcile/utils/acs/policies.py,sha256=jpbi3qpGkBD_X6MfzsX12dPajUbmACmhIOz_0rDvYzs,5489
664
664
  reconcile/utils/acs/rbac.py,sha256=ugsLM9Pb7FbUbdq85E3VzXGMaB9ZovXob7tdWCxwqZ8,8808
665
665
  reconcile/utils/aws_api_typed/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
666
- reconcile/utils/aws_api_typed/account.py,sha256=puYfDsWF1E1Ub5e8yh0Hqz-whqERGn819N08i-g4zgY,626
666
+ reconcile/utils/aws_api_typed/account.py,sha256=f1UQul336_9rC_iCdzkbTQkhcFh5BjIZsf3X18SACfY,2048
667
667
  reconcile/utils/aws_api_typed/api.py,sha256=1h_dQXnE7FUAZi4RRgBEU7nbyAz8awRo0cpuilXyhHE,9239
668
668
  reconcile/utils/aws_api_typed/dynamodb.py,sha256=AKUbz8HGzmSq4cnpjJe7PgqsikMkjbpbzUD2UJv2b58,383
669
669
  reconcile/utils/aws_api_typed/iam.py,sha256=a9lFtwqj6YJM1_W4fi2U3y7N0fX8TTPg08JjQwWPTio,2251
@@ -791,7 +791,7 @@ tools/saas_promotion_state/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJ
791
791
  tools/saas_promotion_state/saas_promotion_state.py,sha256=UfwwRLS5Ya4_Nh1w5n1dvoYtchQvYE9yj1VANt2IKqI,3925
792
792
  tools/sre_checkpoints/__init__.py,sha256=CDaDaywJnmRCLyl_NCcvxi-Zc0hTi_3OdwKiFOyS39I,145
793
793
  tools/sre_checkpoints/util.py,sha256=zEDbGr18ZeHNQwW8pUsr2JRjuXIPz--WAGJxZo9sv_Y,894
794
- qontract_reconcile-0.10.2.dev125.dist-info/METADATA,sha256=0CAmKlRzqxs2IzOmfBggloZaP381t54Ava7CEdNQ3AI,24566
795
- qontract_reconcile-0.10.2.dev125.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
796
- qontract_reconcile-0.10.2.dev125.dist-info/entry_points.txt,sha256=5i9l54La3vQrDLAdwDKQWC0iG4sV9RRfOb1BpvzOWLc,698
797
- qontract_reconcile-0.10.2.dev125.dist-info/RECORD,,
794
+ qontract_reconcile-0.10.2.dev127.dist-info/METADATA,sha256=MZa1Z75ScJTRAg6c6E1GS6exq7gFyWYwIoxaIk8m5A0,24566
795
+ qontract_reconcile-0.10.2.dev127.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
796
+ qontract_reconcile-0.10.2.dev127.dist-info/entry_points.txt,sha256=5i9l54La3vQrDLAdwDKQWC0iG4sV9RRfOb1BpvzOWLc,698
797
+ qontract_reconcile-0.10.2.dev127.dist-info/RECORD,,
@@ -227,6 +227,7 @@ class AwsAccountMgmtIntegration(
227
227
  alias=account.alias,
228
228
  quotas=[q for ql in account.quota_limits or [] for q in ql.quotas],
229
229
  security_contact=account.security_contact,
230
+ regions=account.supported_deployment_regions or [],
230
231
  )
231
232
 
232
233
  def reconcile_payer_accounts(
@@ -4,6 +4,7 @@ from textwrap import dedent
4
4
  from typing import Any, Protocol
5
5
 
6
6
  from reconcile.aws_account_manager.utils import state_key
7
+ from reconcile.utils.aws_api_typed.account import OptStatus
7
8
  from reconcile.utils.aws_api_typed.api import AWSApi
8
9
  from reconcile.utils.aws_api_typed.iam import (
9
10
  AWSAccessKey,
@@ -26,6 +27,7 @@ TASK_CHECK_SERVICE_QUOTA_STATUS = "check-service-quota-status"
26
27
  TASK_ENABLE_ENTERPRISE_SUPPORT = "enable-enterprise-support"
27
28
  TASK_CHECK_ENTERPRISE_SUPPORT_STATUS = "check-enterprise-support-status"
28
29
  TASK_SET_SECURITY_CONTACT = "set-security-contact"
30
+ TASK_SET_SUPPORTED_REGIONS = "set-supported-regions"
29
31
 
30
32
 
31
33
  class Quota(Protocol):
@@ -62,7 +64,7 @@ class AWSReconciler:
62
64
  # account already exists, nothing to do
63
65
  return _state.value
64
66
 
65
- logging.info(f"Creating account {name}")
67
+ logging.info(f"{name}: Creating account")
66
68
  if self.dry_run:
67
69
  raise AbortStateTransaction("Dry run")
68
70
 
@@ -83,7 +85,7 @@ class AWSReconciler:
83
85
  # account checked and exists, nothing to do
84
86
  return _state.value
85
87
 
86
- logging.info(f"Checking account creation status {name}")
88
+ logging.info(f"{name}: Checking account creation status")
87
89
  status = aws_api.organizations.describe_create_account_status(
88
90
  create_account_request_id=create_account_request_id
89
91
  )
@@ -110,7 +112,7 @@ class AWSReconciler:
110
112
  # account already tagged, nothing to do
111
113
  return
112
114
 
113
- logging.info(f"Tagging account {name}: {tags}")
115
+ logging.info(f"{name}: Setting tags {tags}")
114
116
  _state.value = tags
115
117
  if self.dry_run:
116
118
  raise AbortStateTransaction("Dry run")
@@ -133,7 +135,7 @@ class AWSReconciler:
133
135
  # account already moved, nothing to do
134
136
  return
135
137
 
136
- logging.info(f"Moving account {name} to {ou}")
138
+ logging.info(f"{name}: Moving account to OU {ou}")
137
139
  destination = self._get_destination_ou(aws_api, destination_path=ou)
138
140
  if self.dry_run:
139
141
  raise AbortStateTransaction("Dry run")
@@ -153,7 +155,7 @@ class AWSReconciler:
153
155
  if _state.exists and _state.value == new_alias:
154
156
  return
155
157
 
156
- logging.info(f"Set account alias '{new_alias}' for {name}")
158
+ logging.info(f"{name}: Set account alias '{new_alias}'")
157
159
  if self.dry_run:
158
160
  raise AbortStateTransaction("Dry run")
159
161
 
@@ -179,7 +181,7 @@ class AWSReconciler:
179
181
  if quota.value > q.value:
180
182
  # a quota can be already higher than requested, because it was may set manually or enforced by the payer account
181
183
  logging.info(
182
- f"Cannot lower quota {q.service_code=}, {q.quota_code=}: {quota.value} -> {q.value}. Skipping."
184
+ f"{name}: Cannot lower quota {q.service_code=}, {q.quota_code=}: {quota.value} -> {q.value}. Skipping."
183
185
  )
184
186
  elif quota.value < q.value:
185
187
  quota.value = q.value
@@ -187,7 +189,7 @@ class AWSReconciler:
187
189
 
188
190
  for q in new_quotas:
189
191
  logging.info(
190
- f"Setting quota for {name}: {q.service_name}/{q.quota_name} ({q.service_code}/{q.quota_code}) -> {q.value}"
192
+ f"{name}: Setting quota: {q.service_name}/{q.quota_name} ({q.service_code}/{q.quota_code}) -> {q.value}"
191
193
  )
192
194
 
193
195
  if self.dry_run:
@@ -203,7 +205,7 @@ class AWSReconciler:
203
205
  )
204
206
  except AWSResourceAlreadyExistsException:
205
207
  raise AbortStateTransaction(
206
- f"A quota increase for this {new_quota.service_code}/{new_quota.quota_code} already exists. Try it again later."
208
+ f"{name}: A quota increase for this {new_quota.service_code}/{new_quota.quota_code} already exists. Try it again later."
207
209
  ) from None
208
210
  ids.append(req.id)
209
211
 
@@ -223,7 +225,7 @@ class AWSReconciler:
223
225
  if _state.exists and _state.value == request_ids:
224
226
  return
225
227
 
226
- logging.info(f"Checking quota change requests for {name}")
228
+ logging.info(f"{name}: Checking quota change requests")
227
229
  if self.dry_run:
228
230
  raise AbortStateTransaction("Dry run")
229
231
 
@@ -258,7 +260,7 @@ class AWSReconciler:
258
260
  raise AbortStateTransaction("Dry run")
259
261
  return None
260
262
 
261
- logging.info(f"Enabling enterprise support for {name}")
263
+ logging.info(f"{name}: Enabling enterprise support")
262
264
  if self.dry_run:
263
265
  raise AbortStateTransaction("Dry run")
264
266
 
@@ -277,7 +279,9 @@ class AWSReconciler:
277
279
  _state.value = case_id
278
280
  return case_id
279
281
 
280
- def _check_enterprise_support_status(self, aws_api: AWSApi, case_id: str) -> None:
282
+ def _check_enterprise_support_status(
283
+ self, aws_api: AWSApi, name: str, case_id: str
284
+ ) -> None:
281
285
  """Check the status of the enterprise support case."""
282
286
  with self.state.transaction(
283
287
  state_key(case_id, TASK_CHECK_ENTERPRISE_SUPPORT_STATUS), True
@@ -285,7 +289,7 @@ class AWSReconciler:
285
289
  if _state.exists:
286
290
  return
287
291
 
288
- logging.info(f"Checking enterprise support case {case_id}")
292
+ logging.info(f"{name}: Checking enterprise support case {case_id}")
289
293
  if self.dry_run:
290
294
  raise AbortStateTransaction("Dry run")
291
295
 
@@ -316,7 +320,7 @@ class AWSReconciler:
316
320
  if _state.exists and _state.value == security_contact:
317
321
  return
318
322
 
319
- logging.info(f"Setting security contact for {account}")
323
+ logging.info(f"{name}: Setting security contact")
320
324
  if self.dry_run:
321
325
  raise AbortStateTransaction("Dry run")
322
326
 
@@ -325,6 +329,55 @@ class AWSReconciler:
325
329
  )
326
330
  _state.value = security_contact
327
331
 
332
+ def _set_supported_regions(
333
+ self,
334
+ aws_api: AWSApi,
335
+ name: str,
336
+ regions: Iterable[str],
337
+ ) -> None:
338
+ """Set the supported regions for the account."""
339
+ with self.state.transaction(
340
+ state_key(name, TASK_SET_SUPPORTED_REGIONS)
341
+ ) as _state:
342
+ if _state.exists and _state.value == regions:
343
+ return
344
+
345
+ aws_regions = aws_api.account.list_regions()
346
+ if invalid_regions := set(regions) - {r.name for r in aws_regions}:
347
+ raise RuntimeError(
348
+ f"{name}: Regions {invalid_regions} are not available"
349
+ )
350
+
351
+ # ATTENTION: Regions can be "enabled by default".
352
+ # That means we cannot enable or disable them manually. We just gently ignore them!
353
+ to_enable_regions = [
354
+ r.name
355
+ for r in aws_regions
356
+ if r.status == OptStatus.DISABLED and r.name in regions
357
+ ]
358
+
359
+ to_disable_regions = [
360
+ r.name
361
+ for r in aws_regions
362
+ if r.status == OptStatus.ENABLED and r.name not in regions
363
+ ]
364
+
365
+ if to_enable_regions:
366
+ logging.info(f"{name}: Enabling regions {to_enable_regions}")
367
+
368
+ if to_disable_regions:
369
+ logging.info(f"{name}: Disabling regions {to_disable_regions}")
370
+
371
+ if self.dry_run:
372
+ raise AbortStateTransaction("Dry run")
373
+
374
+ for aws_region in to_enable_regions:
375
+ aws_api.account.enable_region(aws_region)
376
+ for aws_region in to_disable_regions:
377
+ aws_api.account.disable_region(aws_region)
378
+
379
+ _state.value = regions
380
+
328
381
  #
329
382
  # Public methods
330
383
  #
@@ -353,7 +406,7 @@ class AWSReconciler:
353
406
  if _state.exists and _state.value == user_name:
354
407
  return None
355
408
 
356
- logging.info(f"Creating IAM user '{user_name}' for {name}")
409
+ logging.info(f"{name}: Creating IAM user '{user_name}'")
357
410
  if self.dry_run:
358
411
  raise AbortStateTransaction("Dry run")
359
412
 
@@ -379,7 +432,7 @@ class AWSReconciler:
379
432
  if enterprise_support and (
380
433
  case_id := self._enable_enterprise_support(aws_api, name, uid)
381
434
  ):
382
- self._check_enterprise_support_status(aws_api, case_id)
435
+ self._check_enterprise_support_status(aws_api, name, case_id)
383
436
 
384
437
  def reconcile_account(
385
438
  self,
@@ -388,6 +441,7 @@ class AWSReconciler:
388
441
  alias: str | None,
389
442
  quotas: Iterable[Quota],
390
443
  security_contact: Contact,
444
+ regions: Iterable[str],
391
445
  ) -> None:
392
446
  """Reconcile/update the AWS account. Return the initial user access key if a new user was created."""
393
447
  self._set_account_alias(aws_api, name, alias)
@@ -401,3 +455,4 @@ class AWSReconciler:
401
455
  email=security_contact.email,
402
456
  phone_number=security_contact.phone_number,
403
457
  )
458
+ self._set_supported_regions(aws_api, name, regions)
@@ -45,6 +45,7 @@ fragment AWSAccountManaged on AWSAccount_v1 {
45
45
  email
46
46
  phoneNumber
47
47
  }
48
+ supportedDeploymentRegions
48
49
  }
49
50
 
50
51
  fragment VaultSecret on VaultSecret_v1 {
@@ -117,6 +117,7 @@ query ExternalResourcesNamespaces {
117
117
  blue_green_deployment {
118
118
  enabled
119
119
  switchover
120
+ switchover_timeout
120
121
  delete
121
122
  target {
122
123
  allocated_storage
@@ -598,6 +599,7 @@ class RDSBlueGreenDeploymentTargetV1(ConfiguredBaseModel):
598
599
  class RDSBlueGreenDeploymentV1(ConfiguredBaseModel):
599
600
  enabled: Optional[bool] = Field(..., alias="enabled")
600
601
  switchover: Optional[bool] = Field(..., alias="switchover")
602
+ switchover_timeout: Optional[int] = Field(..., alias="switchover_timeout")
601
603
  delete: Optional[bool] = Field(..., alias="delete")
602
604
  target: Optional[RDSBlueGreenDeploymentTargetV1] = Field(..., alias="target")
603
605
 
@@ -55,3 +55,4 @@ class AWSAccountManaged(ConfiguredBaseModel):
55
55
  organization: Optional[AWSOrganizationV1] = Field(..., alias="organization")
56
56
  quota_limits: Optional[list[AWSQuotaLimitsV1]] = Field(..., alias="quotaLimits")
57
57
  security_contact: Optional[AWSContactV1] = Field(..., alias="securityContact")
58
+ supported_deployment_regions: Optional[list[str]] = Field(..., alias="supportedDeploymentRegions")
@@ -44387,6 +44387,18 @@
44387
44387
  "isDeprecated": false,
44388
44388
  "deprecationReason": null
44389
44389
  },
44390
+ {
44391
+ "name": "switchover_timeout",
44392
+ "description": null,
44393
+ "args": [],
44394
+ "type": {
44395
+ "kind": "SCALAR",
44396
+ "name": "Int",
44397
+ "ofType": null
44398
+ },
44399
+ "isDeprecated": false,
44400
+ "deprecationReason": null
44401
+ },
44390
44402
  {
44391
44403
  "name": "delete",
44392
44404
  "description": null,
@@ -1,3 +1,4 @@
1
+ from enum import StrEnum
1
2
  from typing import TYPE_CHECKING
2
3
 
3
4
  if TYPE_CHECKING:
@@ -5,6 +6,21 @@ if TYPE_CHECKING:
5
6
  else:
6
7
  AccountClient = object
7
8
 
9
+ from pydantic import BaseModel
10
+
11
+
12
+ class OptStatus(StrEnum):
13
+ """Optional status enum."""
14
+
15
+ ENABLED = "ENABLED"
16
+ DISABLED = "DISABLED"
17
+ ENABLED_BY_DEFAULT = "ENABLED_BY_DEFAULT"
18
+
19
+
20
+ class Region(BaseModel):
21
+ name: str
22
+ status: OptStatus
23
+
8
24
 
9
25
  class AWSApiAccount:
10
26
  def __init__(self, client: AccountClient) -> None:
@@ -21,3 +37,29 @@ class AWSApiAccount:
21
37
  Title=title,
22
38
  PhoneNumber=phone_number,
23
39
  )
40
+
41
+ def list_regions(self) -> list[Region]:
42
+ """List all regions in the account."""
43
+ regions = []
44
+ paginator = self.client.get_paginator("list_regions")
45
+ for page in paginator.paginate():
46
+ for region in page["Regions"]:
47
+ match region["RegionOptStatus"]:
48
+ case "DISABLED" | "DISABLING":
49
+ status = OptStatus.DISABLED
50
+ case "ENABLED" | "ENABLING":
51
+ status = OptStatus.ENABLED
52
+ case "ENABLED_BY_DEFAULT":
53
+ status = OptStatus.ENABLED_BY_DEFAULT
54
+ case _:
55
+ raise ValueError(f"Unknown status: {region['RegionOptStatus']}")
56
+ regions.append(Region(name=region["RegionName"], status=status))
57
+ return regions
58
+
59
+ def enable_region(self, region: str) -> None:
60
+ """Enable a region in the account."""
61
+ self.client.enable_region(RegionName=region)
62
+
63
+ def disable_region(self, region: str) -> None:
64
+ """Disable a region in the account."""
65
+ self.client.disable_region(RegionName=region)