qontract-reconcile 0.10.1rc879__py3-none-any.whl → 0.10.1rc894__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.
Files changed (291) hide show
  1. {qontract_reconcile-0.10.1rc879.dist-info → qontract_reconcile-0.10.1rc894.dist-info}/METADATA +1 -1
  2. {qontract_reconcile-0.10.1rc879.dist-info → qontract_reconcile-0.10.1rc894.dist-info}/RECORD +291 -284
  3. reconcile/acs_rbac.py +1 -2
  4. reconcile/aus/advanced_upgrade_service.py +14 -14
  5. reconcile/aus/aus_label_source.py +1 -2
  6. reconcile/aus/base.py +23 -26
  7. reconcile/aus/cluster_version_data.py +4 -4
  8. reconcile/aus/models.py +2 -3
  9. reconcile/aus/version_gate_approver.py +2 -6
  10. reconcile/aus/version_gates/__init__.py +1 -3
  11. reconcile/aus/version_gates/sts_version_gate_handler.py +2 -3
  12. reconcile/aws_account_manager/integration.py +9 -14
  13. reconcile/aws_account_manager/reconciler.py +51 -1
  14. reconcile/aws_account_manager/utils.py +3 -0
  15. reconcile/aws_ami_cleanup/integration.py +3 -4
  16. reconcile/aws_iam_password_reset.py +2 -5
  17. reconcile/aws_version_sync/integration.py +2 -2
  18. reconcile/blackbox_exporter_endpoint_monitoring.py +2 -5
  19. reconcile/change_owners/approver.py +4 -5
  20. reconcile/change_owners/bundle.py +20 -22
  21. reconcile/change_owners/change_types.py +23 -24
  22. reconcile/change_owners/changes.py +13 -16
  23. reconcile/change_owners/decision.py +2 -5
  24. reconcile/change_owners/diff.py +11 -15
  25. reconcile/change_owners/self_service_roles.py +1 -2
  26. reconcile/change_owners/tester.py +7 -10
  27. reconcile/checkpoint.py +2 -5
  28. reconcile/cli.py +26 -12
  29. reconcile/closedbox_endpoint_monitoring_base.py +8 -11
  30. reconcile/cluster_deployment_mapper.py +2 -5
  31. reconcile/cna/assets/asset.py +4 -7
  32. reconcile/cna/assets/null.py +2 -5
  33. reconcile/cna/integration.py +2 -3
  34. reconcile/cna/state.py +2 -5
  35. reconcile/dashdotdb_base.py +8 -11
  36. reconcile/dashdotdb_cso.py +3 -6
  37. reconcile/dashdotdb_dora.py +10 -14
  38. reconcile/dashdotdb_dvo.py +10 -13
  39. reconcile/dashdotdb_slo.py +5 -8
  40. reconcile/database_access_manager.py +5 -6
  41. reconcile/dynatrace_token_provider/integration.py +3 -6
  42. reconcile/dynatrace_token_provider/integration_v2.py +20 -0
  43. reconcile/dynatrace_token_provider/meta.py +1 -0
  44. reconcile/external_resources/integration.py +1 -1
  45. reconcile/external_resources/manager.py +4 -4
  46. reconcile/external_resources/model.py +3 -3
  47. reconcile/external_resources/secrets_sync.py +5 -5
  48. reconcile/external_resources/state.py +5 -5
  49. reconcile/gabi_authorized_users.py +3 -6
  50. reconcile/gcr_mirror.py +1 -1
  51. reconcile/github_org.py +1 -3
  52. reconcile/github_repo_invites.py +2 -5
  53. reconcile/gitlab_housekeeping.py +7 -11
  54. reconcile/gitlab_labeler.py +1 -2
  55. reconcile/gitlab_members.py +2 -5
  56. reconcile/gitlab_permissions.py +1 -3
  57. reconcile/glitchtip/integration.py +5 -8
  58. reconcile/glitchtip_project_alerts/integration.py +57 -33
  59. reconcile/glitchtip_project_dsn/integration.py +8 -11
  60. reconcile/gql_definitions/aws_account_manager/aws_accounts.py +6 -0
  61. reconcile/gql_definitions/fragments/aws_account_managed.py +8 -0
  62. reconcile/gql_definitions/glitchtip/glitchtip_project.py +4 -4
  63. reconcile/gql_definitions/glitchtip_project_alerts/glitchtip_project.py +27 -7
  64. reconcile/integrations_manager.py +5 -8
  65. reconcile/jenkins/types.py +5 -6
  66. reconcile/jenkins_job_builder.py +9 -12
  67. reconcile/jenkins_roles.py +1 -1
  68. reconcile/jira_watcher.py +2 -2
  69. reconcile/ldap_groups/integration.py +2 -5
  70. reconcile/ocm/types.py +21 -26
  71. reconcile/ocm_addons_upgrade_tests_trigger.py +3 -6
  72. reconcile/ocm_clusters.py +8 -8
  73. reconcile/ocm_internal_notifications/integration.py +1 -2
  74. reconcile/ocm_labels/integration.py +2 -5
  75. reconcile/ocm_machine_pools.py +11 -15
  76. reconcile/ocm_upgrade_scheduler_org_updater.py +2 -5
  77. reconcile/openshift_base.py +29 -30
  78. reconcile/openshift_groups.py +15 -20
  79. reconcile/openshift_namespace_labels.py +8 -14
  80. reconcile/openshift_namespaces.py +5 -8
  81. reconcile/openshift_network_policies.py +2 -4
  82. reconcile/openshift_resources_base.py +19 -29
  83. reconcile/openshift_saas_deploy.py +9 -10
  84. reconcile/openshift_saas_deploy_change_tester.py +7 -10
  85. reconcile/openshift_saas_deploy_trigger_base.py +4 -7
  86. reconcile/openshift_saas_deploy_trigger_cleaner.py +5 -8
  87. reconcile/openshift_saas_deploy_trigger_configs.py +1 -2
  88. reconcile/openshift_saas_deploy_trigger_images.py +1 -2
  89. reconcile/openshift_saas_deploy_trigger_moving_commits.py +1 -2
  90. reconcile/openshift_saas_deploy_trigger_upstream_jobs.py +1 -2
  91. reconcile/openshift_tekton_resources.py +7 -11
  92. reconcile/openshift_upgrade_watcher.py +10 -13
  93. reconcile/openshift_users.py +8 -11
  94. reconcile/oum/base.py +3 -4
  95. reconcile/oum/labelset.py +1 -2
  96. reconcile/oum/metrics.py +2 -2
  97. reconcile/oum/models.py +1 -2
  98. reconcile/oum/standalone.py +2 -3
  99. reconcile/prometheus_rules_tester/integration.py +6 -9
  100. reconcile/quay_membership.py +1 -2
  101. reconcile/quay_mirror.py +12 -13
  102. reconcile/quay_mirror_org.py +10 -10
  103. reconcile/queries.py +4 -7
  104. reconcile/resource_scraper.py +3 -4
  105. reconcile/rhidp/common.py +2 -2
  106. reconcile/saas_auto_promotions_manager/integration.py +5 -6
  107. reconcile/saas_auto_promotions_manager/merge_request_manager/batcher.py +1 -2
  108. reconcile/saas_auto_promotions_manager/publisher.py +5 -6
  109. reconcile/saas_auto_promotions_manager/subscriber.py +36 -15
  110. reconcile/saas_auto_promotions_manager/utils/saas_files_inventory.py +8 -0
  111. reconcile/saas_file_validator.py +2 -5
  112. reconcile/signalfx_endpoint_monitoring.py +2 -5
  113. reconcile/skupper_network/integration.py +3 -6
  114. reconcile/skupper_network/models.py +3 -5
  115. reconcile/slack_base.py +4 -7
  116. reconcile/slack_usergroups.py +15 -17
  117. reconcile/sql_query.py +5 -9
  118. reconcile/status_board.py +4 -5
  119. reconcile/statuspage/atlassian.py +14 -15
  120. reconcile/statuspage/integrations/maintenances.py +3 -3
  121. reconcile/statuspage/page.py +8 -8
  122. reconcile/statuspage/state.py +4 -5
  123. reconcile/statuspage/status.py +7 -8
  124. reconcile/templating/lib/rendering.py +8 -8
  125. reconcile/templating/renderer.py +10 -11
  126. reconcile/templating/validator.py +4 -4
  127. reconcile/terraform_aws_route53.py +3 -6
  128. reconcile/terraform_cloudflare_dns.py +9 -12
  129. reconcile/terraform_cloudflare_resources.py +9 -11
  130. reconcile/terraform_cloudflare_users.py +8 -11
  131. reconcile/terraform_init/integration.py +2 -2
  132. reconcile/terraform_repo.py +11 -14
  133. reconcile/terraform_resources.py +20 -21
  134. reconcile/terraform_tgw_attachments.py +32 -36
  135. reconcile/terraform_users.py +6 -7
  136. reconcile/terraform_vpc_resources/integration.py +6 -6
  137. reconcile/test/conftest.py +7 -10
  138. reconcile/test/fixtures.py +1 -1
  139. reconcile/test/saas_auto_promotions_manager/conftest.py +3 -2
  140. reconcile/test/saas_auto_promotions_manager/merge_request_manager/renderer/conftest.py +2 -2
  141. reconcile/test/test_database_access_manager.py +3 -6
  142. reconcile/test/test_gitlab_labeler.py +2 -5
  143. reconcile/test/test_jump_host.py +5 -8
  144. reconcile/test/test_ocm_machine_pools.py +1 -4
  145. reconcile/test/test_openshift_base.py +3 -6
  146. reconcile/test/test_openshift_cluster_bots.py +5 -5
  147. reconcile/test/test_openshift_namespace_labels.py +2 -3
  148. reconcile/test/test_openshift_saas_deploy_trigger_cleaner.py +2 -2
  149. reconcile/test/test_saasherder.py +9 -12
  150. reconcile/test/test_slack_base.py +4 -6
  151. reconcile/test/test_status_board.py +4 -7
  152. reconcile/test/test_terraform_tgw_attachments.py +14 -20
  153. reconcile/typed_queries/alerting_services_settings.py +1 -2
  154. reconcile/typed_queries/app_interface_custom_messages.py +2 -3
  155. reconcile/typed_queries/app_interface_deadmanssnitch_settings.py +1 -3
  156. reconcile/typed_queries/app_interface_repo_url.py +1 -2
  157. reconcile/typed_queries/app_interface_state_settings.py +1 -3
  158. reconcile/typed_queries/app_interface_vault_settings.py +1 -2
  159. reconcile/typed_queries/aws_vpc_requests.py +1 -3
  160. reconcile/typed_queries/aws_vpcs.py +1 -3
  161. reconcile/typed_queries/clusters.py +2 -4
  162. reconcile/typed_queries/clusters_minimal.py +1 -3
  163. reconcile/typed_queries/clusters_with_dms.py +1 -3
  164. reconcile/typed_queries/dynatrace_environments.py +14 -0
  165. reconcile/typed_queries/external_resources.py +3 -4
  166. reconcile/typed_queries/pagerduty_instances.py +1 -2
  167. reconcile/typed_queries/repos.py +2 -3
  168. reconcile/typed_queries/reserved_networks.py +1 -3
  169. reconcile/typed_queries/saas_files.py +49 -59
  170. reconcile/typed_queries/slo_documents.py +1 -3
  171. reconcile/typed_queries/status_board.py +3 -7
  172. reconcile/typed_queries/tekton_pipeline_providers.py +1 -2
  173. reconcile/typed_queries/terraform_namespaces.py +1 -2
  174. reconcile/typed_queries/terraform_tgw_attachments/aws_accounts.py +1 -3
  175. reconcile/utils/acs/base.py +2 -3
  176. reconcile/utils/acs/notifiers.py +3 -3
  177. reconcile/utils/acs/policies.py +3 -3
  178. reconcile/utils/aggregated_list.py +1 -1
  179. reconcile/utils/amtool.py +1 -2
  180. reconcile/utils/aws_api.py +28 -31
  181. reconcile/utils/aws_api_typed/account.py +23 -0
  182. reconcile/utils/aws_api_typed/api.py +20 -9
  183. reconcile/utils/binary.py +1 -3
  184. reconcile/utils/clusterhealth/providerbase.py +1 -2
  185. reconcile/utils/clusterhealth/telemeter.py +2 -2
  186. reconcile/utils/deadmanssnitch_api.py +1 -2
  187. reconcile/utils/disabled_integrations.py +4 -6
  188. reconcile/utils/environ.py +1 -1
  189. reconcile/utils/expiration.py +3 -7
  190. reconcile/utils/external_resource_spec.py +3 -4
  191. reconcile/utils/external_resources.py +4 -7
  192. reconcile/utils/filtering.py +1 -2
  193. reconcile/utils/git.py +3 -9
  194. reconcile/utils/git_secrets.py +5 -5
  195. reconcile/utils/github_api.py +5 -9
  196. reconcile/utils/gitlab_api.py +2 -3
  197. reconcile/utils/glitchtip/client.py +2 -4
  198. reconcile/utils/glitchtip/models.py +8 -11
  199. reconcile/utils/gql.py +26 -35
  200. reconcile/utils/grouping.py +1 -3
  201. reconcile/utils/imap_client.py +2 -5
  202. reconcile/utils/internal_groups/client.py +1 -2
  203. reconcile/utils/internal_groups/models.py +8 -9
  204. reconcile/utils/jenkins_api.py +4 -4
  205. reconcile/utils/jinja2/extensions.py +1 -1
  206. reconcile/utils/jinja2/filters.py +4 -4
  207. reconcile/utils/jinja2/utils.py +16 -16
  208. reconcile/utils/jira_client.py +10 -11
  209. reconcile/utils/jjb_client.py +14 -17
  210. reconcile/utils/jobcontroller/controller.py +5 -5
  211. reconcile/utils/jobcontroller/models.py +2 -2
  212. reconcile/utils/jsonpath.py +4 -5
  213. reconcile/utils/jump_host.py +7 -8
  214. reconcile/utils/keycloak.py +3 -7
  215. reconcile/utils/ldap_client.py +2 -3
  216. reconcile/utils/lean_terraform_client.py +13 -17
  217. reconcile/utils/membershipsources/app_interface_resolver.py +1 -1
  218. reconcile/utils/membershipsources/models.py +19 -22
  219. reconcile/utils/metrics.py +13 -15
  220. reconcile/utils/mr/base.py +7 -11
  221. reconcile/utils/mr/glitchtip_access_reporter.py +2 -2
  222. reconcile/utils/mr/notificator.py +1 -2
  223. reconcile/utils/oc.py +38 -38
  224. reconcile/utils/oc_connection_parameters.py +24 -25
  225. reconcile/utils/oc_filters.py +2 -3
  226. reconcile/utils/oc_map.py +9 -15
  227. reconcile/utils/ocm/addons.py +7 -10
  228. reconcile/utils/ocm/base.py +38 -39
  229. reconcile/utils/ocm/clusters.py +6 -9
  230. reconcile/utils/ocm/label_sources.py +1 -2
  231. reconcile/utils/ocm/labels.py +3 -6
  232. reconcile/utils/ocm/ocm.py +11 -14
  233. reconcile/utils/ocm/products.py +1 -3
  234. reconcile/utils/ocm/search_filters.py +16 -17
  235. reconcile/utils/ocm/service_log.py +2 -3
  236. reconcile/utils/ocm/sre_capability_labels.py +4 -8
  237. reconcile/utils/ocm/subscriptions.py +1 -3
  238. reconcile/utils/ocm/syncsets.py +2 -4
  239. reconcile/utils/ocm/upgrades.py +5 -9
  240. reconcile/utils/ocm_base_client.py +13 -16
  241. reconcile/utils/openshift_resource.py +5 -11
  242. reconcile/utils/output.py +2 -3
  243. reconcile/utils/pagerduty_api.py +4 -5
  244. reconcile/utils/prometheus.py +2 -2
  245. reconcile/utils/promotion_state.py +4 -5
  246. reconcile/utils/promtool.py +2 -8
  247. reconcile/utils/quay_api.py +12 -22
  248. reconcile/utils/raw_github_api.py +3 -5
  249. reconcile/utils/rosa/rosa_cli.py +6 -6
  250. reconcile/utils/rosa/session.py +6 -7
  251. reconcile/utils/runtime/desired_state_diff.py +3 -8
  252. reconcile/utils/runtime/environment.py +4 -7
  253. reconcile/utils/runtime/integration.py +4 -4
  254. reconcile/utils/runtime/meta.py +1 -2
  255. reconcile/utils/runtime/runner.py +7 -10
  256. reconcile/utils/runtime/sharding.py +22 -27
  257. reconcile/utils/saasherder/interfaces.py +63 -69
  258. reconcile/utils/saasherder/models.py +30 -35
  259. reconcile/utils/saasherder/saasherder.py +39 -54
  260. reconcile/utils/secret_reader.py +17 -19
  261. reconcile/utils/slack_api.py +15 -17
  262. reconcile/utils/smtp_client.py +1 -2
  263. reconcile/utils/sqs_gateway.py +1 -3
  264. reconcile/utils/state.py +1 -2
  265. reconcile/utils/terraform/config_client.py +4 -5
  266. reconcile/utils/terraform_client.py +12 -8
  267. reconcile/utils/terrascript/cloudflare_client.py +4 -10
  268. reconcile/utils/terrascript/cloudflare_resources.py +10 -13
  269. reconcile/utils/terrascript/models.py +2 -3
  270. reconcile/utils/terrascript/resources.py +1 -2
  271. reconcile/utils/terrascript_aws_client.py +50 -38
  272. reconcile/utils/unleash/client.py +4 -7
  273. reconcile/utils/unleash/server.py +2 -2
  274. reconcile/utils/vault.py +8 -11
  275. reconcile/utils/vaultsecretref.py +2 -3
  276. reconcile/utils/vcs.py +7 -8
  277. reconcile/vault_replication.py +4 -8
  278. reconcile/vpc_peerings_validator.py +4 -9
  279. release/version.py +6 -7
  280. tools/app_interface_reporter.py +2 -2
  281. tools/cli_commands/gpg_encrypt.py +3 -6
  282. tools/cli_commands/systems_and_tools.py +4 -7
  283. tools/qontract_cli.py +105 -17
  284. tools/saas_promotion_state/__init__.py +0 -0
  285. tools/saas_promotion_state/saas_promotion_state.py +105 -0
  286. tools/template_validation.py +1 -1
  287. tools/test/conftest.py +45 -6
  288. tools/test/test_saas_promotion_state.py +187 -0
  289. {qontract_reconcile-0.10.1rc879.dist-info → qontract_reconcile-0.10.1rc894.dist-info}/WHEEL +0 -0
  290. {qontract_reconcile-0.10.1rc879.dist-info → qontract_reconcile-0.10.1rc894.dist-info}/entry_points.txt +0 -0
  291. {qontract_reconcile-0.10.1rc879.dist-info → qontract_reconcile-0.10.1rc894.dist-info}/top_level.txt +0 -0
reconcile/acs_rbac.py CHANGED
@@ -2,7 +2,6 @@ import logging
2
2
  from collections import defaultdict
3
3
  from collections.abc import Callable
4
4
  from typing import (
5
- Optional,
6
5
  Self,
7
6
  )
8
7
 
@@ -64,7 +63,7 @@ class AcsRole(BaseModel):
64
63
  assignments: list[AssignmentPair]
65
64
  permission_set_name: str
66
65
  access_scope: AcsAccessScope
67
- system_default: Optional[bool]
66
+ system_default: bool | None
68
67
 
69
68
  @classmethod
70
69
  def build(cls, permission: Permission, usernames: list[str]) -> Self:
@@ -197,7 +197,7 @@ class AdvancedUpgradeServiceIntegration(OCMClusterUpgradeSchedulerOrgIntegration
197
197
 
198
198
  def discover_clusters(
199
199
  ocm_api: OCMBaseClient,
200
- org_ids: Optional[set[str]] = None,
200
+ org_ids: set[str] | None = None,
201
201
  ignore_sts_clusters: bool = False,
202
202
  ) -> dict[str, list[ClusterDetails]]:
203
203
  """
@@ -222,7 +222,7 @@ def discover_clusters(
222
222
 
223
223
 
224
224
  def _get_org_labels(
225
- ocm_api: OCMBaseClient, org_ids: Optional[set[str]]
225
+ ocm_api: OCMBaseClient, org_ids: set[str] | None
226
226
  ) -> dict[str, LabelContainer]:
227
227
  """
228
228
  Fetch all AUS OCM org labels from organizations. They hold config
@@ -263,7 +263,7 @@ def _build_org_upgrade_specs_for_ocm_env(
263
263
  }
264
264
 
265
265
 
266
- def aus_label_key(config_atom: Optional[str] = None) -> str:
266
+ def aus_label_key(config_atom: str | None = None) -> str:
267
267
  """
268
268
  Generates label keys for aus, compliant with the naming schema defined in
269
269
  https://service.pages.redhat.com/dev-guidelines/docs/sre-capabilities/framework/ocm-labels/
@@ -276,7 +276,7 @@ class OrganizationLabelSet(BaseModel):
276
276
  Parses, represents and validates a set of organization labels for AUS.
277
277
  """
278
278
 
279
- blocked_versions: Optional[CSV] = Field(alias=aus_label_key("blocked-versions"))
279
+ blocked_versions: CSV | None = Field(alias=aus_label_key("blocked-versions"))
280
280
 
281
281
  sector_deps: dict[str, CSV] = labelset_groupfield(
282
282
  group_prefix=aus_label_key("sector-deps.")
@@ -395,10 +395,10 @@ class ClusterUpgradePolicyLabelSet(BaseModel):
395
395
  soak_days: int = Field(alias=aus_label_key("soak-days"), ge=0)
396
396
  workloads: CSV = Field(alias=aus_label_key("workloads"), csv_min_items=1)
397
397
  schedule: str = Field(alias=aus_label_key("schedule"))
398
- mutexes: Optional[CSV] = Field(alias=aus_label_key("mutexes"))
399
- sector: Optional[str] = Field(alias=aus_label_key("sector"))
400
- blocked_versions: Optional[CSV] = Field(alias=aus_label_key("blocked-versions"))
401
- version_gate_approvals: Optional[CSV] = Field(
398
+ mutexes: CSV | None = Field(alias=aus_label_key("mutexes"))
399
+ sector: str | None = Field(alias=aus_label_key("sector"))
400
+ blocked_versions: CSV | None = Field(alias=aus_label_key("blocked-versions"))
401
+ version_gate_approvals: CSV | None = Field(
402
402
  alias=aus_label_key("version-gate-approvals")
403
403
  )
404
404
  _schedule_validator = validator("schedule", allow_reuse=True)(cron_validator)
@@ -422,10 +422,10 @@ def build_cluster_upgrade_policy_label_set(
422
422
  workloads: list[str],
423
423
  schedule: str,
424
424
  soak_days: int,
425
- mutexes: Optional[list[str]] = None,
426
- sector: Optional[str] = None,
427
- blocked_versions: Optional[list[str]] = None,
428
- version_gate_approvals: Optional[list[str]] = None,
425
+ mutexes: list[str] | None = None,
426
+ sector: str | None = None,
427
+ blocked_versions: list[str] | None = None,
428
+ version_gate_approvals: list[str] | None = None,
429
429
  ) -> ClusterUpgradePolicyLabelSet:
430
430
  return ClusterUpgradePolicyLabelSet(**{
431
431
  aus_label_key("workloads"): ",".join(workloads),
@@ -463,7 +463,7 @@ def _build_policy_from_labels(labels: LabelContainer) -> ClusterUpgradePolicyV1:
463
463
 
464
464
 
465
465
  class VersionDataInheritanceLabelSet(BaseModel):
466
- inherit_version_data: Optional[CSV] = Field(
466
+ inherit_version_data: CSV | None = Field(
467
467
  alias=aus_label_key("version-data.inherit")
468
468
  )
469
469
  """
@@ -472,7 +472,7 @@ class VersionDataInheritanceLabelSet(BaseModel):
472
472
  Version data publishing/inheritance can also be defined between OCM environments.
473
473
  """
474
474
 
475
- publish_version_data: Optional[CSV] = Field(
475
+ publish_version_data: CSV | None = Field(
476
476
  alias=aus_label_key("version-data.publish")
477
477
  )
478
478
  """
@@ -1,5 +1,4 @@
1
- from collections.abc import Iterable
2
- from typing import Callable
1
+ from collections.abc import Callable, Iterable
3
2
 
4
3
  from reconcile.aus.advanced_upgrade_service import (
5
4
  aus_label_key,
reconcile/aus/base.py CHANGED
@@ -5,16 +5,13 @@ from abc import (
5
5
  ABC,
6
6
  abstractmethod,
7
7
  )
8
+ from collections.abc import Callable, Sequence
8
9
  from datetime import (
9
10
  datetime,
10
11
  timedelta,
11
- timezone,
12
12
  )
13
13
  from typing import (
14
- Callable,
15
- Optional,
16
14
  Protocol,
17
- Sequence,
18
15
  cast,
19
16
  )
20
17
 
@@ -112,9 +109,9 @@ MIN_DELTA_MINUTES = 6
112
109
 
113
110
 
114
111
  class AdvancedUpgradeSchedulerBaseIntegrationParams(PydanticRunParams):
115
- ocm_environment: Optional[str] = None
116
- ocm_organization_ids: Optional[set[str]] = None
117
- excluded_ocm_organization_ids: Optional[set[str]] = None
112
+ ocm_environment: str | None = None
113
+ ocm_organization_ids: set[str] | None = None
114
+ excluded_ocm_organization_ids: set[str] | None = None
118
115
  ignore_sts_clusters: bool = False
119
116
 
120
117
 
@@ -342,7 +339,7 @@ class AdvancedUpgradeSchedulerBaseIntegration(
342
339
  def _build_telemeter_health_check_provider_for_env(
343
340
  self,
344
341
  ocm_env_name: str,
345
- ) -> Optional[TelemeterClusterHealthProvider]:
342
+ ) -> TelemeterClusterHealthProvider | None:
346
343
  ocm_env = next(
347
344
  iter(
348
345
  ocm_env_telemeter_query(
@@ -403,12 +400,12 @@ class AbstractUpgradePolicy(ABC, BaseModel):
403
400
 
404
401
  cluster: OCMCluster
405
402
 
406
- id: Optional[str]
407
- next_run: Optional[str]
408
- schedule: Optional[str]
403
+ id: str | None
404
+ next_run: str | None
405
+ schedule: str | None
409
406
  schedule_type: str
410
407
  version: str
411
- state: Optional[str]
408
+ state: str | None
412
409
 
413
410
  @abstractmethod
414
411
  def create(self, ocm_api: OCMBaseClient) -> None:
@@ -424,7 +421,7 @@ class AbstractUpgradePolicy(ABC, BaseModel):
424
421
 
425
422
 
426
423
  def addon_upgrade_policy_soonest_next_run() -> str:
427
- now = datetime.now(tz=timezone.utc)
424
+ now = datetime.now(tz=dt.UTC)
428
425
  next_run = now + timedelta(minutes=MIN_DELTA_MINUTES)
429
426
  return next_run.strftime("%Y-%m-%dT%H:%M:%SZ")
430
427
 
@@ -683,7 +680,7 @@ def update_history(
683
680
  version_data.check_in = now
684
681
 
685
682
 
686
- def version_data_state_key(ocm_env: str, org_id: str, addon_id: Optional[str]) -> str:
683
+ def version_data_state_key(ocm_env: str, org_id: str, addon_id: str | None) -> str:
687
684
  return f"{ocm_env}/{org_id}/{addon_id}" if addon_id else f"{ocm_env}/{org_id}"
688
685
 
689
686
 
@@ -694,7 +691,7 @@ def get_version_data_map(
694
691
  integration: str,
695
692
  addon_id: str = "",
696
693
  inherit_version_data: bool = True,
697
- defer: Optional[Callable] = None,
694
+ defer: Callable | None = None,
698
695
  ) -> VersionDataMap:
699
696
  """Get a summary of versions history per OCM instance
700
697
 
@@ -788,7 +785,7 @@ def version_conditions_met(
788
785
  version: str,
789
786
  version_data: VersionData,
790
787
  upgrade_policy: ClusterUpgradePolicyV1,
791
- sector: Optional[Sector],
788
+ sector: Sector | None,
792
789
  ) -> bool:
793
790
  """Check that upgrade conditions are met for a version
794
791
 
@@ -887,8 +884,8 @@ def gates_to_agree(
887
884
  def upgradeable_version(
888
885
  spec: ClusterUpgradeSpec,
889
886
  version_data: VersionData,
890
- sector: Optional[Sector],
891
- ) -> Optional[str]:
887
+ sector: Sector | None,
888
+ ) -> str | None:
892
889
  """Get the highest next version we can upgrade to, fulfilling all conditions"""
893
890
  for version in reversed(sort_versions(spec.get_available_upgrades())):
894
891
  if spec.version_blocked(version):
@@ -908,7 +905,7 @@ def verify_current_should_skip(
908
905
  desired: ClusterUpgradeSpec,
909
906
  now: datetime,
910
907
  addon_id: str = "",
911
- ) -> tuple[bool, Optional[UpgradePolicyHandler]]:
908
+ ) -> tuple[bool, UpgradePolicyHandler | None]:
912
909
  current_policies = [c for c in current_state if c.cluster.id == desired.cluster.id]
913
910
  if not current_policies:
914
911
  return False, None
@@ -944,7 +941,7 @@ def verify_schedule_should_skip(
944
941
  desired: ClusterUpgradeSpec,
945
942
  now: datetime,
946
943
  addon_id: str = "",
947
- ) -> Optional[str]:
944
+ ) -> str | None:
948
945
  schedule = desired.upgrade_policy.schedule
949
946
  iter = croniter(schedule)
950
947
  # ClusterService refuses scheduling upgrades less than 5m in advance
@@ -1006,7 +1003,7 @@ def _create_upgrade_policy(
1006
1003
 
1007
1004
  def _calculate_node_pool_diffs(
1008
1005
  ocm_api: OCMBaseClient, spec: ClusterUpgradeSpec, now: datetime
1009
- ) -> Optional[UpgradePolicyHandler]:
1006
+ ) -> UpgradePolicyHandler | None:
1010
1007
  node_pools = get_node_pools(ocm_api, spec.cluster.id)
1011
1008
  if node_pools:
1012
1009
  for pool in node_pools:
@@ -1051,7 +1048,7 @@ def calculate_diff(
1051
1048
  """
1052
1049
 
1053
1050
  def set_mutex(
1054
- locked: dict[str, str], cluster_id: str, mutexes: Optional[set[str]] = None
1051
+ locked: dict[str, str], cluster_id: str, mutexes: set[str] | None = None
1055
1052
  ) -> None:
1056
1053
  for mutex in mutexes or set():
1057
1054
  locked[mutex] = cluster_id
@@ -1174,7 +1171,7 @@ def act(
1174
1171
  dry_run: bool,
1175
1172
  diffs: list[UpgradePolicyHandler],
1176
1173
  ocm_api: OCMBaseClient,
1177
- addon_id: Optional[str] = None,
1174
+ addon_id: str | None = None,
1178
1175
  ) -> None:
1179
1176
  diffs.sort(key=sort_diffs)
1180
1177
  for diff in diffs:
@@ -1207,8 +1204,8 @@ def get_orgs_for_environment(
1207
1204
  integration: str,
1208
1205
  ocm_env_name: str,
1209
1206
  query_func: Callable,
1210
- ocm_organization_ids: Optional[set[str]] = None,
1211
- excluded_ocm_organization_ids: Optional[set[str]] = None,
1207
+ ocm_organization_ids: set[str] | None = None,
1208
+ excluded_ocm_organization_ids: set[str] | None = None,
1212
1209
  only_addon_managed_upgrades: bool = False,
1213
1210
  ) -> list[AUSOCMOrganization]:
1214
1211
  """
@@ -1243,7 +1240,7 @@ def get_orgs_for_environment(
1243
1240
  def remaining_soak_day_metric_values_for_cluster(
1244
1241
  spec: ClusterUpgradeSpec,
1245
1242
  soaked_versions: dict[str, float],
1246
- current_upgrade: Optional[AbstractUpgradePolicy],
1243
+ current_upgrade: AbstractUpgradePolicy | None,
1247
1244
  ) -> dict[str, float]:
1248
1245
  """
1249
1246
  Calculate what versions and metric values to report for `AUS*VersionRemainingSoakDaysGauge` metrics.
@@ -1,8 +1,8 @@
1
1
  import json
2
+ from collections.abc import Iterable
2
3
  from datetime import datetime
3
4
  from typing import (
4
5
  Any,
5
- Iterable,
6
6
  Optional,
7
7
  )
8
8
 
@@ -93,9 +93,9 @@ class VersionData(BaseModel):
93
93
  upgrade policies.
94
94
  """
95
95
 
96
- check_in: Optional[datetime]
96
+ check_in: datetime | None
97
97
  versions: dict[str, VersionHistory] = Field(default_factory=dict)
98
- stats: Optional[Stats]
98
+ stats: Stats | None
99
99
 
100
100
  def jsondict(self) -> dict[str, Any]:
101
101
  return json.loads(self.json(exclude_none=True))
@@ -104,7 +104,7 @@ class VersionData(BaseModel):
104
104
  state.add(ocm_name, self.jsondict(), force=True)
105
105
 
106
106
  def workload_history(
107
- self, version: str, workload: str, default: Optional[WorkloadHistory] = None
107
+ self, version: str, workload: str, default: WorkloadHistory | None = None
108
108
  ) -> WorkloadHistory:
109
109
  if not default:
110
110
  vh = self.versions.get(version, VersionHistory())
reconcile/aus/models.py CHANGED
@@ -7,7 +7,6 @@ from collections.abc import (
7
7
  Mapping,
8
8
  Sequence,
9
9
  )
10
- from typing import Optional
11
10
 
12
11
  from pydantic import (
13
12
  BaseModel,
@@ -108,7 +107,7 @@ class OrganizationUpgradeSpec(BaseModel):
108
107
  def __init__(
109
108
  self,
110
109
  org: AUSOCMOrganization,
111
- specs: Optional[Iterable[ClusterUpgradeSpec]] = None,
110
+ specs: Iterable[ClusterUpgradeSpec] | None = None,
112
111
  ) -> None:
113
112
  super().__init__(org=org)
114
113
 
@@ -200,7 +199,7 @@ class SectorConfigError(Exception):
200
199
 
201
200
  class Sector(BaseModel):
202
201
  name: str
203
- dependencies: list["Sector"] = Field(default_factory=list)
202
+ dependencies: list[Sector] = Field(default_factory=list)
204
203
  _specs: dict[str, ClusterUpgradeSpec] = PrivateAttr(default_factory=dict)
205
204
 
206
205
  def __key(self) -> str:
@@ -1,9 +1,5 @@
1
1
  import logging
2
- from typing import (
3
- Callable,
4
- Iterable,
5
- Optional,
6
- )
2
+ from collections.abc import Callable, Iterable
7
3
 
8
4
  from reconcile.aus.advanced_upgrade_service import aus_label_key
9
5
  from reconcile.aus.base import gates_to_agree, get_orgs_for_environment
@@ -54,7 +50,7 @@ class VersionGateApproverParams(PydanticRunParams):
54
50
  job_controller_namespace: str
55
51
  rosa_job_service_account: str
56
52
  rosa_role: str
57
- rosa_job_image: Optional[str] = None
53
+ rosa_job_image: str | None = None
58
54
 
59
55
 
60
56
  class VersionGateApprover(QontractReconcileIntegration[VersionGateApproverParams]):
@@ -1,5 +1,3 @@
1
- from typing import Type
2
-
3
1
  from reconcile.aus.version_gates import (
4
2
  ingress_gate_handler,
5
3
  ocp_gate_handler,
@@ -7,7 +5,7 @@ from reconcile.aus.version_gates import (
7
5
  )
8
6
  from reconcile.aus.version_gates.handler import GateHandler
9
7
 
10
- HANDLERS: dict[str, Type[GateHandler]] = {
8
+ HANDLERS: dict[str, type[GateHandler]] = {
11
9
  ocp_gate_handler.GATE_LABEL: ocp_gate_handler.OCPGateHandler,
12
10
  sts_version_gate_handler.GATE_LABEL: sts_version_gate_handler.STSGateHandler,
13
11
  ingress_gate_handler.GATE_LABEL: ingress_gate_handler.IngressGateHandler,
@@ -1,5 +1,4 @@
1
1
  import logging
2
- from typing import Optional
3
2
 
4
3
  from reconcile.aus.version_gates.handler import GateHandler
5
4
  from reconcile.utils.jobcontroller.controller import K8sJobController
@@ -16,8 +15,8 @@ class STSGateHandler(GateHandler):
16
15
  self,
17
16
  job_controller: K8sJobController,
18
17
  aws_iam_role: str,
19
- rosa_job_service_account: Optional[str] = None,
20
- rosa_job_image: Optional[str] = None,
18
+ rosa_job_service_account: str | None = None,
19
+ rosa_job_image: str | None = None,
21
20
  ) -> None:
22
21
  self.job_controller = job_controller
23
22
  self.aws_iam_role = aws_iam_role
@@ -1,5 +1,5 @@
1
1
  from collections.abc import Callable, Iterable
2
- from datetime import datetime, timezone
2
+ from datetime import UTC, datetime
3
3
  from typing import Any
4
4
 
5
5
  import jinja2
@@ -99,7 +99,7 @@ class AwsAccountMgmtIntegration(
99
99
  "accountRequest": account_request.dict(by_alias=True),
100
100
  "uid": uid,
101
101
  "settings": settings,
102
- "timestamp": int(datetime.now(tz=timezone.utc).timestamp()),
102
+ "timestamp": int(datetime.now(tz=UTC).timestamp()),
103
103
  })
104
104
  return tmpl
105
105
 
@@ -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
 
@@ -12,7 +12,6 @@ from datetime import (
12
12
  from typing import (
13
13
  TYPE_CHECKING,
14
14
  Any,
15
- Optional,
16
15
  )
17
16
 
18
17
  from botocore.exceptions import ClientError
@@ -153,7 +152,7 @@ def get_region(
153
152
 
154
153
 
155
154
  def get_app_interface_amis(
156
- namespaces: Optional[list[NamespaceV1]], ts: Terrascript
155
+ namespaces: list[NamespaceV1] | None, ts: Terrascript
157
156
  ) -> list[AIAmi]:
158
157
  """Returns all the ami referenced in ASGs in app-interface."""
159
158
  app_interface_amis = []
@@ -185,7 +184,7 @@ def get_app_interface_amis(
185
184
 
186
185
  def check_aws_ami_in_use(
187
186
  aws_ami: AWSAmi, app_interface_amis: list[AIAmi]
188
- ) -> Optional[str]:
187
+ ) -> str | None:
189
188
  """Verifies if the given AWS ami is in use in a defined app-interface ASG."""
190
189
  for ai_ami in app_interface_amis:
191
190
  # This can happen if the ASG init template has changed over the time. We don't have a way
@@ -203,7 +202,7 @@ def check_aws_ami_in_use(
203
202
 
204
203
 
205
204
  @defer
206
- def run(dry_run: bool, thread_pool_size: int, defer: Optional[Callable] = None) -> None:
205
+ def run(dry_run: bool, thread_pool_size: int, defer: Callable | None = None) -> None:
207
206
  exit_code = ExitCodes.SUCCESS
208
207
 
209
208
  # We still use here a non-typed query; accounts is passed to AWSApi and Terrascript classes
@@ -4,10 +4,7 @@ from collections.abc import (
4
4
  Iterable,
5
5
  Mapping,
6
6
  )
7
- from typing import (
8
- Any,
9
- Optional,
10
- )
7
+ from typing import Any
11
8
 
12
9
  from pydantic import BaseModel
13
10
 
@@ -21,7 +18,7 @@ QONTRACT_INTEGRATION = "aws-iam-password-reset"
21
18
 
22
19
  def get_roles(
23
20
  roles: Iterable[Mapping[str, Any]], org_username: str
24
- ) -> Optional[Mapping[str, Any]]:
21
+ ) -> Mapping[str, Any] | None:
25
22
  for d in roles:
26
23
  if d["org_username"] == org_username:
27
24
  return d
@@ -3,7 +3,7 @@ from collections.abc import (
3
3
  Callable,
4
4
  Iterable,
5
5
  )
6
- from enum import Enum
6
+ from enum import StrEnum
7
7
  from typing import Any
8
8
 
9
9
  import semver
@@ -69,7 +69,7 @@ class ExternalResourceProvisioner(BaseModel):
69
69
  path: str | None = None
70
70
 
71
71
 
72
- class VersionFormat(str, Enum):
72
+ class VersionFormat(StrEnum):
73
73
  MAJOR = "major"
74
74
  MAJOR_MINOR = "major_minor"
75
75
  MAJOR_MINOR_PATCH = "major_minor_patch"
@@ -1,9 +1,6 @@
1
1
  import logging
2
2
  import sys
3
- from typing import (
4
- Any,
5
- Optional,
6
- )
3
+ from typing import Any
7
4
 
8
5
  from reconcile import queries
9
6
  from reconcile.closedbox_endpoint_monitoring_base import (
@@ -69,7 +66,7 @@ def get_blackbox_providers() -> list[EndpointMonitoringProvider]:
69
66
 
70
67
  def build_probe(
71
68
  provider: EndpointMonitoringProvider, endpoints: list[Endpoint]
72
- ) -> Optional[OpenshiftResource]:
69
+ ) -> OpenshiftResource | None:
73
70
  blackbox_exporter = provider.blackboxExporter
74
71
  if blackbox_exporter:
75
72
  prober_url = parse_prober_url(blackbox_exporter.exporterUrl)
@@ -1,6 +1,5 @@
1
1
  from dataclasses import dataclass
2
2
  from typing import (
3
- Optional,
4
3
  Protocol,
5
4
  )
6
5
 
@@ -17,25 +16,25 @@ class Approver:
17
16
  """
18
17
 
19
18
  org_username: str
20
- tag_on_merge_requests: Optional[bool] = False
19
+ tag_on_merge_requests: bool | None = False
21
20
 
22
21
 
23
22
  class ApproverResolver(Protocol):
24
- def lookup_approver_by_path(self, path: str) -> Optional[Approver]: ...
23
+ def lookup_approver_by_path(self, path: str) -> Approver | None: ...
25
24
 
26
25
 
27
26
  class GqlApproverResolver:
28
27
  def __init__(self, gqlapis: list[GqlApi]):
29
28
  self.gqlapis = gqlapis
30
29
 
31
- def lookup_approver_by_path(self, path: str) -> Optional[Approver]:
30
+ def lookup_approver_by_path(self, path: str) -> Approver | None:
32
31
  for gqlapi in self.gqlapis:
33
32
  approver = self._lookup_approver_by_path(gqlapi, path)
34
33
  if approver:
35
34
  return approver
36
35
  return None
37
36
 
38
- def _lookup_approver_by_path(self, gqlapi: GqlApi, path: str) -> Optional[Approver]:
37
+ def _lookup_approver_by_path(self, gqlapi: GqlApi, path: str) -> Approver | None:
39
38
  approvers = gqlapi.query(
40
39
  """
41
40
  query Approvers($path: String) {