qontract-reconcile 0.10.1rc880__py3-none-any.whl → 0.10.1rc882__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.1
2
2
  Name: qontract-reconcile
3
- Version: 0.10.1rc880
3
+ Version: 0.10.1rc882
4
4
  Summary: Collection of tools to reconcile services with their desired state as defined in the app-interface DB.
5
5
  Home-page: https://github.com/app-sre/qontract-reconcile
6
6
  Author: Red Hat App-SRE Team
@@ -61,7 +61,7 @@ reconcile/ocm_groups.py,sha256=CluPyvmwE5JOZS2HQSReC1sD8L1ChhnJlAg8lcwdtxc,3395
61
61
  reconcile/ocm_machine_pools.py,sha256=oY4oLPm5Y_ajBV8KFg2LuBQvsZl-CxTjSxEyxg4b2OI,16634
62
62
  reconcile/ocm_update_recommended_version.py,sha256=IYkfLXIprOW1jguZeELcGP1iBPuj-b53R-FTqKulMl8,4204
63
63
  reconcile/ocm_upgrade_scheduler_org_updater.py,sha256=sQSicCyU-TPtIBYsSn97qzYiQLFaMmOMSFr1OP6tOck,4308
64
- reconcile/openshift_base.py,sha256=mE_NuKgEFsZDeA3kUo4iMsvIF1wN5yemUHeuE0M9fbY,49735
64
+ reconcile/openshift_base.py,sha256=xM14RxKYqbFQZH3kTHcvh8gEXNPpNCWZ4-SiVvFP09U,49797
65
65
  reconcile/openshift_cluster_bots.py,sha256=eRPYZqWMKFNxLlSN0QG97V5t1iIESQ0BbGaiaQP5VB0,10940
66
66
  reconcile/openshift_clusterrolebindings.py,sha256=QfSy1Ik8eEY5XObc1Q4xyhqyErZenJmbPv_u9wcDNNo,5864
67
67
  reconcile/openshift_groups.py,sha256=fqBQ6ITDOArkC_zOjskmMigdCUDq3wntLZ0NcppXaFc,9414
@@ -138,11 +138,11 @@ reconcile/aus/version_gates/ingress_gate_handler.py,sha256=ZCtyggBzzcb0prtdbMpJs
138
138
  reconcile/aus/version_gates/ocp_gate_handler.py,sha256=RW1ppDaCZXVegV9AzzqYXxDUu_Z_7d43Z5h2Pk_piKc,716
139
139
  reconcile/aus/version_gates/sts_version_gate_handler.py,sha256=PhJ7yBh2q-rv9CJcfFhc0H11nyDyG7NAryNS3F74xdY,3697
140
140
  reconcile/aws_account_manager/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
141
- reconcile/aws_account_manager/integration.py,sha256=FRZ1_jaK4maY4ClJmVGB7dCkDxZCaQP4yMDlYF-Lc3E,15042
141
+ reconcile/aws_account_manager/integration.py,sha256=60tMmQNAsc5BGqJh3v3sOrnIjfTPRG2rgPxwAbCsPP4,15005
142
142
  reconcile/aws_account_manager/merge_request_manager.py,sha256=zZct3NxWMBQupl4QfD7ULxnt4ipt_2FBoH_NusboIuw,3781
143
143
  reconcile/aws_account_manager/metrics.py,sha256=YB10ea4kIGwJfs5N14RF-RoXPb-QQWaDBz1jLZ3YWE0,917
144
- reconcile/aws_account_manager/reconciler.py,sha256=AqAA3TIEfuYzIogHSBgwYTebxbTy1D6JhcxdLiOfCsc,13588
145
- reconcile/aws_account_manager/utils.py,sha256=K4rAjEMK-eQ_Sv4lOf6dPynQy97xZ4h-n6cJn5Z6zVw,1248
144
+ reconcile/aws_account_manager/reconciler.py,sha256=5YG7nZ4GG1SY2Pe_OxetdtzOnl37MllZMu_ca22vRSo,15025
145
+ reconcile/aws_account_manager/utils.py,sha256=iYPPOtbZ7FiKkz9v5f1YXRIHw5YFOtSavUkF8oMwfJY,1439
146
146
  reconcile/aws_ami_cleanup/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
147
147
  reconcile/aws_ami_cleanup/integration.py,sha256=IW95cpMj2P5ffs-AxsR_TDQCJnYFBhLIfP2de7dz_8A,10109
148
148
  reconcile/aws_cloudwatch_log_retention/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -211,7 +211,7 @@ reconcile/gql_definitions/advanced_upgrade_service/aus_organization.py,sha256=zU
211
211
  reconcile/gql_definitions/app_interface_metrics_exporter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
212
212
  reconcile/gql_definitions/app_interface_metrics_exporter/onboarding_status.py,sha256=uVEEqU6YYmKsNTo6EWlFnoVmqha2rvBDx-wiD64VmG0,1679
213
213
  reconcile/gql_definitions/aws_account_manager/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
214
- reconcile/gql_definitions/aws_account_manager/aws_accounts.py,sha256=jJfIzTtDiW6rasv3PFEbyVHA0d8bfRYSVYP5HxslYnY,4649
214
+ reconcile/gql_definitions/aws_account_manager/aws_accounts.py,sha256=R-Au5ZErdKSWqANtpiayLikvMSj3ha9muWoX5FI7-AE,4718
215
215
  reconcile/gql_definitions/aws_ami_cleanup/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
216
216
  reconcile/gql_definitions/aws_ami_cleanup/asg_namespaces.py,sha256=OJmeTu7uirLGAysZ3IQTtRXqMyL8noi_QZxPuWYxxmI,3678
217
217
  reconcile/gql_definitions/aws_saml_idp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -281,7 +281,7 @@ reconcile/gql_definitions/external_resources/external_resources_settings.py,sha2
281
281
  reconcile/gql_definitions/fragments/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
282
282
  reconcile/gql_definitions/fragments/aus_organization.py,sha256=uBKbTuBa3CZmTXR5HOcGhRcu2U9kM93KbYmoWTxcpB0,4767
283
283
  reconcile/gql_definitions/fragments/aws_account_common.py,sha256=3-7ZAP6GSff7Z2Syz2VQCLY4IySqBOSVmceaRiVNQpw,2385
284
- reconcile/gql_definitions/fragments/aws_account_managed.py,sha256=zXbux0Bb7QZ37fU4LLMKB4m0rT3Y8bD10C1TuOsH_ZQ,1470
284
+ reconcile/gql_definitions/fragments/aws_account_managed.py,sha256=sM5J4TczyqpzhBFkX0IIQifRYvYpDIMY7pOhEDOUXCY,1789
285
285
  reconcile/gql_definitions/fragments/aws_account_sso.py,sha256=ITR3PLz4Iq1SiWAoYGWPDuHJnAmTyZ0QQqs2Zsi8pxA,979
286
286
  reconcile/gql_definitions/fragments/aws_infra_management_account.py,sha256=uAmALVRF2gBM3p_Dmez_ew6KVAtetamwOPkRIPZAlGc,1254
287
287
  reconcile/gql_definitions/fragments/aws_vpc.py,sha256=T2egTwi2Rb0IRBBmsyag8xKpu_m6GbIAy80fhZNZwk8,1434
@@ -674,7 +674,7 @@ reconcile/utils/make.py,sha256=QaEwucrzbl8-VHS66Wfdjfo0ubmAcvt_hZGpiGsKU50,231
674
674
  reconcile/utils/metrics.py,sha256=ot4dBO-KLZRowvNozm7jG0RWjcVsH1SL-lQ0jJgBBZM,18645
675
675
  reconcile/utils/models.py,sha256=It_Q1WNIvw_EDCsiSWzIgpSPr_X9jMgbJI-DR3N23xY,4677
676
676
  reconcile/utils/oauth2_backend_application_session.py,sha256=6W16sMpnWEPFDUX7qi5Cui2yOnmLfpgUxWtB3Ii35D0,4177
677
- reconcile/utils/oc.py,sha256=OB78zd4xzc80ZneENHEdVzeevFyq8a34hNmefwyBw6A,65612
677
+ reconcile/utils/oc.py,sha256=D8NhbFTIeIvqzPLAqTD6-fyU4tgJHc4ofnqaY2VI0Nw,65802
678
678
  reconcile/utils/oc_connection_parameters.py,sha256=85slrnDigYwYmzhyceVkMElWzFArp4ge1d-fHXVqh0w,9729
679
679
  reconcile/utils/oc_filters.py,sha256=R2Lf3fo0jQCeE62Ygeo_KN24XbAosq0QbjimYG6qHI4,1402
680
680
  reconcile/utils/oc_map.py,sha256=nT69J5pdPeIDnIYjD9fwY6GkE3BMQCf-AF0rmHJuUNw,9068
@@ -715,7 +715,8 @@ reconcile/utils/acs/notifiers.py,sha256=2n5blP9N1FdGLZuy3do9bpjd8NKg88kmLNNqhAGn
715
715
  reconcile/utils/acs/policies.py,sha256=_jAz6cv8KRYtDsXjGoJgNbD8_9PUa5LSwwVlpK4A_cQ,5505
716
716
  reconcile/utils/acs/rbac.py,sha256=ugsLM9Pb7FbUbdq85E3VzXGMaB9ZovXob7tdWCxwqZ8,8808
717
717
  reconcile/utils/aws_api_typed/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
718
- reconcile/utils/aws_api_typed/api.py,sha256=NJJXmV01nBtQLf-Vv5xfieboCsY3inNd4ekRKxS_-Wo,8790
718
+ reconcile/utils/aws_api_typed/account.py,sha256=puYfDsWF1E1Ub5e8yh0Hqz-whqERGn819N08i-g4zgY,626
719
+ reconcile/utils/aws_api_typed/api.py,sha256=1h_dQXnE7FUAZi4RRgBEU7nbyAz8awRo0cpuilXyhHE,9239
719
720
  reconcile/utils/aws_api_typed/dynamodb.py,sha256=AKUbz8HGzmSq4cnpjJe7PgqsikMkjbpbzUD2UJv2b58,383
720
721
  reconcile/utils/aws_api_typed/iam.py,sha256=ka46H2-SzTCgy6EJYapKTzyZK9vR1bkfD0wF8bDdy1Q,2201
721
722
  reconcile/utils/aws_api_typed/organization.py,sha256=oXftcLVuSs9qej6efdssl38FvjeZaQC5R2Wj3NzxX4U,5529
@@ -837,8 +838,8 @@ tools/test/test_app_interface_metrics_exporter.py,sha256=SX7qL3D1SIRKFo95FoQztvf
837
838
  tools/test/test_qontract_cli.py,sha256=_D61RFGAN5x44CY1tYbouhlGXXABwYfxKSWSQx3Jrss,4941
838
839
  tools/test/test_sd_app_sre_alert_report.py,sha256=v363r9zM7__0kR5K6mvJoGFcM9BvE33fWAayrqkpojA,2116
839
840
  tools/test/test_sre_checkpoints.py,sha256=SKqPPTl9ua0RFdSSofnoQX-JZE6dFLO3LRhfQzqtfh8,2607
840
- qontract_reconcile-0.10.1rc880.dist-info/METADATA,sha256=ILC7PTRwvnvvSHK3u1ku0vZpYEjG0uz5cAXZrSYr2w0,2273
841
- qontract_reconcile-0.10.1rc880.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
842
- qontract_reconcile-0.10.1rc880.dist-info/entry_points.txt,sha256=GKQqCl2j2X1BJQ69een6rHcR26PmnxnONLNOQB-nRjY,491
843
- qontract_reconcile-0.10.1rc880.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
844
- qontract_reconcile-0.10.1rc880.dist-info/RECORD,,
841
+ qontract_reconcile-0.10.1rc882.dist-info/METADATA,sha256=h86zL76A3OqZCu_IiZB3FL-aoKD2jRhyQV3yAgRGVI8,2273
842
+ qontract_reconcile-0.10.1rc882.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
843
+ qontract_reconcile-0.10.1rc882.dist-info/entry_points.txt,sha256=GKQqCl2j2X1BJQ69een6rHcR26PmnxnONLNOQB-nRjY,491
844
+ qontract_reconcile-0.10.1rc882.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
845
+ qontract_reconcile-0.10.1rc882.dist-info/RECORD,,
@@ -114,10 +114,8 @@ class AwsAccountMgmtIntegration(
114
114
  for account in data.accounts or []
115
115
  if integration_is_enabled(self.name, account)
116
116
  and (not account_name or account.name == account_name)
117
+ and validate(account)
117
118
  ]
118
- for account in all_aws_accounts:
119
- validate(account)
120
-
121
119
  payer_accounts = [
122
120
  account
123
121
  for account in all_aws_accounts
@@ -219,18 +217,16 @@ class AwsAccountMgmtIntegration(
219
217
  self.reconcile_account(account_role_api, reconciler, account)
220
218
 
221
219
  def reconcile_account(
222
- self,
223
- aws_api: AWSApi,
224
- reconciler: AWSReconciler,
225
- account: AWSAccountManaged,
226
- create_initial_user: bool = True,
220
+ self, aws_api: AWSApi, reconciler: AWSReconciler, account: AWSAccountManaged
227
221
  ) -> None:
228
222
  """Reconcile an AWS account."""
223
+ assert account.security_contact # mypy
229
224
  reconciler.reconcile_account(
230
225
  aws_api=aws_api,
231
226
  name=account.name,
232
227
  alias=account.alias,
233
228
  quotas=[q for ql in account.quota_limits or [] for q in ql.quotas],
229
+ security_contact=account.security_contact,
234
230
  )
235
231
 
236
232
  def reconcile_payer_accounts(
@@ -245,8 +241,7 @@ class AwsAccountMgmtIntegration(
245
241
  # reconcile accounts within payer accounts, aka organization accounts
246
242
  for payer_account in payer_accounts:
247
243
  # having a state per flavor and payer account makes it easier in a shared environment
248
- reconciler.state.state_path = default_state_path
249
- reconciler.state.state_path += f"/{payer_account.name}"
244
+ reconciler.state.state_path = f"{default_state_path}/{payer_account.name}"
250
245
  aws_account_manager_role = (
251
246
  payer_account.automation_role.aws_account_manager
252
247
  if payer_account.automation_role
@@ -290,8 +285,8 @@ class AwsAccountMgmtIntegration(
290
285
  ) -> None:
291
286
  """Reconcile accounts not part of an organization via a payer account (e.g. payer accounts themselves)"""
292
287
  for account in non_organization_accounts:
293
- reconciler.state.state_path = default_state_path
294
- reconciler.state.state_path += f"/{account.name}"
288
+ # the state must be account specific
289
+ reconciler.state.state_path = f"{default_state_path}/{account.name}"
295
290
  secret = self.secret_reader.read_all_secret(account.automation_token)
296
291
  with AWSApi(
297
292
  AWSStaticCredentials(
@@ -25,6 +25,7 @@ TASK_REQUEST_SERVICE_QUOTA = "request-service-quota"
25
25
  TASK_CHECK_SERVICE_QUOTA_STATUS = "check-service-quota-status"
26
26
  TASK_ENABLE_ENTERPRISE_SUPPORT = "enable-enterprise-support"
27
27
  TASK_CHECK_ENTERPRISE_SUPPORT_STATUS = "check-enterprise-support-status"
28
+ TASK_SET_SECURITY_CONTACT = "set-security-contact"
28
29
 
29
30
 
30
31
  class Quota(Protocol):
@@ -35,6 +36,15 @@ class Quota(Protocol):
35
36
  def dict(self) -> dict[str, Any]: ...
36
37
 
37
38
 
39
+ class Contact(Protocol):
40
+ name: str
41
+ title: str | None
42
+ email: str
43
+ phone_number: str
44
+
45
+ def dict(self) -> dict[str, Any]: ...
46
+
47
+
38
48
  class AWSReconciler:
39
49
  def __init__(self, state: State, dry_run: bool) -> None:
40
50
  self.state = state
@@ -288,6 +298,33 @@ class AWSReconciler:
288
298
  )
289
299
  raise AbortStateTransaction("Enterprise support case still open")
290
300
 
301
+ def _set_security_contact(
302
+ self,
303
+ aws_api: AWSApi,
304
+ account: str,
305
+ name: str,
306
+ title: str | None,
307
+ email: str,
308
+ phone_number: str,
309
+ ) -> None:
310
+ """Set the security contact for the account."""
311
+ title = title or name
312
+ security_contact = f"{name} {title} {email} {phone_number}"
313
+ with self.state.transaction(
314
+ state_key(account, TASK_SET_SECURITY_CONTACT)
315
+ ) as _state:
316
+ if _state.exists and _state.value == security_contact:
317
+ return
318
+
319
+ logging.info(f"Setting security contact for {account}")
320
+ if self.dry_run:
321
+ raise AbortStateTransaction("Dry run")
322
+
323
+ aws_api.account.set_security_contact(
324
+ name=name, title=title, email=email, phone_number=phone_number
325
+ )
326
+ _state.value = security_contact
327
+
291
328
  #
292
329
  # Public methods
293
330
  #
@@ -345,9 +382,22 @@ class AWSReconciler:
345
382
  self._check_enterprise_support_status(aws_api, case_id)
346
383
 
347
384
  def reconcile_account(
348
- self, aws_api: AWSApi, name: str, alias: str | None, quotas: Iterable[Quota]
385
+ self,
386
+ aws_api: AWSApi,
387
+ name: str,
388
+ alias: str | None,
389
+ quotas: Iterable[Quota],
390
+ security_contact: Contact,
349
391
  ) -> None:
350
392
  """Reconcile/update the AWS account. Return the initial user access key if a new user was created."""
351
393
  self._set_account_alias(aws_api, name, alias)
352
394
  if request_ids := self._request_quotas(aws_api, name, quotas):
353
395
  self._check_quota_change_requests(aws_api, name, request_ids)
396
+ self._set_security_contact(
397
+ aws_api,
398
+ account=name,
399
+ name=security_contact.name,
400
+ title=security_contact.title,
401
+ email=security_contact.email,
402
+ phone_number=security_contact.phone_number,
403
+ )
@@ -30,6 +30,9 @@ def validate(account: AWSAccountV1) -> bool:
30
30
  f"Premium support is required for payer account {account.name}"
31
31
  )
32
32
 
33
+ # security contact is mandatory for all accounts since June 2024
34
+ if not account.security_contact:
35
+ raise ValueError(f"Security contact is required for account {account.name}")
33
36
  return True
34
37
 
35
38
 
@@ -39,6 +39,12 @@ fragment AWSAccountManaged on AWSAccount_v1 {
39
39
  value
40
40
  }
41
41
  }
42
+ securityContact {
43
+ name
44
+ title
45
+ email
46
+ phoneNumber
47
+ }
42
48
  }
43
49
 
44
50
  fragment VaultSecret on VaultSecret_v1 {
@@ -40,6 +40,13 @@ class AWSQuotaLimitsV1(ConfiguredBaseModel):
40
40
  quotas: list[AWSQuotaV1] = Field(..., alias="quotas")
41
41
 
42
42
 
43
+ class AWSContactV1(ConfiguredBaseModel):
44
+ name: str = Field(..., alias="name")
45
+ title: Optional[str] = Field(..., alias="title")
46
+ email: str = Field(..., alias="email")
47
+ phone_number: str = Field(..., alias="phoneNumber")
48
+
49
+
43
50
  class AWSAccountManaged(ConfiguredBaseModel):
44
51
  name: str = Field(..., alias="name")
45
52
  uid: str = Field(..., alias="uid")
@@ -47,3 +54,4 @@ class AWSAccountManaged(ConfiguredBaseModel):
47
54
  premium_support: bool = Field(..., alias="premiumSupport")
48
55
  organization: Optional[AWSOrganizationV1] = Field(..., alias="organization")
49
56
  quota_limits: Optional[list[AWSQuotaLimitsV1]] = Field(..., alias="quotaLimits")
57
+ security_contact: Optional[AWSContactV1] = Field(..., alias="securityContact")
@@ -40,6 +40,7 @@ from reconcile.utils.oc import (
40
40
  OCClient,
41
41
  OCLogMsg,
42
42
  PrimaryClusterIPCanNotBeUnsetError,
43
+ RequestEntityTooLargeError,
43
44
  StatefulSetUpdateForbidden,
44
45
  StatusCodeError,
45
46
  UnsupportedMediaTypeError,
@@ -402,7 +403,7 @@ def apply(
402
403
 
403
404
  try:
404
405
  oc.apply(namespace, annotated)
405
- except InvalidValueApplyError:
406
+ except (InvalidValueApplyError, RequestEntityTooLargeError):
406
407
  oc.remove_last_applied_configuration(
407
408
  namespace, resource_type, resource.name
408
409
  )
@@ -0,0 +1,23 @@
1
+ from typing import TYPE_CHECKING
2
+
3
+ if TYPE_CHECKING:
4
+ from mypy_boto3_account import AccountClient
5
+ else:
6
+ AccountClient = object
7
+
8
+
9
+ class AWSApiAccount:
10
+ def __init__(self, client: AccountClient) -> None:
11
+ self.client = client
12
+
13
+ def set_security_contact(
14
+ self, name: str, title: str, email: str, phone_number: str
15
+ ) -> None:
16
+ """Set the security contact for the account."""
17
+ self.client.put_alternate_contact(
18
+ AlternateContactType="SECURITY",
19
+ EmailAddress=email,
20
+ Name=name,
21
+ Title=title,
22
+ PhoneNumber=phone_number,
23
+ )
@@ -9,6 +9,7 @@ from boto3 import Session
9
9
  from botocore.client import BaseClient
10
10
  from pydantic import BaseModel
11
11
 
12
+ import reconcile.utils.aws_api_typed.account
12
13
  import reconcile.utils.aws_api_typed.dynamodb
13
14
  import reconcile.utils.aws_api_typed.iam
14
15
  import reconcile.utils.aws_api_typed.organization
@@ -16,6 +17,7 @@ import reconcile.utils.aws_api_typed.s3
16
17
  import reconcile.utils.aws_api_typed.service_quotas
17
18
  import reconcile.utils.aws_api_typed.sts
18
19
  import reconcile.utils.aws_api_typed.support
20
+ from reconcile.utils.aws_api_typed.account import AWSApiAccount
19
21
  from reconcile.utils.aws_api_typed.dynamodb import AWSApiDynamoDB
20
22
  from reconcile.utils.aws_api_typed.iam import AWSApiIam
21
23
  from reconcile.utils.aws_api_typed.organization import AWSApiOrganizations
@@ -26,13 +28,14 @@ from reconcile.utils.aws_api_typed.support import AWSApiSupport
26
28
 
27
29
  SubApi = TypeVar(
28
30
  "SubApi",
31
+ AWSApiAccount,
32
+ AWSApiDynamoDB,
29
33
  AWSApiIam,
30
34
  AWSApiOrganizations,
31
35
  AWSApiS3,
32
36
  AWSApiServiceQuotas,
33
37
  AWSApiSts,
34
38
  AWSApiSupport,
35
- AWSApiDynamoDB,
36
39
  )
37
40
 
38
41
 
@@ -168,6 +171,12 @@ class AWSApi:
168
171
  def _init_sub_api(self, api_cls: type[SubApi]) -> SubApi:
169
172
  """Return a new or cached sub api client."""
170
173
  match api_cls:
174
+ case reconcile.utils.aws_api_typed.account.AWSApiAccount:
175
+ client = self.session.client("account")
176
+ api = api_cls(client)
177
+ case reconcile.utils.aws_api_typed.dynamodb.AWSApiDynamoDB:
178
+ client = self.session.client("dynamodb")
179
+ api = api_cls(client)
171
180
  case reconcile.utils.aws_api_typed.iam.AWSApiIam:
172
181
  client = self.session.client("iam")
173
182
  api = api_cls(client)
@@ -186,15 +195,22 @@ class AWSApi:
186
195
  case reconcile.utils.aws_api_typed.support.AWSApiSupport:
187
196
  client = self.session.client("support")
188
197
  api = api_cls(client)
189
- case reconcile.utils.aws_api_typed.dynamodb.AWSApiDynamoDB:
190
- client = self.session.client("dynamodb")
191
- api = api_cls(client)
192
198
  case _:
193
199
  raise ValueError(f"Unknown API class: {api_cls}")
194
200
 
195
201
  self._session_clients.append(client)
196
202
  return api
197
203
 
204
+ @cached_property
205
+ def account(self) -> AWSApiAccount:
206
+ """Return an AWS Acount Api client"""
207
+ return self._init_sub_api(AWSApiAccount)
208
+
209
+ @cached_property
210
+ def dynamodb(self) -> AWSApiDynamoDB:
211
+ """Return an AWS DynamoDB Api client"""
212
+ return self._init_sub_api(AWSApiDynamoDB)
213
+
198
214
  @cached_property
199
215
  def iam(self) -> AWSApiIam:
200
216
  """Return an AWS IAM Api client."""
@@ -225,11 +241,6 @@ class AWSApi:
225
241
  """Return an AWS Support Api client."""
226
242
  return self._init_sub_api(AWSApiSupport)
227
243
 
228
- @cached_property
229
- def dynamodb(self) -> AWSApiDynamoDB:
230
- """Return an AWS DynamoDB Api client"""
231
- return self._init_sub_api(AWSApiDynamoDB)
232
-
233
244
  def assume_role(self, account_id: str, role: str) -> AWSApi:
234
245
  """Return a new AWSApi with the assumed role."""
235
246
  credentials = self.sts.assume_role(account_id=account_id, role=role)
reconcile/utils/oc.py CHANGED
@@ -127,6 +127,10 @@ class JobNotRunningError(Exception):
127
127
  pass
128
128
 
129
129
 
130
+ class RequestEntityTooLargeError(Exception):
131
+ pass
132
+
133
+
130
134
  class OCDecorators:
131
135
  @classmethod
132
136
  def process_reconcile_time(cls, function):
@@ -1118,6 +1122,8 @@ class OCCli: # pylint: disable=too-many-public-methods
1118
1122
  raise StatefulSetUpdateForbidden(f"[{self.server}]: {err}")
1119
1123
  if "the object has been modified" in err:
1120
1124
  raise ObjectHasBeenModifiedError(f"[{self.server}]: {err}")
1125
+ if "Request entity too large" in err:
1126
+ raise RequestEntityTooLargeError(f"[{self.server}]: {err}")
1121
1127
  if not (allow_not_found and "NotFound" in err):
1122
1128
  raise StatusCodeError(f"[{self.server}]: {err}")
1123
1129