qontract-reconcile 0.10.2.dev349__py3-none-any.whl → 0.10.2.dev414__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 (356) hide show
  1. {qontract_reconcile-0.10.2.dev349.dist-info → qontract_reconcile-0.10.2.dev414.dist-info}/METADATA +12 -11
  2. {qontract_reconcile-0.10.2.dev349.dist-info → qontract_reconcile-0.10.2.dev414.dist-info}/RECORD +356 -350
  3. reconcile/acs_rbac.py +2 -2
  4. reconcile/aus/advanced_upgrade_service.py +15 -12
  5. reconcile/aus/base.py +26 -27
  6. reconcile/aus/cluster_version_data.py +15 -5
  7. reconcile/aus/models.py +1 -1
  8. reconcile/automated_actions/config/integration.py +15 -3
  9. reconcile/aws_account_manager/integration.py +8 -8
  10. reconcile/aws_account_manager/reconciler.py +3 -3
  11. reconcile/aws_ami_cleanup/integration.py +8 -12
  12. reconcile/aws_ami_share.py +69 -62
  13. reconcile/aws_cloudwatch_log_retention/integration.py +155 -126
  14. reconcile/aws_ecr_image_pull_secrets.py +2 -2
  15. reconcile/aws_iam_keys.py +7 -41
  16. reconcile/aws_saml_idp/integration.py +12 -4
  17. reconcile/aws_saml_roles/integration.py +32 -25
  18. reconcile/aws_version_sync/integration.py +6 -12
  19. reconcile/change_owners/bundle.py +3 -3
  20. reconcile/change_owners/change_log_tracking.py +3 -2
  21. reconcile/change_owners/change_owners.py +1 -1
  22. reconcile/change_owners/diff.py +2 -4
  23. reconcile/checkpoint.py +11 -3
  24. reconcile/cli.py +33 -8
  25. reconcile/dashdotdb_dora.py +5 -12
  26. reconcile/dashdotdb_slo.py +1 -1
  27. reconcile/database_access_manager.py +123 -117
  28. reconcile/dynatrace_token_provider/integration.py +1 -1
  29. reconcile/endpoints_discovery/integration.py +4 -1
  30. reconcile/endpoints_discovery/merge_request.py +1 -1
  31. reconcile/endpoints_discovery/merge_request_manager.py +9 -11
  32. reconcile/external_resources/factories.py +5 -12
  33. reconcile/external_resources/integration.py +1 -1
  34. reconcile/external_resources/manager.py +24 -10
  35. reconcile/external_resources/meta.py +0 -1
  36. reconcile/external_resources/metrics.py +1 -1
  37. reconcile/external_resources/model.py +13 -13
  38. reconcile/external_resources/reconciler.py +7 -4
  39. reconcile/external_resources/secrets_sync.py +6 -8
  40. reconcile/external_resources/state.py +60 -17
  41. reconcile/fleet_labeler/integration.py +1 -1
  42. reconcile/gabi_authorized_users.py +8 -5
  43. reconcile/gcp_image_mirror.py +2 -2
  44. reconcile/github_org.py +1 -1
  45. reconcile/github_owners.py +4 -0
  46. reconcile/gitlab_housekeeping.py +13 -15
  47. reconcile/gitlab_members.py +6 -12
  48. reconcile/gitlab_mr_sqs_consumer.py +2 -2
  49. reconcile/gitlab_owners.py +15 -11
  50. reconcile/gitlab_permissions.py +8 -12
  51. reconcile/glitchtip_project_alerts/integration.py +3 -1
  52. reconcile/gql_definitions/acs/acs_instances.py +5 -5
  53. reconcile/gql_definitions/acs/acs_policies.py +5 -5
  54. reconcile/gql_definitions/acs/acs_rbac.py +5 -5
  55. reconcile/gql_definitions/advanced_upgrade_service/aus_clusters.py +5 -5
  56. reconcile/gql_definitions/advanced_upgrade_service/aus_organization.py +5 -5
  57. reconcile/gql_definitions/app_interface_metrics_exporter/onboarding_status.py +5 -5
  58. reconcile/gql_definitions/app_sre_tekton_access_revalidation/roles.py +5 -5
  59. reconcile/gql_definitions/app_sre_tekton_access_revalidation/users.py +5 -5
  60. reconcile/gql_definitions/automated_actions/instance.py +46 -7
  61. reconcile/gql_definitions/aws_account_manager/aws_accounts.py +5 -5
  62. reconcile/gql_definitions/aws_ami_cleanup/aws_accounts.py +15 -5
  63. reconcile/gql_definitions/aws_cloudwatch_log_retention/aws_accounts.py +27 -66
  64. reconcile/gql_definitions/aws_saml_idp/aws_accounts.py +15 -5
  65. reconcile/gql_definitions/aws_saml_roles/aws_accounts.py +15 -5
  66. reconcile/gql_definitions/aws_saml_roles/roles.py +5 -5
  67. reconcile/gql_definitions/aws_version_sync/clusters.py +5 -5
  68. reconcile/gql_definitions/aws_version_sync/namespaces.py +5 -5
  69. reconcile/gql_definitions/change_owners/queries/change_types.py +5 -5
  70. reconcile/gql_definitions/change_owners/queries/self_service_roles.py +5 -5
  71. reconcile/gql_definitions/cluster_auth_rhidp/clusters.py +5 -5
  72. reconcile/gql_definitions/common/alerting_services_settings.py +5 -5
  73. reconcile/gql_definitions/common/app_code_component_repos.py +5 -5
  74. reconcile/gql_definitions/common/app_interface_custom_messages.py +5 -5
  75. reconcile/gql_definitions/common/app_interface_dms_settings.py +5 -5
  76. reconcile/gql_definitions/common/app_interface_repo_settings.py +5 -5
  77. reconcile/gql_definitions/common/app_interface_roles.py +5 -5
  78. reconcile/gql_definitions/common/app_interface_state_settings.py +5 -5
  79. reconcile/gql_definitions/common/app_interface_vault_settings.py +5 -5
  80. reconcile/gql_definitions/common/app_quay_repos_escalation_policies.py +5 -5
  81. reconcile/gql_definitions/common/apps.py +5 -5
  82. reconcile/gql_definitions/common/aws_vpc_requests.py +15 -5
  83. reconcile/gql_definitions/common/aws_vpcs.py +5 -5
  84. reconcile/gql_definitions/common/clusters.py +7 -5
  85. reconcile/gql_definitions/common/clusters_minimal.py +5 -5
  86. reconcile/gql_definitions/common/clusters_with_dms.py +5 -5
  87. reconcile/gql_definitions/common/clusters_with_peering.py +5 -5
  88. reconcile/gql_definitions/common/github_orgs.py +5 -5
  89. reconcile/gql_definitions/common/jira_settings.py +5 -5
  90. reconcile/gql_definitions/common/jiralert_settings.py +5 -5
  91. reconcile/gql_definitions/common/ldap_settings.py +5 -5
  92. reconcile/gql_definitions/common/namespaces.py +5 -5
  93. reconcile/gql_definitions/common/namespaces_minimal.py +7 -5
  94. reconcile/gql_definitions/common/ocm_env_telemeter.py +5 -5
  95. reconcile/gql_definitions/common/ocm_environments.py +5 -5
  96. reconcile/gql_definitions/common/pagerduty_instances.py +5 -5
  97. reconcile/gql_definitions/common/pgp_reencryption_settings.py +5 -5
  98. reconcile/gql_definitions/common/pipeline_providers.py +5 -5
  99. reconcile/gql_definitions/common/quay_instances.py +5 -5
  100. reconcile/gql_definitions/common/quay_orgs.py +5 -5
  101. reconcile/gql_definitions/common/reserved_networks.py +5 -5
  102. reconcile/gql_definitions/common/rhcs_provider_settings.py +5 -5
  103. reconcile/gql_definitions/common/saas_files.py +5 -5
  104. reconcile/gql_definitions/common/saas_target_namespaces.py +5 -5
  105. reconcile/gql_definitions/common/saasherder_settings.py +5 -5
  106. reconcile/gql_definitions/common/slack_workspaces.py +5 -5
  107. reconcile/gql_definitions/common/smtp_client_settings.py +5 -5
  108. reconcile/gql_definitions/common/state_aws_account.py +5 -5
  109. reconcile/gql_definitions/common/users.py +5 -5
  110. reconcile/gql_definitions/common/users_with_paths.py +5 -5
  111. reconcile/gql_definitions/cost_report/app_names.py +5 -5
  112. reconcile/gql_definitions/cost_report/cost_namespaces.py +5 -5
  113. reconcile/gql_definitions/cost_report/settings.py +5 -5
  114. reconcile/gql_definitions/dashdotdb_slo/slo_documents_query.py +5 -5
  115. reconcile/gql_definitions/dynatrace_token_provider/dynatrace_bootstrap_tokens.py +5 -5
  116. reconcile/gql_definitions/dynatrace_token_provider/token_specs.py +5 -5
  117. reconcile/gql_definitions/email_sender/apps.py +5 -5
  118. reconcile/gql_definitions/email_sender/emails.py +5 -5
  119. reconcile/gql_definitions/email_sender/users.py +5 -5
  120. reconcile/gql_definitions/endpoints_discovery/apps.py +5 -5
  121. reconcile/gql_definitions/external_resources/aws_accounts.py +5 -5
  122. reconcile/gql_definitions/external_resources/external_resources_modules.py +5 -5
  123. reconcile/gql_definitions/external_resources/external_resources_namespaces.py +89 -6
  124. reconcile/gql_definitions/external_resources/external_resources_settings.py +7 -5
  125. reconcile/gql_definitions/external_resources/fragments/external_resources_module_overrides.py +5 -5
  126. reconcile/gql_definitions/fleet_labeler/fleet_labels.py +5 -5
  127. reconcile/gql_definitions/fragments/aus_organization.py +5 -5
  128. reconcile/gql_definitions/fragments/aws_account_common.py +7 -5
  129. reconcile/gql_definitions/fragments/aws_account_managed.py +5 -5
  130. reconcile/gql_definitions/fragments/aws_account_sso.py +5 -5
  131. reconcile/gql_definitions/fragments/aws_infra_management_account.py +5 -5
  132. reconcile/gql_definitions/fragments/aws_organization.py +33 -0
  133. reconcile/gql_definitions/fragments/aws_vpc.py +5 -5
  134. reconcile/gql_definitions/fragments/aws_vpc_request.py +7 -5
  135. reconcile/gql_definitions/fragments/container_image_mirror.py +5 -5
  136. reconcile/gql_definitions/fragments/deploy_resources.py +5 -5
  137. reconcile/gql_definitions/fragments/disable.py +5 -5
  138. reconcile/gql_definitions/fragments/email_service.py +5 -5
  139. reconcile/gql_definitions/fragments/email_user.py +5 -5
  140. reconcile/gql_definitions/fragments/jumphost_common_fields.py +5 -5
  141. reconcile/gql_definitions/fragments/membership_source.py +5 -5
  142. reconcile/gql_definitions/fragments/minimal_ocm_organization.py +5 -5
  143. reconcile/gql_definitions/fragments/oc_connection_cluster.py +5 -5
  144. reconcile/gql_definitions/fragments/ocm_environment.py +5 -5
  145. reconcile/gql_definitions/fragments/pipeline_provider_retention.py +5 -5
  146. reconcile/gql_definitions/fragments/prometheus_instance.py +5 -5
  147. reconcile/gql_definitions/fragments/resource_limits_requirements.py +5 -5
  148. reconcile/gql_definitions/fragments/resource_requests_requirements.py +5 -5
  149. reconcile/gql_definitions/fragments/resource_values.py +5 -5
  150. reconcile/gql_definitions/fragments/saas_slo_document.py +5 -5
  151. reconcile/gql_definitions/fragments/saas_target_namespace.py +5 -5
  152. reconcile/gql_definitions/fragments/serviceaccount_token.py +5 -5
  153. reconcile/gql_definitions/fragments/terraform_state.py +5 -5
  154. reconcile/gql_definitions/fragments/upgrade_policy.py +5 -5
  155. reconcile/gql_definitions/fragments/user.py +5 -5
  156. reconcile/gql_definitions/fragments/vault_secret.py +5 -5
  157. reconcile/gql_definitions/gcp/gcp_docker_repos.py +5 -5
  158. reconcile/gql_definitions/gcp/gcp_projects.py +5 -5
  159. reconcile/gql_definitions/gitlab_members/gitlab_instances.py +5 -5
  160. reconcile/gql_definitions/gitlab_members/permissions.py +5 -5
  161. reconcile/gql_definitions/glitchtip/glitchtip_instance.py +5 -5
  162. reconcile/gql_definitions/glitchtip/glitchtip_project.py +5 -5
  163. reconcile/gql_definitions/glitchtip_project_alerts/glitchtip_project.py +5 -5
  164. reconcile/gql_definitions/integrations/integrations.py +5 -5
  165. reconcile/gql_definitions/introspection.json +2137 -1053
  166. reconcile/gql_definitions/jenkins_configs/jenkins_configs.py +5 -5
  167. reconcile/gql_definitions/jenkins_configs/jenkins_instances.py +5 -5
  168. reconcile/gql_definitions/jira/jira_servers.py +5 -5
  169. reconcile/gql_definitions/jira_permissions_validator/jira_boards_for_permissions_validator.py +9 -5
  170. reconcile/gql_definitions/jumphosts/jumphosts.py +5 -5
  171. reconcile/gql_definitions/ldap_groups/roles.py +5 -5
  172. reconcile/gql_definitions/ldap_groups/settings.py +5 -5
  173. reconcile/gql_definitions/maintenance/maintenances.py +5 -5
  174. reconcile/gql_definitions/membershipsources/roles.py +5 -5
  175. reconcile/gql_definitions/ocm_labels/clusters.py +5 -5
  176. reconcile/gql_definitions/ocm_labels/organizations.py +5 -5
  177. reconcile/gql_definitions/openshift_cluster_bots/clusters.py +5 -5
  178. reconcile/gql_definitions/openshift_groups/managed_groups.py +5 -5
  179. reconcile/gql_definitions/openshift_groups/managed_roles.py +5 -5
  180. reconcile/gql_definitions/openshift_serviceaccount_tokens/tokens.py +5 -5
  181. reconcile/gql_definitions/quay_membership/quay_membership.py +5 -5
  182. reconcile/gql_definitions/rhcs/certs.py +5 -5
  183. reconcile/gql_definitions/rhidp/organizations.py +5 -5
  184. reconcile/gql_definitions/service_dependencies/jenkins_instance_fragment.py +5 -5
  185. reconcile/gql_definitions/service_dependencies/service_dependencies.py +5 -5
  186. reconcile/gql_definitions/sharding/aws_accounts.py +5 -5
  187. reconcile/gql_definitions/sharding/ocm_organization.py +5 -5
  188. reconcile/gql_definitions/skupper_network/site_controller_template.py +5 -5
  189. reconcile/gql_definitions/skupper_network/skupper_networks.py +5 -5
  190. reconcile/gql_definitions/slack_usergroups/clusters.py +5 -5
  191. reconcile/gql_definitions/slack_usergroups/permissions.py +5 -5
  192. reconcile/gql_definitions/slack_usergroups/users.py +5 -5
  193. reconcile/gql_definitions/slo_documents/slo_documents.py +5 -5
  194. reconcile/gql_definitions/status_board/status_board.py +5 -5
  195. reconcile/gql_definitions/statuspage/statuspages.py +5 -5
  196. reconcile/gql_definitions/templating/template_collection.py +5 -5
  197. reconcile/gql_definitions/templating/templates.py +5 -5
  198. reconcile/gql_definitions/terraform_cloudflare_dns/app_interface_cloudflare_dns_settings.py +5 -5
  199. reconcile/gql_definitions/terraform_cloudflare_dns/terraform_cloudflare_zones.py +5 -5
  200. reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_accounts.py +5 -5
  201. reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_resources.py +5 -5
  202. reconcile/gql_definitions/terraform_cloudflare_users/app_interface_setting_cloudflare_and_vault.py +5 -5
  203. reconcile/gql_definitions/terraform_cloudflare_users/terraform_cloudflare_roles.py +5 -5
  204. reconcile/gql_definitions/terraform_init/aws_accounts.py +19 -5
  205. reconcile/gql_definitions/terraform_repo/terraform_repo.py +5 -5
  206. reconcile/gql_definitions/terraform_resources/database_access_manager.py +5 -5
  207. reconcile/gql_definitions/terraform_resources/terraform_resources_namespaces.py +38 -6
  208. reconcile/gql_definitions/terraform_tgw_attachments/aws_accounts.py +15 -5
  209. reconcile/gql_definitions/unleash_feature_toggles/feature_toggles.py +5 -5
  210. reconcile/gql_definitions/vault_instances/vault_instances.py +5 -5
  211. reconcile/gql_definitions/vault_policies/vault_policies.py +5 -5
  212. reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator.py +5 -5
  213. reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator_peered_cluster_fragment.py +5 -5
  214. reconcile/integrations_manager.py +3 -3
  215. reconcile/jenkins_worker_fleets.py +10 -8
  216. reconcile/jira_permissions_validator.py +237 -122
  217. reconcile/ldap_groups/integration.py +1 -1
  218. reconcile/ocm/types.py +35 -56
  219. reconcile/ocm_aws_infrastructure_access.py +1 -1
  220. reconcile/ocm_clusters.py +4 -4
  221. reconcile/ocm_labels/integration.py +3 -2
  222. reconcile/ocm_machine_pools.py +23 -23
  223. reconcile/openshift_base.py +53 -2
  224. reconcile/openshift_cluster_bots.py +3 -2
  225. reconcile/openshift_namespace_labels.py +1 -1
  226. reconcile/openshift_namespaces.py +97 -101
  227. reconcile/openshift_resources_base.py +6 -2
  228. reconcile/openshift_rhcs_certs.py +5 -5
  229. reconcile/openshift_rolebindings.py +7 -11
  230. reconcile/openshift_saas_deploy.py +6 -7
  231. reconcile/openshift_saas_deploy_change_tester.py +9 -7
  232. reconcile/openshift_saas_deploy_trigger_cleaner.py +3 -5
  233. reconcile/openshift_serviceaccount_tokens.py +2 -2
  234. reconcile/openshift_upgrade_watcher.py +4 -4
  235. reconcile/oum/labelset.py +5 -3
  236. reconcile/oum/models.py +1 -4
  237. reconcile/prometheus_rules_tester/integration.py +3 -3
  238. reconcile/quay_mirror.py +1 -1
  239. reconcile/queries.py +131 -1
  240. reconcile/rhidp/common.py +3 -5
  241. reconcile/rhidp/sso_client/base.py +1 -1
  242. reconcile/saas_auto_promotions_manager/merge_request_manager/renderer.py +1 -1
  243. reconcile/saas_auto_promotions_manager/subscriber.py +4 -3
  244. reconcile/skupper_network/integration.py +2 -2
  245. reconcile/slack_usergroups.py +35 -14
  246. reconcile/sql_query.py +1 -0
  247. reconcile/status_board.py +6 -6
  248. reconcile/statuspage/atlassian.py +7 -7
  249. reconcile/statuspage/integrations/maintenances.py +4 -3
  250. reconcile/statuspage/page.py +4 -9
  251. reconcile/statuspage/status.py +5 -8
  252. reconcile/templates/rosa-classic-cluster-creation.sh.j2 +4 -0
  253. reconcile/templates/rosa-hcp-cluster-creation.sh.j2 +3 -0
  254. reconcile/templating/lib/rendering.py +3 -3
  255. reconcile/templating/renderer.py +4 -3
  256. reconcile/terraform_aws_route53.py +7 -1
  257. reconcile/terraform_cloudflare_dns.py +3 -3
  258. reconcile/terraform_cloudflare_resources.py +5 -5
  259. reconcile/terraform_cloudflare_users.py +3 -2
  260. reconcile/terraform_init/integration.py +187 -23
  261. reconcile/terraform_repo.py +16 -12
  262. reconcile/terraform_resources.py +17 -7
  263. reconcile/terraform_tgw_attachments.py +27 -19
  264. reconcile/terraform_users.py +7 -0
  265. reconcile/terraform_vpc_peerings.py +14 -3
  266. reconcile/terraform_vpc_resources/integration.py +10 -1
  267. reconcile/typed_queries/aws_account_tags.py +41 -0
  268. reconcile/typed_queries/cost_report/app_names.py +1 -1
  269. reconcile/typed_queries/cost_report/cost_namespaces.py +2 -2
  270. reconcile/typed_queries/saas_files.py +13 -13
  271. reconcile/typed_queries/status_board.py +2 -2
  272. reconcile/unleash_feature_toggles/integration.py +4 -2
  273. reconcile/utils/acs/base.py +6 -3
  274. reconcile/utils/acs/policies.py +2 -2
  275. reconcile/utils/aggregated_list.py +4 -3
  276. reconcile/utils/aws_api.py +51 -54
  277. reconcile/utils/aws_api_typed/api.py +38 -9
  278. reconcile/utils/aws_api_typed/cloudformation.py +149 -0
  279. reconcile/utils/aws_api_typed/logs.py +73 -0
  280. reconcile/utils/aws_api_typed/organization.py +4 -2
  281. reconcile/utils/datetime_util.py +67 -0
  282. reconcile/utils/deadmanssnitch_api.py +1 -1
  283. reconcile/utils/differ.py +2 -3
  284. reconcile/utils/early_exit_cache.py +11 -12
  285. reconcile/utils/expiration.py +7 -3
  286. reconcile/utils/external_resource_spec.py +24 -1
  287. reconcile/utils/filtering.py +1 -1
  288. reconcile/utils/gitlab_api.py +7 -5
  289. reconcile/utils/glitchtip/client.py +6 -2
  290. reconcile/utils/glitchtip/models.py +25 -28
  291. reconcile/utils/gql.py +4 -7
  292. reconcile/utils/helm.py +2 -1
  293. reconcile/utils/helpers.py +1 -1
  294. reconcile/utils/instrumented_wrappers.py +1 -1
  295. reconcile/utils/internal_groups/client.py +2 -2
  296. reconcile/utils/internal_groups/models.py +8 -17
  297. reconcile/utils/jinja2/utils.py +6 -101
  298. reconcile/utils/jira_client.py +82 -63
  299. reconcile/utils/jjb_client.py +9 -12
  300. reconcile/utils/jobcontroller/controller.py +1 -1
  301. reconcile/utils/jobcontroller/models.py +17 -1
  302. reconcile/utils/json.py +70 -0
  303. reconcile/utils/membershipsources/app_interface_resolver.py +4 -2
  304. reconcile/utils/membershipsources/models.py +16 -23
  305. reconcile/utils/membershipsources/resolver.py +4 -2
  306. reconcile/utils/merge_request_manager/merge_request_manager.py +4 -4
  307. reconcile/utils/merge_request_manager/parser.py +6 -6
  308. reconcile/utils/metrics.py +5 -5
  309. reconcile/utils/models.py +304 -82
  310. reconcile/utils/mr/app_interface_reporter.py +2 -2
  311. reconcile/utils/mr/base.py +2 -2
  312. reconcile/utils/mr/notificator.py +3 -3
  313. reconcile/utils/mr/update_access_report_base.py +3 -4
  314. reconcile/utils/mr/user_maintenance.py +3 -2
  315. reconcile/utils/oc.py +118 -97
  316. reconcile/utils/oc_filters.py +3 -3
  317. reconcile/utils/ocm/addons.py +0 -1
  318. reconcile/utils/ocm/base.py +17 -20
  319. reconcile/utils/ocm/cluster_groups.py +1 -1
  320. reconcile/utils/ocm/identity_providers.py +2 -2
  321. reconcile/utils/ocm/labels.py +1 -1
  322. reconcile/utils/ocm/products.py +9 -3
  323. reconcile/utils/ocm/search_filters.py +3 -6
  324. reconcile/utils/ocm/service_log.py +4 -6
  325. reconcile/utils/ocm/sre_capability_labels.py +20 -13
  326. reconcile/utils/openshift_resource.py +10 -5
  327. reconcile/utils/output.py +3 -2
  328. reconcile/utils/pagerduty_api.py +10 -7
  329. reconcile/utils/promotion_state.py +6 -11
  330. reconcile/utils/raw_github_api.py +1 -1
  331. reconcile/utils/rhcsv2_certs.py +1 -4
  332. reconcile/utils/runtime/integration.py +2 -3
  333. reconcile/utils/runtime/runner.py +2 -2
  334. reconcile/utils/saasherder/interfaces.py +13 -20
  335. reconcile/utils/saasherder/models.py +25 -21
  336. reconcile/utils/saasherder/saasherder.py +35 -24
  337. reconcile/utils/slack_api.py +26 -4
  338. reconcile/utils/sloth.py +171 -2
  339. reconcile/utils/sqs_gateway.py +2 -1
  340. reconcile/utils/state.py +2 -1
  341. reconcile/utils/structs.py +1 -1
  342. reconcile/utils/terraform_client.py +5 -4
  343. reconcile/utils/terrascript_aws_client.py +171 -114
  344. reconcile/utils/unleash/server.py +2 -8
  345. reconcile/utils/vault.py +5 -12
  346. reconcile/utils/vcs.py +8 -8
  347. reconcile/vault_replication.py +107 -42
  348. tools/app_interface_reporter.py +4 -4
  349. tools/cli_commands/cost_report/cost_management_api.py +3 -3
  350. tools/cli_commands/cost_report/view.py +7 -6
  351. tools/cli_commands/erv2.py +3 -1
  352. tools/cli_commands/systems_and_tools.py +5 -1
  353. tools/qontract_cli.py +31 -18
  354. tools/template_validation.py +3 -1
  355. {qontract_reconcile-0.10.2.dev349.dist-info → qontract_reconcile-0.10.2.dev414.dist-info}/WHEEL +0 -0
  356. {qontract_reconcile-0.10.2.dev349.dist-info → qontract_reconcile-0.10.2.dev414.dist-info}/entry_points.txt +0 -0
