qontract-reconcile 0.10.1rc879__py3-none-any.whl → 0.10.1rc881__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.1rc879
3
+ Version: 0.10.1rc881
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
@@ -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
@@ -194,12 +194,12 @@ reconcile/external_resources/reconciler.py,sha256=E50X_lnOD0OWYXMzyZld1P6dCFJFYj
194
194
  reconcile/external_resources/secrets_sync.py,sha256=xFQ_ObWl29btbxzEJSkndvHBPcsVpSTkmUlWcUGzZ70,15132
195
195
  reconcile/external_resources/state.py,sha256=fA_CzT4oNie4wnaImwW-W1duWEOKFyS1omcnMyYwx2Q,9644
196
196
  reconcile/glitchtip/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
197
- reconcile/glitchtip/integration.py,sha256=Y7ofQg_xCt3dOln3pjeXp7rAnwohCgD2zcUAb-Hciis,8375
197
+ reconcile/glitchtip/integration.py,sha256=SCfdllg0cywCyLKCA66yUm9Z_Sb8t5E0jDEKdwRk6HI,8372
198
198
  reconcile/glitchtip/reconciler.py,sha256=nUvDv7qG1ly0cA16MmlL6NV71yl1mJYLT2mui7lmi0Y,12402
199
199
  reconcile/glitchtip_project_alerts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
200
- reconcile/glitchtip_project_alerts/integration.py,sha256=EJBZJldOyK_ECAfgr1RjxabtsVRIBbZcW-7JU4cwVsA,12429
200
+ reconcile/glitchtip_project_alerts/integration.py,sha256=HI_HgvTTc2K-8Md0SpEBs6tOZ3CQm-AbOatbw_NrKqg,12425
201
201
  reconcile/glitchtip_project_dsn/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
202
- reconcile/glitchtip_project_dsn/integration.py,sha256=qt2a33FOUUlCyonSHm3nUb7pNXgLAq8sv_JQCm6Vj3U,8113
202
+ reconcile/glitchtip_project_dsn/integration.py,sha256=5c8RVIO3Wjz2kBfB52EmxZ6VP2JQ1rLGMM9lybNhdAE,8109
203
203
  reconcile/gql_definitions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
204
204
  reconcile/gql_definitions/acs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
205
205
  reconcile/gql_definitions/acs/acs_instances.py,sha256=L91WW9LbhJbBSrECqShQpFtjoBOsmNIYLRpMbx1io5o,2181
@@ -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
@@ -309,9 +309,9 @@ reconcile/gql_definitions/gitlab_members/gitlab_instances.py,sha256=oYPvfiOsPTGH
309
309
  reconcile/gql_definitions/gitlab_members/permissions.py,sha256=Qzj3Fpv7xj8v9eygeP312nHRNg8er8XMRBveynPIyQM,3302
310
310
  reconcile/gql_definitions/glitchtip/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
311
311
  reconcile/gql_definitions/glitchtip/glitchtip_instance.py,sha256=QUfLhRkdE_-SorWgLBB8LHFD6kihbFwEoVByCLax3yM,2781
312
- reconcile/gql_definitions/glitchtip/glitchtip_project.py,sha256=oFijq1LIQysUM-IOyNUjxVZgRD93NK12lNvJuBq4vjE,6008
312
+ reconcile/gql_definitions/glitchtip/glitchtip_project.py,sha256=AojrkCDGbVjY0TOkfookz-9tqLA9txY7_xNdsWe174c,6004
313
313
  reconcile/gql_definitions/glitchtip_project_alerts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
314
- reconcile/gql_definitions/glitchtip_project_alerts/glitchtip_project.py,sha256=tZhSFb5eIIc5BOgMPQeV9tEwrVu1MnrxaQ5pQ0zEKk4,4468
314
+ reconcile/gql_definitions/glitchtip_project_alerts/glitchtip_project.py,sha256=pnkysdaQtdiVVxz9s7V8qttFygqcXOuXREd4LF7tCW8,4466
315
315
  reconcile/gql_definitions/integrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
316
316
  reconcile/gql_definitions/integrations/integrations.py,sha256=LfpgVbCCCk20ohwP5pDea5fwxMFGrcgE6J_WHBuGqek,11595
317
317
  reconcile/gql_definitions/jenkins_configs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -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.1rc879.dist-info/METADATA,sha256=4h4jN56Q5GugfmWXxRkczGqc0xzefD2xu-AnO3BlsBc,2273
841
- qontract_reconcile-0.10.1rc879.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
842
- qontract_reconcile-0.10.1rc879.dist-info/entry_points.txt,sha256=GKQqCl2j2X1BJQ69een6rHcR26PmnxnONLNOQB-nRjY,491
843
- qontract_reconcile-0.10.1rc879.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
844
- qontract_reconcile-0.10.1rc879.dist-info/RECORD,,
841
+ qontract_reconcile-0.10.1rc881.dist-info/METADATA,sha256=aWt-wXRL72DWuJYJh5aKEqNiw9lbIpToGNsTP6zL8tI,2273
842
+ qontract_reconcile-0.10.1rc881.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
843
+ qontract_reconcile-0.10.1rc881.dist-info/entry_points.txt,sha256=GKQqCl2j2X1BJQ69een6rHcR26PmnxnONLNOQB-nRjY,491
844
+ qontract_reconcile-0.10.1rc881.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
845
+ qontract_reconcile-0.10.1rc881.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
 
@@ -19,7 +19,7 @@ from reconcile.gql_definitions.glitchtip.glitchtip_project import (
19
19
  DEFINITION as GLITCHTIP_PROJECT_DEFINITION,
20
20
  )
21
21
  from reconcile.gql_definitions.glitchtip.glitchtip_project import (
22
- GlitchtipProjectsV1,
22
+ GlitchtipProjectV1,
23
23
  RoleV1,
24
24
  )
25
25
  from reconcile.gql_definitions.glitchtip.glitchtip_project import (
@@ -87,7 +87,7 @@ def fetch_current_state(
87
87
 
88
88
 
89
89
  def fetch_desired_state(
90
- glitchtip_projects: Sequence[GlitchtipProjectsV1],
90
+ glitchtip_projects: Sequence[GlitchtipProjectV1],
91
91
  mail_domain: str,
92
92
  internal_groups_client: InternalGroupsClient,
93
93
  ) -> list[Organization]:
@@ -143,7 +143,7 @@ def fetch_desired_state(
143
143
  return list(organizations.values())
144
144
 
145
145
 
146
- def get_glitchtip_projects(query_func: Callable) -> list[GlitchtipProjectsV1]:
146
+ def get_glitchtip_projects(query_func: Callable) -> list[GlitchtipProjectV1]:
147
147
  glitchtip_projects = (
148
148
  glitchtip_project_query(query_func=query_func).glitchtip_projects or []
149
149
  )
@@ -18,7 +18,7 @@ from reconcile.gql_definitions.glitchtip_project_alerts.glitchtip_project import
18
18
  GlitchtipProjectAlertRecipientEmailV1,
19
19
  GlitchtipProjectAlertRecipientV1,
20
20
  GlitchtipProjectAlertRecipientWebhookV1,
21
- GlitchtipProjectsV1,
21
+ GlitchtipProjectV1,
22
22
  )
23
23
  from reconcile.gql_definitions.glitchtip_project_alerts.glitchtip_project import (
24
24
  query as glitchtip_project_query,
@@ -73,7 +73,7 @@ class GlitchtipProjectAlertsIntegration(
73
73
  def get_early_exit_desired_state(self) -> Optional[dict[str, Any]]:
74
74
  return {"projects": [c.dict() for c in self.get_projects(gql.get_api().query)]}
75
75
 
76
- def get_projects(self, query_func: Callable) -> list[GlitchtipProjectsV1]:
76
+ def get_projects(self, query_func: Callable) -> list[GlitchtipProjectV1]:
77
77
  return glitchtip_project_query(query_func=query_func).glitchtip_projects or []
78
78
 
79
79
  def _build_project_alert_recipient(
@@ -95,7 +95,7 @@ class GlitchtipProjectAlertsIntegration(
95
95
 
96
96
  def fetch_desired_state(
97
97
  self,
98
- glitchtip_projects: Iterable[GlitchtipProjectsV1],
98
+ glitchtip_projects: Iterable[GlitchtipProjectV1],
99
99
  gjb_alert_url: str | None,
100
100
  gjb_token: str | None,
101
101
  ) -> list[Organization]:
@@ -264,7 +264,7 @@ class GlitchtipProjectAlertsIntegration(
264
264
  glitchtip_instances = glitchtip_instance_query(
265
265
  query_func=gqlapi.query
266
266
  ).instances
267
- glitchtip_projects_by_instance: dict[str, list[GlitchtipProjectsV1]] = (
267
+ glitchtip_projects_by_instance: dict[str, list[GlitchtipProjectV1]] = (
268
268
  defaultdict(list)
269
269
  )
270
270
  for glitchtip_project in self.get_projects(query_func=gqlapi.query):
@@ -20,7 +20,7 @@ from reconcile.gql_definitions.glitchtip.glitchtip_instance import (
20
20
  from reconcile.gql_definitions.glitchtip.glitchtip_project import (
21
21
  DEFINITION as GLITCHTIP_PROJECT_DEFINITION,
22
22
  )
23
- from reconcile.gql_definitions.glitchtip.glitchtip_project import GlitchtipProjectsV1
23
+ from reconcile.gql_definitions.glitchtip.glitchtip_project import GlitchtipProjectV1
24
24
  from reconcile.gql_definitions.glitchtip.glitchtip_project import (
25
25
  query as glitchtip_project_query,
26
26
  )
@@ -73,7 +73,7 @@ def glitchtip_project_dsn_secret(project: Project, key: ProjectKey) -> dict[str,
73
73
 
74
74
 
75
75
  def fetch_current_state(
76
- project: GlitchtipProjectsV1,
76
+ project: GlitchtipProjectV1,
77
77
  oc_map: OCMap,
78
78
  ri: ResourceInventory,
79
79
  ) -> None:
@@ -110,7 +110,7 @@ def fetch_current_state(
110
110
 
111
111
 
112
112
  def fetch_desired_state(
113
- glitchtip_projects: Iterable[GlitchtipProjectsV1],
113
+ glitchtip_projects: Iterable[GlitchtipProjectV1],
114
114
  ri: ResourceInventory,
115
115
  glitchtip_client: GlitchtipClient,
116
116
  ) -> None:
@@ -145,7 +145,7 @@ def fetch_desired_state(
145
145
  )
146
146
 
147
147
 
148
- def projects_query(query_func: Callable) -> list[GlitchtipProjectsV1]:
148
+ def projects_query(query_func: Callable) -> list[GlitchtipProjectV1]:
149
149
  glitchtip_projects = []
150
150
  for project in (
151
151
  glitchtip_project_query(query_func=query_func).glitchtip_projects or []
@@ -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")
@@ -143,7 +143,7 @@ class GlitchtipInstanceV1(ConfiguredBaseModel):
143
143
  name: str = Field(..., alias="name")
144
144
 
145
145
 
146
- class GlitchtipProjectsV1_GlitchtipOrganizationV1(ConfiguredBaseModel):
146
+ class GlitchtipProjectV1_GlitchtipOrganizationV1(ConfiguredBaseModel):
147
147
  name: str = Field(..., alias="name")
148
148
  instance: GlitchtipInstanceV1 = Field(..., alias="instance")
149
149
  owners: Optional[list[str]] = Field(..., alias="owners")
@@ -180,19 +180,19 @@ class AppV1(ConfiguredBaseModel):
180
180
  path: str = Field(..., alias="path")
181
181
 
182
182
 
183
- class GlitchtipProjectsV1(ConfiguredBaseModel):
183
+ class GlitchtipProjectV1(ConfiguredBaseModel):
184
184
  name: str = Field(..., alias="name")
185
185
  platform: str = Field(..., alias="platform")
186
186
  project_id: Optional[str] = Field(..., alias="projectId")
187
187
  event_throttle_rate: Optional[int] = Field(..., alias="eventThrottleRate")
188
188
  teams: list[GlitchtipTeamV1] = Field(..., alias="teams")
189
- organization: GlitchtipProjectsV1_GlitchtipOrganizationV1 = Field(..., alias="organization")
189
+ organization: GlitchtipProjectV1_GlitchtipOrganizationV1 = Field(..., alias="organization")
190
190
  namespaces: list[NamespaceV1] = Field(..., alias="namespaces")
191
191
  app: Optional[AppV1] = Field(..., alias="app")
192
192
 
193
193
 
194
194
  class ProjectsQueryData(ConfiguredBaseModel):
195
- glitchtip_projects: Optional[list[GlitchtipProjectsV1]] = Field(..., alias="glitchtip_projects")
195
+ glitchtip_projects: Optional[list[GlitchtipProjectV1]] = Field(..., alias="glitchtip_projects")
196
196
 
197
197
 
198
198
  def query(query_func: Callable, **kwargs: Any) -> ProjectsQueryData:
@@ -122,7 +122,7 @@ class GlitchtipProjectJiraV1(ConfiguredBaseModel):
122
122
  labels: Optional[list[str]] = Field(..., alias="labels")
123
123
 
124
124
 
125
- class GlitchtipProjectsV1(ConfiguredBaseModel):
125
+ class GlitchtipProjectV1(ConfiguredBaseModel):
126
126
  name: str = Field(..., alias="name")
127
127
  project_id: Optional[str] = Field(..., alias="projectId")
128
128
  organization: GlitchtipOrganizationV1 = Field(..., alias="organization")
@@ -131,7 +131,7 @@ class GlitchtipProjectsV1(ConfiguredBaseModel):
131
131
 
132
132
 
133
133
  class GlitchtipProjectsWithAlertsQueryData(ConfiguredBaseModel):
134
- glitchtip_projects: Optional[list[GlitchtipProjectsV1]] = Field(..., alias="glitchtip_projects")
134
+ glitchtip_projects: Optional[list[GlitchtipProjectV1]] = Field(..., alias="glitchtip_projects")
135
135
 
136
136
 
137
137
  def query(query_func: Callable, **kwargs: Any) -> GlitchtipProjectsWithAlertsQueryData:
@@ -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)