@@ -7,11 +7,7 @@ from collections.abc import Iterable, Mapping
7
7
  from enum import Enum
8
8
  from typing import Any, Self
9
9
 
10
- from pydantic import (
11
- BaseModel,
12
- Field,
13
- root_validator,
14
- )
10
+ from pydantic import BaseModel, Field, model_validator
15
11
 
16
12
  from reconcile import queries
17
13
  from reconcile.gql_definitions.common.clusters import (
@@ -22,6 +18,7 @@ from reconcile.gql_definitions.common.clusters import (
22
18
  from reconcile.typed_queries.clusters import get_clusters
23
19
  from reconcile.utils.differ import diff_mappings
24
20
  from reconcile.utils.disabled_integrations import integration_is_enabled
21
+ from reconcile.utils.json import json_dumps
25
22
  from reconcile.utils.ocm import (
26
23
  DEFAULT_OCM_MACHINE_POOL_ID,
27
24
  OCM,
@@ -61,7 +58,8 @@ class MachinePoolAutoscaling(AbstractAutoscaling):
61
58
  min_replicas: int
62
59
  max_replicas: int
63
60
 
64
- @root_validator()
61
+ @model_validator(mode="before")
62
+ @classmethod
65
63
  def max_greater_min(cls, field_values: Mapping[str, Any]) -> Mapping[str, Any]:
66
64
  min_replicas = field_values.get("min_replicas")
67
65
  max_replicas = field_values.get("max_replicas")
@@ -82,7 +80,8 @@ class NodePoolAutoscaling(AbstractAutoscaling):
82
80
  min_replica: int
83
81
  max_replica: int
84
82
 
85
- @root_validator()
83
+ @model_validator(mode="before")
84
+ @classmethod
86
85
  def max_greater_min(cls, field_values: Mapping[str, Any]) -> Mapping[str, Any]:
87
86
  min_replica = field_values.get("min_replica")
88
87
  max_replica = field_values.get("max_replica")
@@ -103,14 +102,15 @@ class AbstractPool(ABC, BaseModel):
103
102
  # Abstract class for machine pools, to be implemented by OSD/HyperShift classes
104
103
 
105
104
  id: str
106
- replicas: int | None
107
- taints: list[Mapping[str, str]] | None
108
- labels: Mapping[str, str] | None
105
+ replicas: int | None = None
106
+ taints: list[Mapping[str, str]] | None = None
107
+ labels: Mapping[str, str] | None = None
109
108
  cluster: str
110
109
  cluster_type: ClusterType = Field(..., exclude=True)
111
- autoscaling: AbstractAutoscaling | None
110
+ autoscaling: AbstractAutoscaling | None = None
112
111
 
113
- @root_validator()
112
+ @model_validator(mode="before")
113
+ @classmethod
114
114
  def validate_scaling(cls, field_values: Mapping[str, Any]) -> Mapping[str, Any]:
115
115
  if field_values.get("autoscaling") and field_values.get("replicas"):
116
116
  raise ValueError("autoscaling and replicas are mutually exclusive")
@@ -154,13 +154,13 @@ class MachinePool(AbstractPool):
154
154
  instance_type: str
155
155
 
156
156
  def delete(self, ocm: OCM) -> None:
157
- ocm.delete_machine_pool(self.cluster, self.dict(by_alias=True))
157
+ ocm.delete_machine_pool(self.cluster, self.model_dump(by_alias=True))
158
158
 
159
159
  def create(self, ocm: OCM) -> None:
160
- ocm.create_machine_pool(self.cluster, self.dict(by_alias=True))
160
+ ocm.create_machine_pool(self.cluster, self.model_dump(by_alias=True))
161
161
 
162
162
  def update(self, ocm: OCM) -> None:
163
- update_dict = self.dict(by_alias=True)
163
+ update_dict = self.model_dump(by_alias=True)
164
164
  # can not update instance_type
165
165
  del update_dict["instance_type"]
166
166
  if not update_dict["labels"]:
@@ -214,7 +214,7 @@ class MachinePool(AbstractPool):
214
214
  replicas=pool.replicas,
215
215
  autoscaling=autoscaling,
216
216
  instance_type=pool.instance_type,
217
- taints=[p.dict(by_alias=True) for p in pool.taints or []],
217
+ taints=[p.model_dump(by_alias=True) for p in pool.taints or []],
218
218
  labels=pool.labels,
219
219
  cluster=cluster,
220
220
  cluster_type=cluster_type,
@@ -232,14 +232,14 @@ class NodePool(AbstractPool):
232
232
  subnet: str | None
233
233
 
234
234
  def delete(self, ocm: OCM) -> None:
235
- ocm.delete_node_pool(self.cluster, self.dict(by_alias=True))
235
+ ocm.delete_node_pool(self.cluster, self.model_dump(by_alias=True))
236
236
 
237
237
  def create(self, ocm: OCM) -> None:
238
- spec = self.dict(by_alias=True)
238
+ spec = self.model_dump(by_alias=True)
239
239
  ocm.create_node_pool(self.cluster, spec)
240
240
 
241
241
  def update(self, ocm: OCM) -> None:
242
- update_dict = self.dict(by_alias=True)
242
+ update_dict = self.model_dump(by_alias=True)
243
243
  # can not update instance_type
244
244
  del update_dict["aws_node_pool"]
245
245
  # can not update subnet
@@ -297,7 +297,7 @@ class NodePool(AbstractPool):
297
297
  aws_node_pool=AWSNodePool(
298
298
  instance_type=pool.instance_type,
299
299
  ),
300
- taints=[p.dict(by_alias=True) for p in pool.taints or []],
300
+ taints=[p.model_dump(by_alias=True) for p in pool.taints or []],
301
301
  labels=pool.labels,
302
302
  subnet=pool.subnet,
303
303
  cluster=cluster,
@@ -312,7 +312,7 @@ class PoolHandler(BaseModel):
312
312
  pool: AbstractPool
313
313
 
314
314
  def act(self, dry_run: bool, ocm: OCM) -> None:
315
- logging.info(f"{self.action} {self.pool.dict(by_alias=True)}")
315
+ logging.info(f"{self.action} {self.pool.model_dump(by_alias=True)}")
316
316
  if dry_run:
317
317
  return
318
318
 
@@ -469,7 +469,7 @@ def calculate_diff(
469
469
  if invalid_diff:
470
470
  errors.append(
471
471
  InvalidUpdateError(
472
- f"can not update {invalid_diff} for existing machine pool on cluster {cluster_name}, CURRENT: {diff_pair.current.json()}, DESIRED: {diff_pair.desired.json()}"
472
+ f"can not update {invalid_diff} for existing machine pool on cluster {cluster_name}, CURRENT: {json_dumps(diff_pair.current)}, DESIRED: {json_dumps(diff_pair.desired)}"
473
473
  )
474
474
  )
475
475
  else:
@@ -531,7 +531,7 @@ def run(dry_run: bool) -> None:
531
531
 
532
532
  settings = queries.get_app_interface_settings()
533
533
  cluster_like_objects = [
534
- cluster.dict(by_alias=True) for cluster in filtered_clusters
534
+ cluster.model_dump(by_alias=True) for cluster in filtered_clusters
535
535
  ]
536
536
  ocm_map = OCMMap(
537
537
  clusters=cluster_like_objects,
@@ -30,9 +30,11 @@ from reconcile.utils import (
30
30
  )
31
31
  from reconcile.utils.constants import DEFAULT_THREAD_POOL_SIZE
32
32
  from reconcile.utils.oc import (
33
+ AmbiguousResourceTypeError,
33
34
  DeploymentFieldIsImmutableError,
34
35
  FieldIsImmutableError,
35
36
  InvalidValueApplyError,
37
+ KindNotFoundError,
36
38
  MayNotChangeOnceSetError,
37
39
  MetaDataAnnotationsTooLongApplyError,
38
40
  OC_Map,
@@ -128,6 +130,29 @@ class ClusterMap(Protocol):
128
130
  ) -> list[str]: ...
129
131
 
130
132
 
133
+ def validate_managed_resource_types(
134
+ oc: OCCli,
135
+ managed_resource_types: Iterable[str],
136
+ managed_resource_names: Iterable[Mapping[str, Any]],
137
+ cluster_scope_resource_validation: bool,
138
+ ) -> None:
139
+ """Validate the managed resource types."""
140
+ managed_resources = [
141
+ managed_resource_name["resource"]
142
+ for managed_resource_name in managed_resource_names
143
+ ]
144
+ for managed_resource_type in managed_resource_types:
145
+ # The k8s kind must be supported by the cluster
146
+ resource = oc.get_api_resource(managed_resource_type)
147
+
148
+ if cluster_scope_resource_validation and not resource.namespaced:
149
+ # cluster-scoped resources must be use managedResourceNames!
150
+ if managed_resource_type not in managed_resources:
151
+ raise ValidationError(
152
+ f"Cluster-scoped resource {managed_resource_type} must be managed by name only. Please use 'managedResourceNames' field to specify the names of the resources to manage."
153
+ )
154
+
155
+
131
156
  def init_specs_to_fetch(
132
157
  ri: ResourceInventory,
133
158
  oc_map: ClusterMap,
@@ -136,6 +161,7 @@ def init_specs_to_fetch(
136
161
  override_managed_types: Iterable[str] | None = None,
137
162
  managed_types_key: str = "managedResourceTypes",
138
163
  cluster_admin: bool = False,
164
+ cluster_scope_resource_validation: bool = False,
139
165
  ) -> list[StateSpec]:
140
166
  state_specs: list[StateSpec] = []
141
167
 
@@ -163,9 +189,27 @@ def init_specs_to_fetch(
163
189
  logging.log(level=ex.log_level, msg=ex.message)
164
190
  continue
165
191
 
192
+ managed_resource_names = namespace_info.get("managedResourceNames") or []
193
+ try:
194
+ validate_managed_resource_types(
195
+ oc,
196
+ managed_types,
197
+ managed_resource_names,
198
+ cluster_scope_resource_validation=cluster_scope_resource_validation,
199
+ )
200
+ except KindNotFoundError:
201
+ # We must allow kinds that are not supported by the cluster because:
202
+ # 1. We install CRD with an operator in the same MR
203
+ # 2. SAAS files initialize the namespace objects with managedResourceTypes from the SAAS file
204
+ # and we can't expect that all of those are valid for all clusters
205
+ pass
206
+ except (AmbiguousResourceTypeError, ValidationError) as e:
207
+ ri.register_error()
208
+ logging.error(f"[{cluster}/{namespace_info['name']}] {e}")
209
+ continue
210
+
166
211
  namespace = namespace_info["name"]
167
212
  # These may exit but have a value of None
168
- managed_resource_names = namespace_info.get("managedResourceNames") or []
169
213
  managed_resource_type_overrides = (
170
214
  namespace_info.get("managedResourceTypeOverrides") or []
171
215
  )
@@ -340,6 +384,7 @@ def fetch_current_state(
340
384
  cluster_admin: bool = False,
341
385
  caller: str | None = None,
342
386
  init_projects: bool = False,
387
+ cluster_scope_resource_validation: bool = False,
343
388
  ) -> tuple[ResourceInventory, OC_Map]:
344
389
  ri = ResourceInventory()
345
390
  settings = queries.get_app_interface_settings()
@@ -362,6 +407,7 @@ def fetch_current_state(
362
407
  clusters=clusters,
363
408
  override_managed_types=override_managed_types,
364
409
  cluster_admin=cluster_admin,
410
+ cluster_scope_resource_validation=cluster_scope_resource_validation,
365
411
  )
366
412
  threaded.run(
367
413
  populate_current_state,
@@ -1363,6 +1409,11 @@ class HasOpenShiftResources(Protocol):
1363
1409
  openshift_resources: list | None
1364
1410
 
1365
1411
 
1412
+ @runtime_checkable
1413
+ class HasOpenShiftResourcesRequired(Protocol):
1414
+ openshift_resources: list
1415
+
1416
+
1366
1417
  @runtime_checkable
1367
1418
  class HasOpenshiftServiceAccountTokens(Protocol):
1368
1419
  openshift_service_account_tokens: list | None
@@ -1371,7 +1422,7 @@ class HasOpenshiftServiceAccountTokens(Protocol):
1371
1422
  @runtime_checkable
1372
1423
  class HasSharedResourcesOpenShiftResources(Protocol):
1373
1424
  @property
1374
- def shared_resources(self) -> Sequence[HasOpenShiftResources] | None: ...
1425
+ def shared_resources(self) -> Sequence[HasOpenShiftResourcesRequired] | None: ...
1375
1426
 
1376
1427
 
1377
1428
  @runtime_checkable
@@ -16,6 +16,7 @@ from reconcile.gql_definitions.openshift_cluster_bots.clusters import ClusterV1
16
16
  from reconcile.status import ExitCodes
17
17
  from reconcile.utils import gql
18
18
  from reconcile.utils.disabled_integrations import integration_is_enabled
19
+ from reconcile.utils.json import json_dumps
19
20
  from reconcile.utils.mr import clusters_updates
20
21
  from reconcile.utils.ocm import OCM, OCMMap
21
22
  from reconcile.utils.openshift_resource import (
@@ -102,7 +103,7 @@ def oc(
102
103
 
103
104
  def oc_apply(kubeconfig: str, namespace: str, items: list[dict]) -> None:
104
105
  for item in items:
105
- stdin = json.dumps(item).encode()
106
+ stdin = json_dumps(item).encode()
106
107
  oc(kubeconfig, namespace, ["apply", "-f", "-"], stdin)
107
108
 
108
109
 
@@ -300,7 +301,7 @@ def filter_clusters(clusters: list[ClusterV1]) -> list[ClusterV1]:
300
301
 
301
302
  def get_ocm_map(clusters: list[ClusterV1]) -> OCMMap:
302
303
  settings = queries.get_app_interface_settings()
303
- clusters_info = [c.dict(by_alias=True) for c in clusters]
304
+ clusters_info = [c.model_dump(by_alias=True) for c in clusters]
304
305
  return OCMMap(
305
306
  settings=settings,
306
307
  clusters=clusters_info,
@@ -229,7 +229,7 @@ def get_gql_namespaces_in_shard() -> list[NamespaceV1]:
229
229
  return [
230
230
  ns
231
231
  for ns in all_namespaces
232
- if not ob.is_namespace_deleted(ns.dict(by_alias=True))
232
+ if not ob.is_namespace_deleted(ns.model_dump(by_alias=True))
233
233
  and is_in_shard(f"{ns.cluster.name}/{ns.name}")
234
234
  ]
235
235
 
@@ -1,25 +1,27 @@
1
1
  import logging
2
- import sys
2
+ from collections import defaultdict
3
3
  from collections.abc import (
4
4
  Callable,
5
5
  Iterable,
6
- Mapping,
7
6
  Sequence,
8
7
  )
8
+ from dataclasses import dataclass
9
+ from enum import StrEnum
9
10
  from typing import Any
10
11
 
11
12
  from sretoolbox.utils import threaded
12
13
 
13
14
  import reconcile.openshift_base as ob
14
15
  from reconcile.gql_definitions.common.namespaces_minimal import NamespaceV1
15
- from reconcile.status import ExitCodes
16
16
  from reconcile.typed_queries.app_interface_vault_settings import (
17
17
  get_app_interface_vault_settings,
18
18
  )
19
19
  from reconcile.typed_queries.namespaces_minimal import get_namespaces_minimal
20
20
  from reconcile.utils.constants import DEFAULT_THREAD_POOL_SIZE
21
21
  from reconcile.utils.defer import defer
22
- from reconcile.utils.oc_filters import filter_namespaces_by_cluster_and_namespace
22
+ from reconcile.utils.oc_filters import (
23
+ filter_namespaces_by_cluster_and_namespace,
24
+ )
23
25
  from reconcile.utils.oc_map import (
24
26
  OCLogMsg,
25
27
  OCMap,
@@ -30,113 +32,112 @@ from reconcile.utils.sharding import is_in_shard
30
32
 
31
33
  QONTRACT_INTEGRATION = "openshift-namespaces"
32
34
 
33
- NS_STATE_PRESENT = "present"
34
- NS_STATE_ABSENT = "absent"
35
-
36
- NS_ACTION_CREATE = "create"
37
- NS_ACTION_DELETE = "delete"
38
35
 
36
+ class Action(StrEnum):
37
+ CREATE = "create"
38
+ DELETE = "delete"
39
39
 
40
- DUPLICATES_LOG_MSG = "Found multiple definitions for the namespace {key}"
41
40
 
41
+ @dataclass(frozen=True)
42
+ class DesiredState:
43
+ cluster: str
44
+ namespace: str
45
+ delete: bool
42
46
 
43
- def get_desired_state(namespaces: Iterable[NamespaceV1]) -> list[dict[str, str]]:
44
- desired_state: list[dict[str, str]] = []
45
- for ns in namespaces:
46
- state = NS_STATE_PRESENT
47
- if ns.delete:
48
- state = NS_STATE_ABSENT
49
47
 
50
- desired_state.append({
51
- "cluster": ns.cluster.name,
52
- "namespace": ns.name,
53
- "desired_state": state,
54
- })
48
+ class NamespaceDuplicateError(Exception):
49
+ pass
55
50
 
56
- return desired_state
57
51
 
52
+ def get_namespaces(
53
+ cluster_name: Sequence[str] | None,
54
+ namespace_name: Sequence[str] | None,
55
+ ) -> tuple[list[NamespaceV1], list[NamespaceDuplicateError]]:
56
+ all_namespaces = get_namespaces_minimal()
58
57
 
59
- def get_shard_namespaces(
60
- namespaces: Iterable[NamespaceV1],
61
- ) -> tuple[list[NamespaceV1], bool]:
62
- # Structure holding duplicates by namespace key
63
- duplicates: dict[str, list[NamespaceV1]] = {}
64
- # namespace filtered list without duplicates
65
- filtered_ns: dict[str, NamespaceV1] = {}
66
-
67
- err = False
68
- for ns in namespaces:
69
- key = f"{ns.cluster.name}/{ns.name}"
58
+ namespaces_by_shard_key = defaultdict(list)
70
59
 
60
+ for namespace in all_namespaces:
61
+ key = f"{namespace.cluster.name}/{namespace.name}"
71
62
  if is_in_shard(key):
72
- if key not in filtered_ns:
73
- filtered_ns[key] = ns
74
- else:
75
- # Duplicated NS
76
- dupe_list_by_key = duplicates.setdefault(key, [])
77
- dupe_list_by_key.append(ns)
78
-
79
- for key, dupe_list in duplicates.items():
80
- dupe_list.append(filtered_ns[key])
81
- delete_flags = (
82
- [ns.delete for ns in dupe_list_by_key] if dupe_list_by_key else []
83
- )
63
+ namespaces_by_shard_key[key].append(namespace)
84
64
 
85
- if len(set(delete_flags)) > 1:
86
- # If true only some definitions in list have the delete flag.
87
- # this case will generate an error
88
- err = True
89
- # Remove the namespace found from the filtered list
90
- del filtered_ns[key]
91
- logging.error(DUPLICATES_LOG_MSG.format(key=key))
65
+ managed_namespaces = []
66
+ duplicate_errors = []
67
+
68
+ for key, namespaces in namespaces_by_shard_key.items():
69
+ if len(namespaces) == 1:
70
+ namespace = namespaces[0]
71
+ if not namespace.managed_by_external:
72
+ managed_namespaces.append(namespace)
92
73
  else:
93
- # If all namespaces have the same delete option
94
- # The action will be performaed
95
- logging.debug(DUPLICATES_LOG_MSG.format(key=key))
74
+ msg = f"Found multiple definitions for the namespace {key}"
75
+ duplicate_errors.append(NamespaceDuplicateError(msg))
76
+ logging.error(msg)
77
+
78
+ namespaces = filter_namespaces_by_cluster_and_namespace(
79
+ namespaces=managed_namespaces,
80
+ cluster_names=cluster_name,
81
+ namespace_names=namespace_name,
82
+ )
83
+
84
+ return namespaces, duplicate_errors
85
+
96
86
 
97
- return list(filtered_ns.values()), err
87
+ def build_desired_state(
88
+ namespaces: Iterable[NamespaceV1],
89
+ ) -> list[DesiredState]:
90
+ return [
91
+ DesiredState(
92
+ cluster=namespace.cluster.name,
93
+ namespace=namespace.name,
94
+ delete=namespace.delete or False,
95
+ )
96
+ for namespace in namespaces
97
+ ]
98
98
 
99
99
 
100
- def manage_namespaces(spec: Mapping[str, str], oc_map: OCMap, dry_run: bool) -> None:
101
- cluster = spec["cluster"]
102
- namespace = spec["namespace"]
103
- desired_state = spec["desired_state"]
100
+ def manage_namespace(
101
+ desired_state: DesiredState,
102
+ oc_map: OCMap,
103
+ dry_run: bool,
104
+ ) -> None:
105
+ namespace = desired_state.namespace
104
106
 
105
- oc = oc_map.get(cluster)
107
+ oc = oc_map.get(desired_state.cluster)
106
108
  if isinstance(oc, OCLogMsg):
107
109
  logging.log(level=oc.log_level, msg=oc.message)
108
- return None
110
+ return
109
111
 
110
- act = {NS_ACTION_CREATE: oc.new_project, NS_ACTION_DELETE: oc.delete_project}
112
+ current_delete = not oc.project_exists(namespace)
111
113
 
112
- exists = oc.project_exists(namespace)
113
- action = None
114
- if not exists and desired_state == NS_STATE_PRESENT:
115
- if namespace.startswith("openshift-"):
116
- raise ValueError('cannot request a project starting with "openshift-"')
117
- action = NS_ACTION_CREATE
118
- elif exists and desired_state == NS_STATE_ABSENT:
119
- action = NS_ACTION_DELETE
114
+ if desired_state.delete == current_delete:
115
+ return
120
116
 
121
- if action:
122
- logging.info([action, cluster, namespace])
123
- if not dry_run:
124
- act[action](namespace)
117
+ action = Action.DELETE if desired_state.delete else Action.CREATE
125
118
 
119
+ if namespace.startswith("openshift-"):
120
+ raise ValueError(f'cannot {action} a project starting with "openshift-"')
126
121
 
127
- def check_results(
128
- desired_state: Iterable[Mapping[str, str]], results: Iterable[Any]
129
- ) -> bool:
130
- err = False
122
+ logging.info([str(action), desired_state.cluster, namespace])
123
+ if not dry_run:
124
+ match action:
125
+ case Action.CREATE:
126
+ oc.new_project(namespace)
127
+ case Action.DELETE:
128
+ oc.delete_project(namespace)
129
+
130
+
131
+ def build_runtime_errors(
132
+ desired_state: Iterable[DesiredState],
133
+ results: Iterable[Any],
134
+ ) -> list[Exception]:
135
+ exceptions = []
131
136
  for s, e in zip(desired_state, results, strict=False):
132
137
  if isinstance(e, Exception):
133
- err = True
134
- msg = (
135
- f"cluster: {s['cluster']}, namespace: {s['namespace']}, "
136
- f"exception: {e!s}"
137
- )
138
- logging.error(msg)
139
- return err
138
+ e.add_note(f"cluster: {s.cluster}, namespace: {s.namespace}")
139
+ exceptions.append(e)
140
+ return exceptions
140
141
 
141
142
 
142
143
  @defer
@@ -149,15 +150,8 @@ def run(
149
150
  namespace_name: Sequence[str] | None = None,
150
151
  defer: Callable | None = None,
151
152
  ) -> None:
152
- all_namespaces = get_namespaces_minimal()
153
- shard_namespaces, duplicates = get_shard_namespaces(all_namespaces)
154
- namespaces = filter_namespaces_by_cluster_and_namespace(
155
- namespaces=shard_namespaces,
156
- cluster_names=cluster_name,
157
- namespace_names=namespace_name,
158
- )
159
-
160
- desired_state = get_desired_state(namespaces)
153
+ namespaces, duplicate_errors = get_namespaces(cluster_name, namespace_name)
154
+ desired_state = build_desired_state(namespaces)
161
155
 
162
156
  vault_settings = get_app_interface_vault_settings()
163
157
  secret_reader = create_secret_reader(use_vault=vault_settings.vault)
@@ -171,16 +165,17 @@ def run(
171
165
  thread_pool_size=thread_pool_size,
172
166
  init_projects=True,
173
167
  )
174
-
175
168
  if defer:
176
169
  defer(oc_map.cleanup)
177
170
 
178
171
  ob.publish_cluster_desired_metrics_from_state(
179
- desired_state, QONTRACT_INTEGRATION, "Namespace"
172
+ state=({"cluster": s.cluster} for s in desired_state),
173
+ integration=QONTRACT_INTEGRATION,
174
+ kind="Namespace",
180
175
  )
181
176
 
182
177
  results = threaded.run(
183
- manage_namespaces,
178
+ manage_namespace,
184
179
  desired_state,
185
180
  thread_pool_size,
186
181
  return_exceptions=True,
@@ -188,6 +183,7 @@ def run(
188
183
  oc_map=oc_map,
189
184
  )
190
185
 
191
- err = check_results(desired_state=desired_state, results=results)
192
- if err or duplicates:
193
- sys.exit(ExitCodes.ERROR)
186
+ runtime_errors = build_runtime_errors(desired_state, results)
187
+ errors = runtime_errors + duplicate_errors
188
+ if errors:
189
+ raise ExceptionGroup("Reconcile errors occurred", errors)
@@ -806,7 +806,11 @@ def fetch_data(
806
806
  init_api_resources=init_api_resources,
807
807
  )
808
808
  state_specs = ob.init_specs_to_fetch(
809
- ri, oc_map, namespaces=namespaces, override_managed_types=overrides
809
+ ri,
810
+ oc_map,
811
+ namespaces=namespaces,
812
+ override_managed_types=overrides,
813
+ cluster_scope_resource_validation=True,
810
814
  )
811
815
  threaded.run(fetch_states, state_specs, thread_pool_size, ri=ri, settings=settings)
812
816
 
@@ -861,7 +865,7 @@ def canonicalize_namespaces(
861
865
  elif providers[0] == "route":
862
866
  override = ["Route"]
863
867
  elif providers[0] == "prometheus-rule":
864
- override = ["PrometheusRule"]
868
+ override = ["PrometheusRule.monitoring.coreos.com"]
865
869
 
866
870
  namespace_info["openshiftResources"] = ors
867
871
  canonicalized_namespaces.append(namespace_info)
@@ -77,7 +77,7 @@ def get_namespaces_with_rhcs_certs(
77
77
  ) -> list[NamespaceV1]:
78
78
  result: list[NamespaceV1] = []
79
79
  for ns in rhcs_certs_query(query_func=query_func).namespaces or []:
80
- ob.aggregate_shared_resources_typed(cast("Any", ns)) # mypy: ignore[arg-type]
80
+ ob.aggregate_shared_resources_typed(ns)
81
81
  if (
82
82
  integration_is_enabled(QONTRACT_INTEGRATION, ns.cluster)
83
83
  and not bool(ns.delete)
@@ -149,7 +149,7 @@ def generate_vault_cert_secret(
149
149
  logging.info(
150
150
  f"Creating cert with service account credentials for '{cert_resource.service_account_name}'. cluster='{ns.cluster.name}', namespace='{ns.name}', secret='{cert_resource.secret_name}'"
151
151
  )
152
- sa_password = vault.read(cert_resource.service_account_password.dict())
152
+ sa_password = vault.read(cert_resource.service_account_password.model_dump())
153
153
  if dry_run:
154
154
  rhcs_cert = RhcsV2Cert(
155
155
  certificate="PLACEHOLDER_CERT",
@@ -171,12 +171,12 @@ def generate_vault_cert_secret(
171
171
  )
172
172
  vault.write(
173
173
  secret={
174
- "data": rhcs_cert.dict(by_alias=True),
174
+ "data": rhcs_cert.model_dump(by_alias=True),
175
175
  "path": f"{vault_base_path}/{ns.cluster.name}/{ns.name}/{cert_resource.secret_name}",
176
176
  },
177
177
  decode_base64=False,
178
178
  )
179
- return rhcs_cert.dict(by_alias=True)
179
+ return rhcs_cert.model_dump(by_alias=True)
180
180
 
181
181
 
182
182
  def fetch_openshift_resource_for_cert_resource(
@@ -277,7 +277,7 @@ def run(
277
277
  state_specs = ob.init_specs_to_fetch(
278
278
  ri,
279
279
  oc_map,
280
- namespaces=[ns.dict(by_alias=True) for ns in namespaces],
280
+ namespaces=[ns.model_dump(by_alias=True) for ns in namespaces],
281
281
  override_managed_types=["Secret"],
282
282
  )
283
283
  for spec in state_specs: