qontract-reconcile 0.9.1rc298__py3-none-any.whl → 0.10.1.dev1203__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 (843) hide show
  1. qontract_reconcile-0.10.1.dev1203.dist-info/METADATA +500 -0
  2. qontract_reconcile-0.10.1.dev1203.dist-info/RECORD +771 -0
  3. {qontract_reconcile-0.9.1rc298.dist-info → qontract_reconcile-0.10.1.dev1203.dist-info}/WHEEL +1 -2
  4. {qontract_reconcile-0.9.1rc298.dist-info → qontract_reconcile-0.10.1.dev1203.dist-info}/entry_points.txt +4 -2
  5. reconcile/acs_notifiers.py +126 -0
  6. reconcile/acs_policies.py +243 -0
  7. reconcile/acs_rbac.py +596 -0
  8. reconcile/aus/advanced_upgrade_service.py +621 -8
  9. reconcile/aus/aus_label_source.py +115 -0
  10. reconcile/aus/base.py +1053 -353
  11. reconcile/{utils → aus}/cluster_version_data.py +27 -12
  12. reconcile/aus/healthchecks.py +77 -0
  13. reconcile/aus/metrics.py +158 -0
  14. reconcile/aus/models.py +245 -5
  15. reconcile/aus/node_pool_spec.py +35 -0
  16. reconcile/aus/ocm_addons_upgrade_scheduler_org.py +225 -110
  17. reconcile/aus/ocm_upgrade_scheduler.py +76 -71
  18. reconcile/aus/ocm_upgrade_scheduler_org.py +81 -23
  19. reconcile/aus/version_gate_approver.py +204 -0
  20. reconcile/aus/version_gates/__init__.py +12 -0
  21. reconcile/aus/version_gates/handler.py +33 -0
  22. reconcile/aus/version_gates/ingress_gate_handler.py +32 -0
  23. reconcile/aus/version_gates/ocp_gate_handler.py +26 -0
  24. reconcile/aus/version_gates/sts_version_gate_handler.py +100 -0
  25. reconcile/aws_account_manager/README.md +5 -0
  26. reconcile/aws_account_manager/integration.py +373 -0
  27. reconcile/aws_account_manager/merge_request_manager.py +114 -0
  28. reconcile/aws_account_manager/metrics.py +39 -0
  29. reconcile/aws_account_manager/reconciler.py +403 -0
  30. reconcile/aws_account_manager/utils.py +41 -0
  31. reconcile/aws_ami_cleanup/integration.py +273 -0
  32. reconcile/aws_ami_share.py +18 -14
  33. reconcile/aws_cloudwatch_log_retention/integration.py +253 -0
  34. reconcile/aws_iam_keys.py +1 -1
  35. reconcile/aws_iam_password_reset.py +56 -20
  36. reconcile/aws_saml_idp/integration.py +204 -0
  37. reconcile/aws_saml_roles/integration.py +322 -0
  38. reconcile/aws_support_cases_sos.py +2 -2
  39. reconcile/aws_version_sync/integration.py +430 -0
  40. reconcile/aws_version_sync/merge_request_manager/merge_request.py +156 -0
  41. reconcile/aws_version_sync/merge_request_manager/merge_request_manager.py +160 -0
  42. reconcile/aws_version_sync/utils.py +64 -0
  43. reconcile/blackbox_exporter_endpoint_monitoring.py +2 -5
  44. reconcile/change_owners/README.md +34 -0
  45. reconcile/change_owners/approver.py +7 -9
  46. reconcile/change_owners/bundle.py +134 -9
  47. reconcile/change_owners/change_log_tracking.py +236 -0
  48. reconcile/change_owners/change_owners.py +204 -194
  49. reconcile/change_owners/change_types.py +183 -265
  50. reconcile/change_owners/changes.py +488 -0
  51. reconcile/change_owners/decision.py +120 -41
  52. reconcile/change_owners/diff.py +63 -92
  53. reconcile/change_owners/implicit_ownership.py +19 -16
  54. reconcile/change_owners/self_service_roles.py +158 -35
  55. reconcile/change_owners/tester.py +20 -18
  56. reconcile/checkpoint.py +4 -6
  57. reconcile/cli.py +1523 -242
  58. reconcile/closedbox_endpoint_monitoring_base.py +10 -17
  59. reconcile/cluster_auth_rhidp/integration.py +257 -0
  60. reconcile/cluster_deployment_mapper.py +2 -5
  61. reconcile/cna/assets/asset.py +4 -7
  62. reconcile/cna/assets/null.py +2 -5
  63. reconcile/cna/integration.py +2 -3
  64. reconcile/cna/state.py +6 -9
  65. reconcile/dashdotdb_base.py +31 -10
  66. reconcile/dashdotdb_cso.py +3 -6
  67. reconcile/dashdotdb_dora.py +530 -0
  68. reconcile/dashdotdb_dvo.py +10 -13
  69. reconcile/dashdotdb_slo.py +75 -19
  70. reconcile/database_access_manager.py +753 -0
  71. reconcile/deadmanssnitch.py +207 -0
  72. reconcile/dynatrace_token_provider/dependencies.py +69 -0
  73. reconcile/dynatrace_token_provider/integration.py +656 -0
  74. reconcile/dynatrace_token_provider/metrics.py +62 -0
  75. reconcile/dynatrace_token_provider/model.py +14 -0
  76. reconcile/dynatrace_token_provider/ocm.py +140 -0
  77. reconcile/dynatrace_token_provider/validate.py +48 -0
  78. reconcile/endpoints_discovery/integration.py +348 -0
  79. reconcile/endpoints_discovery/merge_request.py +96 -0
  80. reconcile/endpoints_discovery/merge_request_manager.py +178 -0
  81. reconcile/external_resources/aws.py +204 -0
  82. reconcile/external_resources/factories.py +163 -0
  83. reconcile/external_resources/integration.py +194 -0
  84. reconcile/external_resources/integration_secrets_sync.py +47 -0
  85. reconcile/external_resources/manager.py +405 -0
  86. reconcile/external_resources/meta.py +17 -0
  87. reconcile/external_resources/metrics.py +95 -0
  88. reconcile/external_resources/model.py +350 -0
  89. reconcile/external_resources/reconciler.py +265 -0
  90. reconcile/external_resources/secrets_sync.py +465 -0
  91. reconcile/external_resources/state.py +258 -0
  92. reconcile/gabi_authorized_users.py +19 -11
  93. reconcile/gcr_mirror.py +43 -34
  94. reconcile/github_org.py +4 -6
  95. reconcile/github_owners.py +1 -1
  96. reconcile/github_repo_invites.py +2 -5
  97. reconcile/gitlab_fork_compliance.py +14 -13
  98. reconcile/gitlab_housekeeping.py +185 -91
  99. reconcile/gitlab_labeler.py +15 -14
  100. reconcile/gitlab_members.py +126 -120
  101. reconcile/gitlab_owners.py +53 -66
  102. reconcile/gitlab_permissions.py +167 -6
  103. reconcile/glitchtip/README.md +150 -0
  104. reconcile/glitchtip/integration.py +99 -51
  105. reconcile/glitchtip/reconciler.py +99 -70
  106. reconcile/glitchtip_project_alerts/__init__.py +0 -0
  107. reconcile/glitchtip_project_alerts/integration.py +333 -0
  108. reconcile/glitchtip_project_dsn/integration.py +43 -43
  109. reconcile/gql_definitions/acs/__init__.py +0 -0
  110. reconcile/gql_definitions/acs/acs_instances.py +83 -0
  111. reconcile/gql_definitions/acs/acs_policies.py +239 -0
  112. reconcile/gql_definitions/acs/acs_rbac.py +111 -0
  113. reconcile/gql_definitions/advanced_upgrade_service/aus_clusters.py +46 -8
  114. reconcile/gql_definitions/advanced_upgrade_service/aus_organization.py +38 -8
  115. reconcile/gql_definitions/app_interface_metrics_exporter/__init__.py +0 -0
  116. reconcile/gql_definitions/app_interface_metrics_exporter/onboarding_status.py +61 -0
  117. reconcile/gql_definitions/aws_account_manager/__init__.py +0 -0
  118. reconcile/gql_definitions/aws_account_manager/aws_accounts.py +177 -0
  119. reconcile/gql_definitions/aws_ami_cleanup/__init__.py +0 -0
  120. reconcile/gql_definitions/aws_ami_cleanup/aws_accounts.py +161 -0
  121. reconcile/gql_definitions/aws_saml_idp/__init__.py +0 -0
  122. reconcile/gql_definitions/aws_saml_idp/aws_accounts.py +117 -0
  123. reconcile/gql_definitions/aws_saml_roles/__init__.py +0 -0
  124. reconcile/gql_definitions/aws_saml_roles/aws_accounts.py +117 -0
  125. reconcile/gql_definitions/aws_saml_roles/roles.py +97 -0
  126. reconcile/gql_definitions/aws_version_sync/__init__.py +0 -0
  127. reconcile/gql_definitions/aws_version_sync/clusters.py +83 -0
  128. reconcile/gql_definitions/aws_version_sync/namespaces.py +143 -0
  129. reconcile/gql_definitions/change_owners/queries/change_types.py +16 -29
  130. reconcile/gql_definitions/change_owners/queries/self_service_roles.py +45 -11
  131. reconcile/gql_definitions/cluster_auth_rhidp/__init__.py +0 -0
  132. reconcile/gql_definitions/cluster_auth_rhidp/clusters.py +128 -0
  133. reconcile/gql_definitions/cna/queries/cna_provisioners.py +6 -8
  134. reconcile/gql_definitions/cna/queries/cna_resources.py +3 -5
  135. reconcile/gql_definitions/common/alerting_services_settings.py +2 -2
  136. reconcile/gql_definitions/common/app_code_component_repos.py +9 -5
  137. reconcile/gql_definitions/{glitchtip/glitchtip_settings.py → common/app_interface_custom_messages.py} +14 -16
  138. reconcile/gql_definitions/common/app_interface_dms_settings.py +86 -0
  139. reconcile/gql_definitions/common/app_interface_repo_settings.py +2 -2
  140. reconcile/gql_definitions/common/app_interface_state_settings.py +3 -5
  141. reconcile/gql_definitions/common/app_interface_vault_settings.py +3 -5
  142. reconcile/gql_definitions/common/app_quay_repos_escalation_policies.py +120 -0
  143. reconcile/gql_definitions/common/apps.py +72 -0
  144. reconcile/gql_definitions/common/aws_vpc_requests.py +109 -0
  145. reconcile/gql_definitions/common/aws_vpcs.py +84 -0
  146. reconcile/gql_definitions/common/clusters.py +120 -254
  147. reconcile/gql_definitions/common/clusters_minimal.py +11 -35
  148. reconcile/gql_definitions/common/clusters_with_dms.py +72 -0
  149. reconcile/gql_definitions/common/clusters_with_peering.py +70 -98
  150. reconcile/gql_definitions/common/github_orgs.py +2 -2
  151. reconcile/gql_definitions/common/jira_settings.py +68 -0
  152. reconcile/gql_definitions/common/jiralert_settings.py +68 -0
  153. reconcile/gql_definitions/common/namespaces.py +74 -32
  154. reconcile/gql_definitions/common/namespaces_minimal.py +4 -10
  155. reconcile/gql_definitions/common/ocm_env_telemeter.py +95 -0
  156. reconcile/gql_definitions/common/ocm_environments.py +4 -2
  157. reconcile/gql_definitions/common/pagerduty_instances.py +5 -5
  158. reconcile/gql_definitions/common/pgp_reencryption_settings.py +5 -11
  159. reconcile/gql_definitions/common/pipeline_providers.py +45 -90
  160. reconcile/gql_definitions/common/quay_instances.py +64 -0
  161. reconcile/gql_definitions/common/quay_orgs.py +68 -0
  162. reconcile/gql_definitions/common/reserved_networks.py +94 -0
  163. reconcile/gql_definitions/common/saas_files.py +133 -95
  164. reconcile/gql_definitions/common/saas_target_namespaces.py +41 -26
  165. reconcile/gql_definitions/common/saasherder_settings.py +2 -2
  166. reconcile/gql_definitions/common/slack_workspaces.py +62 -0
  167. reconcile/gql_definitions/common/smtp_client_settings.py +2 -2
  168. reconcile/gql_definitions/common/state_aws_account.py +77 -0
  169. reconcile/gql_definitions/common/users.py +3 -2
  170. reconcile/gql_definitions/cost_report/__init__.py +0 -0
  171. reconcile/gql_definitions/cost_report/app_names.py +68 -0
  172. reconcile/gql_definitions/cost_report/cost_namespaces.py +86 -0
  173. reconcile/gql_definitions/cost_report/settings.py +77 -0
  174. reconcile/gql_definitions/dashdotdb_slo/slo_documents_query.py +42 -12
  175. reconcile/gql_definitions/dynatrace_token_provider/__init__.py +0 -0
  176. reconcile/gql_definitions/dynatrace_token_provider/dynatrace_bootstrap_tokens.py +79 -0
  177. reconcile/gql_definitions/dynatrace_token_provider/token_specs.py +84 -0
  178. reconcile/gql_definitions/endpoints_discovery/__init__.py +0 -0
  179. reconcile/gql_definitions/endpoints_discovery/namespaces.py +127 -0
  180. reconcile/gql_definitions/external_resources/__init__.py +0 -0
  181. reconcile/gql_definitions/external_resources/aws_accounts.py +73 -0
  182. reconcile/gql_definitions/external_resources/external_resources_modules.py +78 -0
  183. reconcile/gql_definitions/external_resources/external_resources_namespaces.py +1111 -0
  184. reconcile/gql_definitions/external_resources/external_resources_settings.py +98 -0
  185. reconcile/gql_definitions/fragments/aus_organization.py +34 -39
  186. reconcile/gql_definitions/fragments/aws_account_common.py +62 -0
  187. reconcile/gql_definitions/fragments/aws_account_managed.py +57 -0
  188. reconcile/gql_definitions/fragments/aws_account_sso.py +35 -0
  189. reconcile/gql_definitions/fragments/aws_infra_management_account.py +2 -2
  190. reconcile/gql_definitions/fragments/aws_vpc.py +47 -0
  191. reconcile/gql_definitions/fragments/aws_vpc_request.py +65 -0
  192. reconcile/gql_definitions/fragments/aws_vpc_request_subnet.py +29 -0
  193. reconcile/gql_definitions/fragments/deplopy_resources.py +7 -7
  194. reconcile/gql_definitions/fragments/disable.py +28 -0
  195. reconcile/gql_definitions/fragments/jumphost_common_fields.py +2 -2
  196. reconcile/gql_definitions/fragments/membership_source.py +47 -0
  197. reconcile/gql_definitions/fragments/minimal_ocm_organization.py +29 -0
  198. reconcile/gql_definitions/fragments/oc_connection_cluster.py +4 -9
  199. reconcile/gql_definitions/fragments/ocm_environment.py +5 -5
  200. reconcile/gql_definitions/fragments/pipeline_provider_retention.py +30 -0
  201. reconcile/gql_definitions/fragments/prometheus_instance.py +48 -0
  202. reconcile/gql_definitions/fragments/resource_limits_requirements.py +29 -0
  203. reconcile/gql_definitions/fragments/{resource_requirements.py → resource_requests_requirements.py} +3 -3
  204. reconcile/gql_definitions/fragments/resource_values.py +2 -2
  205. reconcile/gql_definitions/fragments/saas_target_namespace.py +55 -12
  206. reconcile/gql_definitions/fragments/serviceaccount_token.py +38 -0
  207. reconcile/gql_definitions/fragments/terraform_state.py +36 -0
  208. reconcile/gql_definitions/fragments/upgrade_policy.py +5 -3
  209. reconcile/gql_definitions/fragments/user.py +3 -2
  210. reconcile/gql_definitions/fragments/vault_secret.py +2 -2
  211. reconcile/gql_definitions/gitlab_members/gitlab_instances.py +6 -2
  212. reconcile/gql_definitions/gitlab_members/permissions.py +3 -5
  213. reconcile/gql_definitions/glitchtip/glitchtip_instance.py +16 -2
  214. reconcile/gql_definitions/glitchtip/glitchtip_project.py +22 -23
  215. reconcile/gql_definitions/glitchtip_project_alerts/__init__.py +0 -0
  216. reconcile/gql_definitions/glitchtip_project_alerts/glitchtip_project.py +173 -0
  217. reconcile/gql_definitions/integrations/integrations.py +62 -45
  218. reconcile/gql_definitions/introspection.json +51176 -0
  219. reconcile/gql_definitions/jenkins_configs/jenkins_configs.py +13 -5
  220. reconcile/gql_definitions/jenkins_configs/jenkins_instances.py +79 -0
  221. reconcile/gql_definitions/jira/__init__.py +0 -0
  222. reconcile/gql_definitions/jira/jira_servers.py +80 -0
  223. reconcile/gql_definitions/jira_permissions_validator/__init__.py +0 -0
  224. reconcile/gql_definitions/jira_permissions_validator/jira_boards_for_permissions_validator.py +131 -0
  225. reconcile/gql_definitions/jumphosts/jumphosts.py +3 -5
  226. reconcile/gql_definitions/ldap_groups/__init__.py +0 -0
  227. reconcile/gql_definitions/ldap_groups/roles.py +111 -0
  228. reconcile/gql_definitions/ldap_groups/settings.py +79 -0
  229. reconcile/gql_definitions/maintenance/__init__.py +0 -0
  230. reconcile/gql_definitions/maintenance/maintenances.py +101 -0
  231. reconcile/gql_definitions/membershipsources/__init__.py +0 -0
  232. reconcile/gql_definitions/membershipsources/roles.py +112 -0
  233. reconcile/gql_definitions/ocm_labels/__init__.py +0 -0
  234. reconcile/gql_definitions/ocm_labels/clusters.py +112 -0
  235. reconcile/gql_definitions/ocm_labels/organizations.py +78 -0
  236. reconcile/gql_definitions/ocm_subscription_labels/__init__.py +0 -0
  237. reconcile/gql_definitions/openshift_cluster_bots/__init__.py +0 -0
  238. reconcile/gql_definitions/openshift_cluster_bots/clusters.py +126 -0
  239. reconcile/gql_definitions/openshift_groups/managed_groups.py +2 -2
  240. reconcile/gql_definitions/openshift_groups/managed_roles.py +3 -2
  241. reconcile/gql_definitions/openshift_serviceaccount_tokens/__init__.py +0 -0
  242. reconcile/gql_definitions/openshift_serviceaccount_tokens/tokens.py +132 -0
  243. reconcile/gql_definitions/quay_membership/quay_membership.py +3 -5
  244. reconcile/gql_definitions/rhidp/__init__.py +0 -0
  245. reconcile/gql_definitions/rhidp/organizations.py +96 -0
  246. reconcile/gql_definitions/service_dependencies/jenkins_instance_fragment.py +2 -2
  247. reconcile/gql_definitions/service_dependencies/service_dependencies.py +9 -31
  248. reconcile/gql_definitions/sharding/aws_accounts.py +2 -2
  249. reconcile/gql_definitions/sharding/ocm_organization.py +63 -0
  250. reconcile/gql_definitions/skupper_network/site_controller_template.py +2 -2
  251. reconcile/gql_definitions/skupper_network/skupper_networks.py +12 -38
  252. reconcile/gql_definitions/slack_usergroups/clusters.py +2 -2
  253. reconcile/gql_definitions/slack_usergroups/permissions.py +8 -15
  254. reconcile/gql_definitions/slack_usergroups/users.py +3 -2
  255. reconcile/gql_definitions/slo_documents/__init__.py +0 -0
  256. reconcile/gql_definitions/slo_documents/slo_documents.py +142 -0
  257. reconcile/gql_definitions/status_board/__init__.py +0 -0
  258. reconcile/gql_definitions/status_board/status_board.py +163 -0
  259. reconcile/gql_definitions/statuspage/statuspages.py +56 -7
  260. reconcile/gql_definitions/templating/__init__.py +0 -0
  261. reconcile/gql_definitions/templating/template_collection.py +130 -0
  262. reconcile/gql_definitions/templating/templates.py +108 -0
  263. reconcile/gql_definitions/terraform_cloudflare_dns/app_interface_cloudflare_dns_settings.py +4 -8
  264. reconcile/gql_definitions/terraform_cloudflare_dns/terraform_cloudflare_zones.py +8 -8
  265. reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_accounts.py +6 -8
  266. reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_resources.py +45 -56
  267. reconcile/gql_definitions/terraform_cloudflare_users/app_interface_setting_cloudflare_and_vault.py +4 -8
  268. reconcile/gql_definitions/terraform_cloudflare_users/terraform_cloudflare_roles.py +4 -8
  269. reconcile/gql_definitions/terraform_init/__init__.py +0 -0
  270. reconcile/gql_definitions/terraform_init/aws_accounts.py +93 -0
  271. reconcile/gql_definitions/terraform_repo/__init__.py +0 -0
  272. reconcile/gql_definitions/terraform_repo/terraform_repo.py +141 -0
  273. reconcile/gql_definitions/terraform_resources/database_access_manager.py +158 -0
  274. reconcile/gql_definitions/terraform_resources/terraform_resources_namespaces.py +153 -162
  275. reconcile/gql_definitions/terraform_tgw_attachments/__init__.py +0 -0
  276. reconcile/gql_definitions/terraform_tgw_attachments/aws_accounts.py +119 -0
  277. reconcile/gql_definitions/unleash_feature_toggles/__init__.py +0 -0
  278. reconcile/gql_definitions/unleash_feature_toggles/feature_toggles.py +113 -0
  279. reconcile/gql_definitions/vault_instances/vault_instances.py +17 -50
  280. reconcile/gql_definitions/vault_policies/vault_policies.py +2 -2
  281. reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator.py +49 -12
  282. reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator_peered_cluster_fragment.py +7 -2
  283. reconcile/integrations_manager.py +25 -13
  284. reconcile/jenkins/types.py +5 -1
  285. reconcile/jenkins_base.py +36 -0
  286. reconcile/jenkins_job_builder.py +10 -48
  287. reconcile/jenkins_job_builds_cleaner.py +40 -25
  288. reconcile/jenkins_job_cleaner.py +1 -3
  289. reconcile/jenkins_roles.py +22 -26
  290. reconcile/jenkins_webhooks.py +9 -6
  291. reconcile/jenkins_worker_fleets.py +11 -6
  292. reconcile/jira_permissions_validator.py +340 -0
  293. reconcile/jira_watcher.py +3 -5
  294. reconcile/ldap_groups/__init__.py +0 -0
  295. reconcile/ldap_groups/integration.py +279 -0
  296. reconcile/ldap_users.py +3 -0
  297. reconcile/ocm/types.py +39 -59
  298. reconcile/ocm_additional_routers.py +0 -1
  299. reconcile/ocm_addons_upgrade_tests_trigger.py +10 -15
  300. reconcile/ocm_aws_infrastructure_access.py +30 -32
  301. reconcile/ocm_clusters.py +217 -130
  302. reconcile/ocm_external_configuration_labels.py +15 -0
  303. reconcile/ocm_github_idp.py +1 -1
  304. reconcile/ocm_groups.py +25 -5
  305. reconcile/ocm_internal_notifications/__init__.py +0 -0
  306. reconcile/ocm_internal_notifications/integration.py +119 -0
  307. reconcile/ocm_labels/__init__.py +0 -0
  308. reconcile/ocm_labels/integration.py +409 -0
  309. reconcile/ocm_machine_pools.py +517 -108
  310. reconcile/ocm_upgrade_scheduler_org_updater.py +15 -11
  311. reconcile/openshift_base.py +609 -207
  312. reconcile/openshift_cluster_bots.py +344 -0
  313. reconcile/openshift_clusterrolebindings.py +15 -15
  314. reconcile/openshift_groups.py +42 -45
  315. reconcile/openshift_limitranges.py +1 -0
  316. reconcile/openshift_namespace_labels.py +22 -28
  317. reconcile/openshift_namespaces.py +22 -22
  318. reconcile/openshift_network_policies.py +4 -8
  319. reconcile/openshift_prometheus_rules.py +43 -0
  320. reconcile/openshift_resourcequotas.py +2 -16
  321. reconcile/openshift_resources.py +12 -10
  322. reconcile/openshift_resources_base.py +304 -328
  323. reconcile/openshift_rolebindings.py +18 -20
  324. reconcile/openshift_saas_deploy.py +105 -21
  325. reconcile/openshift_saas_deploy_change_tester.py +30 -35
  326. reconcile/openshift_saas_deploy_trigger_base.py +39 -36
  327. reconcile/openshift_saas_deploy_trigger_cleaner.py +41 -27
  328. reconcile/openshift_saas_deploy_trigger_configs.py +1 -2
  329. reconcile/openshift_saas_deploy_trigger_images.py +1 -2
  330. reconcile/openshift_saas_deploy_trigger_moving_commits.py +1 -2
  331. reconcile/openshift_saas_deploy_trigger_upstream_jobs.py +1 -2
  332. reconcile/openshift_serviceaccount_tokens.py +138 -74
  333. reconcile/openshift_tekton_resources.py +89 -24
  334. reconcile/openshift_upgrade_watcher.py +110 -62
  335. reconcile/openshift_users.py +16 -15
  336. reconcile/openshift_vault_secrets.py +11 -6
  337. reconcile/oum/__init__.py +0 -0
  338. reconcile/oum/base.py +387 -0
  339. reconcile/oum/labelset.py +55 -0
  340. reconcile/oum/metrics.py +71 -0
  341. reconcile/oum/models.py +69 -0
  342. reconcile/oum/providers.py +59 -0
  343. reconcile/oum/standalone.py +196 -0
  344. reconcile/prometheus_rules_tester/integration.py +31 -23
  345. reconcile/quay_base.py +4 -1
  346. reconcile/quay_membership.py +1 -2
  347. reconcile/quay_mirror.py +111 -61
  348. reconcile/quay_mirror_org.py +34 -21
  349. reconcile/quay_permissions.py +7 -3
  350. reconcile/quay_repos.py +24 -32
  351. reconcile/queries.py +263 -198
  352. reconcile/query_validator.py +3 -5
  353. reconcile/resource_scraper.py +3 -4
  354. reconcile/{template_tester.py → resource_template_tester.py} +3 -3
  355. reconcile/rhidp/__init__.py +0 -0
  356. reconcile/rhidp/common.py +214 -0
  357. reconcile/rhidp/metrics.py +20 -0
  358. reconcile/rhidp/ocm_oidc_idp/__init__.py +0 -0
  359. reconcile/rhidp/ocm_oidc_idp/base.py +221 -0
  360. reconcile/rhidp/ocm_oidc_idp/integration.py +56 -0
  361. reconcile/rhidp/ocm_oidc_idp/metrics.py +22 -0
  362. reconcile/rhidp/sso_client/__init__.py +0 -0
  363. reconcile/rhidp/sso_client/base.py +266 -0
  364. reconcile/rhidp/sso_client/integration.py +60 -0
  365. reconcile/rhidp/sso_client/metrics.py +39 -0
  366. reconcile/run_integration.py +293 -0
  367. reconcile/saas_auto_promotions_manager/integration.py +69 -24
  368. reconcile/saas_auto_promotions_manager/merge_request_manager/batcher.py +208 -0
  369. reconcile/saas_auto_promotions_manager/merge_request_manager/desired_state.py +28 -0
  370. reconcile/saas_auto_promotions_manager/merge_request_manager/merge_request.py +3 -4
  371. reconcile/saas_auto_promotions_manager/merge_request_manager/merge_request_manager_v2.py +172 -0
  372. reconcile/saas_auto_promotions_manager/merge_request_manager/metrics.py +42 -0
  373. reconcile/saas_auto_promotions_manager/merge_request_manager/mr_parser.py +226 -0
  374. reconcile/saas_auto_promotions_manager/merge_request_manager/open_merge_requests.py +23 -0
  375. reconcile/saas_auto_promotions_manager/merge_request_manager/renderer.py +108 -32
  376. reconcile/saas_auto_promotions_manager/meta.py +4 -0
  377. reconcile/saas_auto_promotions_manager/publisher.py +32 -4
  378. reconcile/saas_auto_promotions_manager/s3_exporter.py +77 -0
  379. reconcile/saas_auto_promotions_manager/subscriber.py +110 -23
  380. reconcile/saas_auto_promotions_manager/utils/saas_files_inventory.py +48 -41
  381. reconcile/saas_file_validator.py +16 -6
  382. reconcile/sendgrid_teammates.py +27 -12
  383. reconcile/service_dependencies.py +0 -3
  384. reconcile/signalfx_endpoint_monitoring.py +2 -5
  385. reconcile/skupper_network/integration.py +10 -11
  386. reconcile/skupper_network/models.py +3 -5
  387. reconcile/skupper_network/reconciler.py +28 -35
  388. reconcile/skupper_network/site_controller.py +8 -8
  389. reconcile/slack_base.py +4 -7
  390. reconcile/slack_usergroups.py +249 -171
  391. reconcile/sql_query.py +324 -171
  392. reconcile/status.py +0 -1
  393. reconcile/status_board.py +275 -0
  394. reconcile/statuspage/__init__.py +0 -5
  395. reconcile/statuspage/atlassian.py +219 -80
  396. reconcile/statuspage/integration.py +9 -97
  397. reconcile/statuspage/integrations/__init__.py +0 -0
  398. reconcile/statuspage/integrations/components.py +77 -0
  399. reconcile/statuspage/integrations/maintenances.py +111 -0
  400. reconcile/statuspage/page.py +107 -72
  401. reconcile/statuspage/state.py +6 -11
  402. reconcile/statuspage/status.py +8 -12
  403. reconcile/templates/rosa-classic-cluster-creation.sh.j2 +60 -0
  404. reconcile/templates/rosa-hcp-cluster-creation.sh.j2 +61 -0
  405. reconcile/templating/__init__.py +0 -0
  406. reconcile/templating/lib/__init__.py +0 -0
  407. reconcile/templating/lib/merge_request_manager.py +180 -0
  408. reconcile/templating/lib/model.py +20 -0
  409. reconcile/templating/lib/rendering.py +191 -0
  410. reconcile/templating/renderer.py +410 -0
  411. reconcile/templating/validator.py +153 -0
  412. reconcile/terraform_aws_route53.py +13 -10
  413. reconcile/terraform_cloudflare_dns.py +92 -122
  414. reconcile/terraform_cloudflare_resources.py +15 -13
  415. reconcile/terraform_cloudflare_users.py +27 -27
  416. reconcile/terraform_init/__init__.py +0 -0
  417. reconcile/terraform_init/integration.py +165 -0
  418. reconcile/terraform_init/merge_request.py +57 -0
  419. reconcile/terraform_init/merge_request_manager.py +102 -0
  420. reconcile/terraform_repo.py +403 -0
  421. reconcile/terraform_resources.py +266 -168
  422. reconcile/terraform_tgw_attachments.py +417 -167
  423. reconcile/terraform_users.py +40 -17
  424. reconcile/terraform_vpc_peerings.py +310 -142
  425. reconcile/terraform_vpc_resources/__init__.py +0 -0
  426. reconcile/terraform_vpc_resources/integration.py +220 -0
  427. reconcile/terraform_vpc_resources/merge_request.py +57 -0
  428. reconcile/terraform_vpc_resources/merge_request_manager.py +107 -0
  429. reconcile/typed_queries/alerting_services_settings.py +1 -2
  430. reconcile/typed_queries/app_interface_custom_messages.py +24 -0
  431. reconcile/typed_queries/app_interface_deadmanssnitch_settings.py +17 -0
  432. reconcile/typed_queries/app_interface_metrics_exporter/__init__.py +0 -0
  433. reconcile/typed_queries/app_interface_metrics_exporter/onboarding_status.py +13 -0
  434. reconcile/typed_queries/app_interface_repo_url.py +1 -2
  435. reconcile/typed_queries/app_interface_state_settings.py +1 -3
  436. reconcile/typed_queries/app_interface_vault_settings.py +1 -2
  437. reconcile/typed_queries/app_quay_repos_escalation_policies.py +14 -0
  438. reconcile/typed_queries/apps.py +11 -0
  439. reconcile/typed_queries/aws_vpc_requests.py +9 -0
  440. reconcile/typed_queries/aws_vpcs.py +12 -0
  441. reconcile/typed_queries/cloudflare.py +10 -0
  442. reconcile/typed_queries/clusters.py +7 -5
  443. reconcile/typed_queries/clusters_minimal.py +6 -5
  444. reconcile/typed_queries/clusters_with_dms.py +16 -0
  445. reconcile/typed_queries/cost_report/__init__.py +0 -0
  446. reconcile/typed_queries/cost_report/app_names.py +22 -0
  447. reconcile/typed_queries/cost_report/cost_namespaces.py +43 -0
  448. reconcile/typed_queries/cost_report/settings.py +15 -0
  449. reconcile/typed_queries/dynatrace.py +10 -0
  450. reconcile/typed_queries/dynatrace_environments.py +14 -0
  451. reconcile/typed_queries/dynatrace_token_provider_token_specs.py +14 -0
  452. reconcile/typed_queries/external_resources.py +46 -0
  453. reconcile/typed_queries/get_state_aws_account.py +20 -0
  454. reconcile/typed_queries/glitchtip.py +10 -0
  455. reconcile/typed_queries/jenkins.py +25 -0
  456. reconcile/typed_queries/jira.py +7 -0
  457. reconcile/typed_queries/jira_settings.py +16 -0
  458. reconcile/typed_queries/jiralert_settings.py +22 -0
  459. reconcile/typed_queries/ocm.py +8 -0
  460. reconcile/typed_queries/pagerduty_instances.py +2 -7
  461. reconcile/typed_queries/quay.py +23 -0
  462. reconcile/typed_queries/repos.py +20 -8
  463. reconcile/typed_queries/reserved_networks.py +12 -0
  464. reconcile/typed_queries/saas_files.py +221 -167
  465. reconcile/typed_queries/slack.py +7 -0
  466. reconcile/typed_queries/slo_documents.py +12 -0
  467. reconcile/typed_queries/status_board.py +58 -0
  468. reconcile/typed_queries/tekton_pipeline_providers.py +1 -2
  469. reconcile/typed_queries/terraform_namespaces.py +1 -2
  470. reconcile/typed_queries/terraform_tgw_attachments/__init__.py +0 -0
  471. reconcile/typed_queries/terraform_tgw_attachments/aws_accounts.py +16 -0
  472. reconcile/typed_queries/unleash.py +10 -0
  473. reconcile/typed_queries/users.py +11 -0
  474. reconcile/typed_queries/vault.py +10 -0
  475. reconcile/unleash_feature_toggles/__init__.py +0 -0
  476. reconcile/unleash_feature_toggles/integration.py +287 -0
  477. reconcile/utils/acs/__init__.py +0 -0
  478. reconcile/utils/acs/base.py +81 -0
  479. reconcile/utils/acs/notifiers.py +143 -0
  480. reconcile/utils/acs/policies.py +163 -0
  481. reconcile/utils/acs/rbac.py +277 -0
  482. reconcile/utils/aggregated_list.py +11 -9
  483. reconcile/utils/amtool.py +6 -4
  484. reconcile/utils/aws_api.py +279 -66
  485. reconcile/utils/aws_api_typed/__init__.py +0 -0
  486. reconcile/utils/aws_api_typed/account.py +23 -0
  487. reconcile/utils/aws_api_typed/api.py +273 -0
  488. reconcile/utils/aws_api_typed/dynamodb.py +16 -0
  489. reconcile/utils/aws_api_typed/iam.py +67 -0
  490. reconcile/utils/aws_api_typed/organization.py +152 -0
  491. reconcile/utils/aws_api_typed/s3.py +26 -0
  492. reconcile/utils/aws_api_typed/service_quotas.py +79 -0
  493. reconcile/utils/aws_api_typed/sts.py +36 -0
  494. reconcile/utils/aws_api_typed/support.py +79 -0
  495. reconcile/utils/aws_helper.py +42 -3
  496. reconcile/utils/batches.py +11 -0
  497. reconcile/utils/binary.py +7 -9
  498. reconcile/utils/cloud_resource_best_practice/__init__.py +0 -0
  499. reconcile/utils/cloud_resource_best_practice/aws_rds.py +66 -0
  500. reconcile/utils/clusterhealth/__init__.py +0 -0
  501. reconcile/utils/clusterhealth/providerbase.py +39 -0
  502. reconcile/utils/clusterhealth/telemeter.py +39 -0
  503. reconcile/utils/config.py +3 -4
  504. reconcile/utils/deadmanssnitch_api.py +86 -0
  505. reconcile/utils/differ.py +205 -0
  506. reconcile/utils/disabled_integrations.py +4 -6
  507. reconcile/utils/dynatrace/__init__.py +0 -0
  508. reconcile/utils/dynatrace/client.py +93 -0
  509. reconcile/utils/early_exit_cache.py +289 -0
  510. reconcile/utils/elasticsearch_exceptions.py +5 -0
  511. reconcile/utils/environ.py +2 -2
  512. reconcile/utils/exceptions.py +4 -0
  513. reconcile/utils/expiration.py +4 -8
  514. reconcile/utils/extended_early_exit.py +210 -0
  515. reconcile/utils/external_resource_spec.py +34 -12
  516. reconcile/utils/external_resources.py +48 -20
  517. reconcile/utils/filtering.py +16 -0
  518. reconcile/utils/git.py +49 -16
  519. reconcile/utils/github_api.py +10 -9
  520. reconcile/utils/gitlab_api.py +333 -190
  521. reconcile/utils/glitchtip/client.py +97 -100
  522. reconcile/utils/glitchtip/models.py +89 -11
  523. reconcile/utils/gql.py +157 -58
  524. reconcile/utils/grouping.py +17 -0
  525. reconcile/utils/helm.py +89 -18
  526. reconcile/utils/helpers.py +51 -0
  527. reconcile/utils/imap_client.py +5 -6
  528. reconcile/utils/internal_groups/__init__.py +0 -0
  529. reconcile/utils/internal_groups/client.py +160 -0
  530. reconcile/utils/internal_groups/models.py +71 -0
  531. reconcile/utils/jenkins_api.py +10 -34
  532. reconcile/utils/jinja2/__init__.py +0 -0
  533. reconcile/utils/{jinja2_ext.py → jinja2/extensions.py} +6 -4
  534. reconcile/utils/jinja2/filters.py +142 -0
  535. reconcile/utils/jinja2/utils.py +278 -0
  536. reconcile/utils/jira_client.py +165 -8
  537. reconcile/utils/jjb_client.py +47 -35
  538. reconcile/utils/jobcontroller/__init__.py +0 -0
  539. reconcile/utils/jobcontroller/controller.py +413 -0
  540. reconcile/utils/jobcontroller/models.py +195 -0
  541. reconcile/utils/jsonpath.py +4 -5
  542. reconcile/utils/jump_host.py +13 -12
  543. reconcile/utils/keycloak.py +106 -0
  544. reconcile/utils/ldap_client.py +35 -6
  545. reconcile/utils/lean_terraform_client.py +115 -6
  546. reconcile/utils/membershipsources/__init__.py +0 -0
  547. reconcile/utils/membershipsources/app_interface_resolver.py +60 -0
  548. reconcile/utils/membershipsources/models.py +91 -0
  549. reconcile/utils/membershipsources/resolver.py +110 -0
  550. reconcile/utils/merge_request_manager/__init__.py +0 -0
  551. reconcile/utils/merge_request_manager/merge_request_manager.py +99 -0
  552. reconcile/utils/merge_request_manager/parser.py +67 -0
  553. reconcile/utils/metrics.py +511 -1
  554. reconcile/utils/models.py +123 -0
  555. reconcile/utils/mr/README.md +198 -0
  556. reconcile/utils/mr/__init__.py +14 -10
  557. reconcile/utils/mr/app_interface_reporter.py +2 -2
  558. reconcile/utils/mr/aws_access.py +4 -4
  559. reconcile/utils/mr/base.py +51 -31
  560. reconcile/utils/mr/clusters_updates.py +10 -7
  561. reconcile/utils/mr/glitchtip_access_reporter.py +2 -4
  562. reconcile/utils/mr/labels.py +14 -1
  563. reconcile/utils/mr/notificator.py +1 -3
  564. reconcile/utils/mr/ocm_update_recommended_version.py +1 -2
  565. reconcile/utils/mr/ocm_upgrade_scheduler_org_updates.py +7 -3
  566. reconcile/utils/mr/promote_qontract.py +203 -0
  567. reconcile/utils/mr/user_maintenance.py +24 -4
  568. reconcile/utils/oauth2_backend_application_session.py +132 -0
  569. reconcile/utils/oc.py +194 -170
  570. reconcile/utils/oc_connection_parameters.py +40 -51
  571. reconcile/utils/oc_filters.py +11 -13
  572. reconcile/utils/oc_map.py +14 -35
  573. reconcile/utils/ocm/__init__.py +30 -1
  574. reconcile/utils/ocm/addons.py +228 -0
  575. reconcile/utils/ocm/base.py +618 -5
  576. reconcile/utils/ocm/cluster_groups.py +5 -56
  577. reconcile/utils/ocm/clusters.py +111 -99
  578. reconcile/utils/ocm/identity_providers.py +66 -0
  579. reconcile/utils/ocm/label_sources.py +75 -0
  580. reconcile/utils/ocm/labels.py +139 -54
  581. reconcile/utils/ocm/manifests.py +39 -0
  582. reconcile/utils/ocm/ocm.py +182 -928
  583. reconcile/utils/ocm/products.py +758 -0
  584. reconcile/utils/ocm/search_filters.py +20 -28
  585. reconcile/utils/ocm/service_log.py +32 -79
  586. reconcile/utils/ocm/sre_capability_labels.py +51 -0
  587. reconcile/utils/ocm/status_board.py +66 -0
  588. reconcile/utils/ocm/subscriptions.py +49 -59
  589. reconcile/utils/ocm/syncsets.py +39 -0
  590. reconcile/utils/ocm/upgrades.py +181 -0
  591. reconcile/utils/ocm_base_client.py +71 -36
  592. reconcile/utils/openshift_resource.py +113 -67
  593. reconcile/utils/output.py +18 -11
  594. reconcile/utils/pagerduty_api.py +16 -10
  595. reconcile/utils/parse_dhms_duration.py +13 -1
  596. reconcile/utils/prometheus.py +123 -0
  597. reconcile/utils/promotion_state.py +56 -19
  598. reconcile/utils/promtool.py +5 -8
  599. reconcile/utils/quay_api.py +13 -25
  600. reconcile/utils/raw_github_api.py +3 -5
  601. reconcile/utils/repo_owners.py +2 -8
  602. reconcile/utils/rest_api_base.py +126 -0
  603. reconcile/utils/rosa/__init__.py +0 -0
  604. reconcile/utils/rosa/rosa_cli.py +310 -0
  605. reconcile/utils/rosa/session.py +201 -0
  606. reconcile/utils/ruamel.py +16 -0
  607. reconcile/utils/runtime/__init__.py +0 -1
  608. reconcile/utils/runtime/desired_state_diff.py +9 -20
  609. reconcile/utils/runtime/environment.py +33 -8
  610. reconcile/utils/runtime/integration.py +28 -12
  611. reconcile/utils/runtime/meta.py +1 -3
  612. reconcile/utils/runtime/runner.py +8 -11
  613. reconcile/utils/runtime/sharding.py +93 -36
  614. reconcile/utils/saasherder/__init__.py +1 -1
  615. reconcile/utils/saasherder/interfaces.py +143 -138
  616. reconcile/utils/saasherder/models.py +201 -43
  617. reconcile/utils/saasherder/saasherder.py +508 -378
  618. reconcile/utils/secret_reader.py +22 -27
  619. reconcile/utils/semver_helper.py +15 -1
  620. reconcile/utils/slack_api.py +124 -36
  621. reconcile/utils/smtp_client.py +1 -2
  622. reconcile/utils/sqs_gateway.py +10 -6
  623. reconcile/utils/state.py +276 -127
  624. reconcile/utils/terraform/config_client.py +6 -7
  625. reconcile/utils/terraform_client.py +284 -125
  626. reconcile/utils/terrascript/cloudflare_client.py +38 -17
  627. reconcile/utils/terrascript/cloudflare_resources.py +67 -18
  628. reconcile/utils/terrascript/models.py +2 -3
  629. reconcile/utils/terrascript/resources.py +1 -2
  630. reconcile/utils/terrascript_aws_client.py +1292 -540
  631. reconcile/utils/three_way_diff_strategy.py +157 -0
  632. reconcile/utils/unleash/__init__.py +11 -0
  633. reconcile/utils/{unleash.py → unleash/client.py} +35 -29
  634. reconcile/utils/unleash/server.py +145 -0
  635. reconcile/utils/vault.py +42 -32
  636. reconcile/utils/vaultsecretref.py +2 -4
  637. reconcile/utils/vcs.py +250 -0
  638. reconcile/vault_replication.py +38 -31
  639. reconcile/vpc_peerings_validator.py +82 -13
  640. tools/app_interface_metrics_exporter.py +70 -0
  641. tools/app_interface_reporter.py +44 -157
  642. tools/cli_commands/container_images_report.py +154 -0
  643. tools/cli_commands/cost_report/__init__.py +0 -0
  644. tools/cli_commands/cost_report/aws.py +137 -0
  645. tools/cli_commands/cost_report/cost_management_api.py +155 -0
  646. tools/cli_commands/cost_report/model.py +49 -0
  647. tools/cli_commands/cost_report/openshift.py +166 -0
  648. tools/cli_commands/cost_report/openshift_cost_optimization.py +187 -0
  649. tools/cli_commands/cost_report/response.py +124 -0
  650. tools/cli_commands/cost_report/util.py +72 -0
  651. tools/cli_commands/cost_report/view.py +524 -0
  652. tools/cli_commands/erv2.py +620 -0
  653. tools/cli_commands/gpg_encrypt.py +5 -8
  654. tools/cli_commands/systems_and_tools.py +489 -0
  655. tools/glitchtip_access_revalidation.py +1 -1
  656. tools/qontract_cli.py +2301 -673
  657. tools/saas_metrics_exporter/__init__.py +0 -0
  658. tools/saas_metrics_exporter/commit_distance/__init__.py +0 -0
  659. tools/saas_metrics_exporter/commit_distance/channel.py +63 -0
  660. tools/saas_metrics_exporter/commit_distance/commit_distance.py +103 -0
  661. tools/saas_metrics_exporter/commit_distance/metrics.py +19 -0
  662. tools/saas_metrics_exporter/main.py +99 -0
  663. tools/saas_promotion_state/__init__.py +0 -0
  664. tools/saas_promotion_state/saas_promotion_state.py +105 -0
  665. tools/sd_app_sre_alert_report.py +145 -0
  666. tools/template_validation.py +107 -0
  667. e2e_tests/cli.py +0 -83
  668. e2e_tests/create_namespace.py +0 -43
  669. e2e_tests/dedicated_admin_rolebindings.py +0 -44
  670. e2e_tests/dedicated_admin_test_base.py +0 -39
  671. e2e_tests/default_network_policies.py +0 -47
  672. e2e_tests/default_project_labels.py +0 -52
  673. e2e_tests/network_policy_test_base.py +0 -17
  674. e2e_tests/test_base.py +0 -56
  675. qontract_reconcile-0.9.1rc298.dist-info/METADATA +0 -63
  676. qontract_reconcile-0.9.1rc298.dist-info/RECORD +0 -585
  677. qontract_reconcile-0.9.1rc298.dist-info/top_level.txt +0 -4
  678. reconcile/ecr_mirror.py +0 -152
  679. reconcile/github_scanner.py +0 -74
  680. reconcile/gitlab_integrations.py +0 -63
  681. reconcile/gql_definitions/ocm_oidc_idp/clusters.py +0 -195
  682. reconcile/gql_definitions/ocp_release_mirror/ocp_release_mirror.py +0 -287
  683. reconcile/integrations_validator.py +0 -18
  684. reconcile/jenkins_plugins.py +0 -129
  685. reconcile/kafka_clusters.py +0 -208
  686. reconcile/ocm_cluster_admin.py +0 -42
  687. reconcile/ocm_oidc_idp.py +0 -198
  688. reconcile/ocp_release_mirror.py +0 -373
  689. reconcile/prometheus_rules_tester_old.py +0 -436
  690. reconcile/saas_auto_promotions_manager/merge_request_manager/merge_request_manager.py +0 -279
  691. reconcile/saas_auto_promotions_manager/utils/vcs.py +0 -141
  692. reconcile/sentry_config.py +0 -613
  693. reconcile/sentry_helper.py +0 -69
  694. reconcile/test/conftest.py +0 -187
  695. reconcile/test/fixtures.py +0 -24
  696. reconcile/test/saas_auto_promotions_manager/conftest.py +0 -69
  697. reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/conftest.py +0 -110
  698. reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/data_keys.py +0 -10
  699. reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/test_housekeeping.py +0 -200
  700. reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/test_merge_request_manager.py +0 -151
  701. reconcile/test/saas_auto_promotions_manager/merge_request_manager/renderer/conftest.py +0 -63
  702. reconcile/test/saas_auto_promotions_manager/merge_request_manager/renderer/data_keys.py +0 -4
  703. reconcile/test/saas_auto_promotions_manager/merge_request_manager/renderer/test_content_multiple_namespaces.py +0 -46
  704. reconcile/test/saas_auto_promotions_manager/merge_request_manager/renderer/test_content_single_namespace.py +0 -94
  705. reconcile/test/saas_auto_promotions_manager/merge_request_manager/renderer/test_content_single_target.py +0 -44
  706. reconcile/test/saas_auto_promotions_manager/subscriber/conftest.py +0 -74
  707. reconcile/test/saas_auto_promotions_manager/subscriber/data_keys.py +0 -11
  708. reconcile/test/saas_auto_promotions_manager/subscriber/test_content_hash.py +0 -155
  709. reconcile/test/saas_auto_promotions_manager/subscriber/test_diff.py +0 -173
  710. reconcile/test/saas_auto_promotions_manager/subscriber/test_multiple_channels_config_hash.py +0 -226
  711. reconcile/test/saas_auto_promotions_manager/subscriber/test_multiple_channels_moving_ref.py +0 -224
  712. reconcile/test/saas_auto_promotions_manager/subscriber/test_single_channel_with_single_publisher.py +0 -350
  713. reconcile/test/saas_auto_promotions_manager/test_integration_test.py +0 -129
  714. reconcile/test/saas_auto_promotions_manager/utils/saas_files_inventory/test_multiple_publishers_for_single_channel.py +0 -70
  715. reconcile/test/saas_auto_promotions_manager/utils/saas_files_inventory/test_saas_files_use_target_config_hash.py +0 -63
  716. reconcile/test/saas_auto_promotions_manager/utils/saas_files_inventory/test_saas_files_with_auto_promote.py +0 -74
  717. reconcile/test/saas_auto_promotions_manager/utils/saas_files_inventory/test_saas_files_without_auto_promote.py +0 -65
  718. reconcile/test/test_aggregated_list.py +0 -237
  719. reconcile/test/test_amtool.py +0 -37
  720. reconcile/test/test_auto_promoter.py +0 -295
  721. reconcile/test/test_aws_ami_share.py +0 -68
  722. reconcile/test/test_aws_iam_keys.py +0 -70
  723. reconcile/test/test_aws_iam_password_reset.py +0 -35
  724. reconcile/test/test_aws_support_cases_sos.py +0 -23
  725. reconcile/test/test_checkpoint.py +0 -178
  726. reconcile/test/test_cli.py +0 -41
  727. reconcile/test/test_closedbox_endpoint_monitoring.py +0 -207
  728. reconcile/test/test_gabi_authorized_users.py +0 -72
  729. reconcile/test/test_github_org.py +0 -154
  730. reconcile/test/test_github_repo_invites.py +0 -123
  731. reconcile/test/test_gitlab_housekeeping.py +0 -88
  732. reconcile/test/test_gitlab_labeler.py +0 -129
  733. reconcile/test/test_gitlab_members.py +0 -283
  734. reconcile/test/test_instrumented_wrappers.py +0 -18
  735. reconcile/test/test_integrations_manager.py +0 -995
  736. reconcile/test/test_jenkins_worker_fleets.py +0 -55
  737. reconcile/test/test_jump_host.py +0 -117
  738. reconcile/test/test_ldap_users.py +0 -123
  739. reconcile/test/test_make.py +0 -28
  740. reconcile/test/test_ocm_additional_routers.py +0 -134
  741. reconcile/test/test_ocm_addons_upgrade_scheduler_org.py +0 -149
  742. reconcile/test/test_ocm_clusters.py +0 -598
  743. reconcile/test/test_ocm_clusters_manifest_updates.py +0 -89
  744. reconcile/test/test_ocm_oidc_idp.py +0 -315
  745. reconcile/test/test_ocm_update_recommended_version.py +0 -145
  746. reconcile/test/test_ocm_upgrade_scheduler.py +0 -614
  747. reconcile/test/test_ocm_upgrade_scheduler_org_updater.py +0 -129
  748. reconcile/test/test_openshift_base.py +0 -730
  749. reconcile/test/test_openshift_namespace_labels.py +0 -345
  750. reconcile/test/test_openshift_namespaces.py +0 -256
  751. reconcile/test/test_openshift_resource.py +0 -415
  752. reconcile/test/test_openshift_resources_base.py +0 -440
  753. reconcile/test/test_openshift_saas_deploy_change_tester.py +0 -310
  754. reconcile/test/test_openshift_tekton_resources.py +0 -253
  755. reconcile/test/test_openshift_upgrade_watcher.py +0 -146
  756. reconcile/test/test_prometheus_rules_tester.py +0 -151
  757. reconcile/test/test_prometheus_rules_tester_old.py +0 -77
  758. reconcile/test/test_quay_membership.py +0 -86
  759. reconcile/test/test_quay_mirror.py +0 -109
  760. reconcile/test/test_quay_mirror_org.py +0 -70
  761. reconcile/test/test_quay_repos.py +0 -59
  762. reconcile/test/test_queries.py +0 -53
  763. reconcile/test/test_repo_owners.py +0 -47
  764. reconcile/test/test_requests_sender.py +0 -139
  765. reconcile/test/test_saasherder.py +0 -1074
  766. reconcile/test/test_saasherder_allowed_secret_paths.py +0 -127
  767. reconcile/test/test_secret_reader.py +0 -153
  768. reconcile/test/test_slack_base.py +0 -185
  769. reconcile/test/test_slack_usergroups.py +0 -744
  770. reconcile/test/test_sql_query.py +0 -19
  771. reconcile/test/test_terraform_cloudflare_dns.py +0 -117
  772. reconcile/test/test_terraform_cloudflare_resources.py +0 -106
  773. reconcile/test/test_terraform_cloudflare_users.py +0 -749
  774. reconcile/test/test_terraform_resources.py +0 -257
  775. reconcile/test/test_terraform_tgw_attachments.py +0 -631
  776. reconcile/test/test_terraform_users.py +0 -57
  777. reconcile/test/test_terraform_vpc_peerings.py +0 -499
  778. reconcile/test/test_terraform_vpc_peerings_build_desired_state.py +0 -1061
  779. reconcile/test/test_unleash.py +0 -138
  780. reconcile/test/test_utils_aws_api.py +0 -240
  781. reconcile/test/test_utils_aws_helper.py +0 -80
  782. reconcile/test/test_utils_cluster_version_data.py +0 -177
  783. reconcile/test/test_utils_data_structures.py +0 -13
  784. reconcile/test/test_utils_disabled_integrations.py +0 -86
  785. reconcile/test/test_utils_expiration.py +0 -109
  786. reconcile/test/test_utils_external_resource_spec.py +0 -383
  787. reconcile/test/test_utils_external_resources.py +0 -247
  788. reconcile/test/test_utils_github_api.py +0 -73
  789. reconcile/test/test_utils_gitlab_api.py +0 -20
  790. reconcile/test/test_utils_gpg.py +0 -69
  791. reconcile/test/test_utils_gql.py +0 -81
  792. reconcile/test/test_utils_helm.py +0 -306
  793. reconcile/test/test_utils_helpers.py +0 -55
  794. reconcile/test/test_utils_imap_client.py +0 -65
  795. reconcile/test/test_utils_jjb_client.py +0 -52
  796. reconcile/test/test_utils_jsonpath.py +0 -286
  797. reconcile/test/test_utils_ldap_client.py +0 -51
  798. reconcile/test/test_utils_mr.py +0 -226
  799. reconcile/test/test_utils_mr_clusters_updates.py +0 -77
  800. reconcile/test/test_utils_oc.py +0 -984
  801. reconcile/test/test_utils_ocm.py +0 -110
  802. reconcile/test/test_utils_pagerduty_api.py +0 -251
  803. reconcile/test/test_utils_parse_dhms_duration.py +0 -34
  804. reconcile/test/test_utils_password_validator.py +0 -155
  805. reconcile/test/test_utils_quay_api.py +0 -86
  806. reconcile/test/test_utils_semver_helper.py +0 -19
  807. reconcile/test/test_utils_sharding.py +0 -56
  808. reconcile/test/test_utils_slack_api.py +0 -439
  809. reconcile/test/test_utils_smtp_client.py +0 -73
  810. reconcile/test/test_utils_state.py +0 -256
  811. reconcile/test/test_utils_terraform.py +0 -13
  812. reconcile/test/test_utils_terraform_client.py +0 -585
  813. reconcile/test/test_utils_terraform_config_client.py +0 -219
  814. reconcile/test/test_utils_terrascript_aws_client.py +0 -277
  815. reconcile/test/test_utils_terrascript_cloudflare_client.py +0 -597
  816. reconcile/test/test_utils_terrascript_cloudflare_resources.py +0 -26
  817. reconcile/test/test_vault_replication.py +0 -515
  818. reconcile/test/test_vault_utils.py +0 -47
  819. reconcile/test/test_version_bump.py +0 -18
  820. reconcile/test/test_vpc_peerings_validator.py +0 -103
  821. reconcile/test/test_wrong_region.py +0 -78
  822. reconcile/typed_queries/glitchtip_settings.py +0 -18
  823. reconcile/typed_queries/ocp_release_mirror.py +0 -11
  824. reconcile/unleash_watcher.py +0 -120
  825. reconcile/utils/git_secrets.py +0 -63
  826. reconcile/utils/mr/auto_promoter.py +0 -218
  827. reconcile/utils/sentry_client.py +0 -383
  828. release/test_version.py +0 -50
  829. release/version.py +0 -100
  830. tools/test/test_qontract_cli.py +0 -60
  831. tools/test/test_sre_checkpoints.py +0 -79
  832. /e2e_tests/__init__.py → /reconcile/aus/upgrades.py +0 -0
  833. /reconcile/{gql_definitions/ocp_release_mirror → aws_account_manager}/__init__.py +0 -0
  834. /reconcile/{test → aws_ami_cleanup}/__init__.py +0 -0
  835. /reconcile/{test/saas_auto_promotions_manager → aws_cloudwatch_log_retention}/__init__.py +0 -0
  836. /reconcile/{test/saas_auto_promotions_manager/merge_request_manager → aws_saml_idp}/__init__.py +0 -0
  837. /reconcile/{test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager → aws_saml_roles}/__init__.py +0 -0
  838. /reconcile/{test/saas_auto_promotions_manager/merge_request_manager/renderer → aws_version_sync}/__init__.py +0 -0
  839. /reconcile/{test/saas_auto_promotions_manager/subscriber → aws_version_sync/merge_request_manager}/__init__.py +0 -0
  840. /reconcile/{test/saas_auto_promotions_manager/utils → cluster_auth_rhidp}/__init__.py +0 -0
  841. /reconcile/{test/saas_auto_promotions_manager/utils/saas_files_inventory → dynatrace_token_provider}/__init__.py +0 -0
  842. {release → reconcile/endpoints_discovery}/__init__.py +0 -0
  843. {tools/test → reconcile/external_resources}/__init__.py +0 -0
@@ -3,37 +3,38 @@ import hashlib
3
3
  import itertools
4
4
  import json
5
5
  import logging
6
+ import re
6
7
  import sys
8
+ from collections import defaultdict
7
9
  from collections.abc import (
10
+ Callable,
11
+ Generator,
8
12
  Iterable,
9
13
  Mapping,
14
+ MutableMapping,
15
+ Sequence,
10
16
  )
11
17
  from contextlib import contextmanager
12
18
  from dataclasses import dataclass
13
- from functools import cache
14
19
  from textwrap import indent
15
20
  from threading import Lock
16
21
  from typing import (
17
22
  Any,
18
- Optional,
19
23
  Protocol,
20
- Tuple,
21
24
  )
22
- from urllib import parse
25
+ from unittest.mock import DEFAULT, patch
23
26
 
24
27
  import anymarkup
25
- import jinja2
26
- import jinja2.sandbox
28
+ from deepdiff import DeepHash
27
29
  from sretoolbox.utils import (
28
- retry,
29
30
  threaded,
30
31
  )
31
32
 
32
33
  import reconcile.openshift_base as ob
34
+ import reconcile.utils.jinja2.utils as jinja2_utils
33
35
  from reconcile import queries
34
36
  from reconcile.change_owners.diff import IDENTIFIER_FIELD_NAME
35
- from reconcile.checkpoint import url_makes_sense
36
- from reconcile.github_users import init_github
37
+ from reconcile.external_resources.meta import SECRET_UPDATED_AT
37
38
  from reconcile.utils import (
38
39
  amtool,
39
40
  gql,
@@ -41,9 +42,10 @@ from reconcile.utils import (
41
42
  )
42
43
  from reconcile.utils.defer import defer
43
44
  from reconcile.utils.exceptions import FetchResourceError
44
- from reconcile.utils.jinja2_ext import (
45
- B64EncodeExtension,
46
- RaiseErrorExtension,
45
+ from reconcile.utils.jinja2.utils import (
46
+ FetchSecretError,
47
+ process_extracurlyjinja2_template,
48
+ process_jinja2_template,
47
49
  )
48
50
  from reconcile.utils.oc import (
49
51
  OC_Map,
@@ -51,12 +53,14 @@ from reconcile.utils.oc import (
51
53
  OCLogMsg,
52
54
  StatusCodeError,
53
55
  )
54
- from reconcile.utils.openshift_resource import ConstructResourceError
55
- from reconcile.utils.openshift_resource import OpenshiftResource as OR
56
56
  from reconcile.utils.openshift_resource import (
57
+ ConstructResourceError,
57
58
  ResourceInventory,
58
59
  ResourceKeyExistsError,
60
+ ResourceNotManagedError,
61
+ base64_encode_secret_field_value,
59
62
  )
63
+ from reconcile.utils.openshift_resource import OpenshiftResource as OR
60
64
  from reconcile.utils.runtime.integration import DesiredStateShardConfig
61
65
  from reconcile.utils.secret_reader import SecretReader
62
66
  from reconcile.utils.semver_helper import make_semver
@@ -142,6 +146,8 @@ NAMESPACES_QUERY = """
142
146
  {
143
147
  namespaces: namespaces_v1 {
144
148
  name
149
+ path
150
+ labels
145
151
  delete
146
152
  clusterAdmin
147
153
  managedResourceTypes
@@ -161,8 +167,13 @@ NAMESPACES_QUERY = """
161
167
  openshiftResources {
162
168
  %s
163
169
  }
170
+ environment {
171
+ name
172
+ parameters
173
+ }
164
174
  cluster {
165
175
  name
176
+ labels
166
177
  serverUrl
167
178
  auth {
168
179
  service
@@ -181,8 +192,14 @@ NAMESPACES_QUERY = """
181
192
  %s
182
193
  }
183
194
  spec {
195
+ ... on ClusterSpecROSA_v1 {
196
+ account {
197
+ uid
198
+ }
199
+ }
184
200
  version
185
201
  region
202
+ hypershift
186
203
  }
187
204
  network {
188
205
  pod
@@ -215,203 +232,58 @@ NAMESPACES_QUERY = """
215
232
  QONTRACT_INTEGRATION = "openshift_resources_base"
216
233
  QONTRACT_INTEGRATION_VERSION = make_semver(1, 9, 2)
217
234
  QONTRACT_BASE64_SUFFIX = "_qb64"
218
- APP_INT_BASE_URL = "https://gitlab.cee.redhat.com/service/app-interface"
235
+ KUBERNETES_SECRET_DATA_KEY_RE = "^[-._a-zA-Z0-9]+$"
219
236
 
237
+ # Keys in vault secrets that do not need to land
238
+ # into K8S secrets.
239
+ VAULT_SECRETS_EXCLUDED_KEYS = {SECRET_UPDATED_AT}
220
240
  _log_lock = Lock()
221
241
 
222
242
 
223
- def _locked_info_log(msg: str):
243
+ def _locked_info_log(msg: str) -> None:
224
244
  with _log_lock:
225
245
  logging.info(msg)
226
246
 
227
247
 
228
- def _locked_debug_log(msg: str):
248
+ def _locked_debug_log(msg: str) -> None:
229
249
  with _log_lock:
230
250
  logging.debug(msg)
231
251
 
232
252
 
233
- def _locked_error_log(msg: str):
253
+ def _locked_error_log(msg: str) -> None:
234
254
  with _log_lock:
235
255
  logging.error(msg)
236
256
 
237
257
 
238
- class FetchSecretError(Exception):
239
- def __init__(self, msg):
240
- super().__init__("error fetching secret: " + str(msg))
241
-
242
-
243
258
  class FetchRouteError(Exception):
244
- def __init__(self, msg):
259
+ def __init__(self, msg: Any):
245
260
  super().__init__("error fetching route: " + str(msg))
246
261
 
247
262
 
248
- class Jinja2TemplateError(Exception):
249
- def __init__(self, msg):
250
- super().__init__("error processing jinja2 template: " + str(msg))
263
+ class ResourceTemplateRenderError(Exception):
264
+ pass
251
265
 
252
266
 
253
- class ResourceTemplateRenderError(Exception):
267
+ class SecretKeyFormatError(Exception):
254
268
  pass
255
269
 
256
270
 
257
271
  class UnknownProviderError(Exception):
258
- def __init__(self, msg):
272
+ def __init__(self, msg: Any):
259
273
  super().__init__("unknown provider error: " + str(msg))
260
274
 
261
275
 
262
276
  class UnknownTemplateTypeError(Exception):
263
- def __init__(self, msg):
277
+ def __init__(self, msg: Any):
264
278
  super().__init__("unknown template type error: " + str(msg))
265
279
 
266
280
 
267
- @retry()
268
- def lookup_secret(path, key, version=None, tvars=None, settings=None):
269
- if tvars is not None:
270
- path = process_jinja2_template(body=path, vars=tvars, settings=settings)
271
- key = process_jinja2_template(body=key, vars=tvars, settings=settings)
272
- if version and not isinstance(version, int):
273
- version = process_jinja2_template(
274
- body=version, vars=tvars, settings=settings
275
- )
276
- secret = {"path": path, "field": key, "version": version}
277
- try:
278
- secret_reader = SecretReader(settings)
279
- return secret_reader.read(secret)
280
- except Exception as e:
281
- raise FetchSecretError(e)
282
-
283
-
284
- def lookup_github_file_content(repo, path, ref, tvars=None, settings=None):
285
- if tvars is not None:
286
- repo = process_jinja2_template(body=repo, vars=tvars, settings=settings)
287
- path = process_jinja2_template(body=path, vars=tvars, settings=settings)
288
- ref = process_jinja2_template(body=ref, vars=tvars, settings=settings)
289
-
290
- gh = init_github()
291
- c = gh.get_repo(repo).get_contents(path, ref).decoded_content
292
- return c.decode("utf-8")
293
-
294
-
295
- def lookup_graphql_query_results(query: str, **kwargs) -> list[Any]:
296
- gqlapi = gql.get_api()
297
- resource = gqlapi.get_resource(query)["content"]
298
- rendered_resource = jinja2.Template(resource).render(**kwargs)
299
- results = list(gqlapi.query(rendered_resource).values())[0]
300
- return results
301
-
302
-
303
- def json_to_dict(input):
304
- """Jinja2 filter to parse JSON strings into dictionaries.
305
- This becomes useful to access Graphql queries data (labels)
306
- :param input: json string
307
- :return: dict with the parsed inputs contents
308
- """
309
- data = json.loads(input)
310
- return data
311
-
312
-
313
- def urlescape(
314
- string: str,
315
- safe: str = "/",
316
- encoding: Optional[str] = None,
317
- ) -> str:
318
- """Jinja2 filter that is a simple wrapper around urllib's URL quoting
319
- functions that takes a string value and makes it safe for use as URL
320
- components escaping any reserved characters using URL encoding. See:
321
- urllib.parse.quote() and urllib.parse.quote_plus() for reference.
322
-
323
- :param str string: String value to escape.
324
- :param str safe: Optional characters that should not be escaped.
325
- :param encoding: Encoding to apply to the string to be escaped. Defaults
326
- to UTF-8. Unsupported characters raise a UnicodeEncodeError error.
327
- :type encoding: typing.Optional[str]
328
- :returns: A string with reserved characters escaped.
329
- :rtype: str
330
- """
331
- return parse.quote(string, safe=safe, encoding=encoding)
332
-
333
-
334
- def urlunescape(string: str, encoding: Optional[str] = None) -> str:
335
- """Jinja2 filter that is a simple wrapper around urllib's URL unquoting
336
- functions that takes an URL-encoded string value and unescapes it
337
- replacing any URL-encoded values with their character equivalent. See:
338
- urllib.parse.unquote() and urllib.parse.unquote_plus() for reference.
339
-
340
- :param str string: String value to unescape.
341
- :param encoding: Encoding to apply to the string to be unescaped. Defaults
342
- to UTF-8. Unsupported characters are replaced by placeholder values.
343
- :type encoding: typing.Optional[str]
344
- :returns: A string with URL-encoded sequences unescaped.
345
- :rtype: str
346
- """
347
- if encoding is None:
348
- encoding = "utf-8"
349
- return parse.unquote(string, encoding=encoding)
350
-
351
-
352
- @cache
353
- def compile_jinja2_template(body, extra_curly: bool = False):
354
- env: dict = {}
355
- if extra_curly:
356
- env = {
357
- "block_start_string": "{{%",
358
- "block_end_string": "%}}",
359
- "variable_start_string": "{{{",
360
- "variable_end_string": "}}}",
361
- "comment_start_string": "{{#",
362
- "comment_end_string": "#}}",
363
- }
364
-
365
- jinja_env = jinja2.sandbox.SandboxedEnvironment(
366
- extensions=[B64EncodeExtension, RaiseErrorExtension],
367
- undefined=jinja2.StrictUndefined,
368
- **env,
369
- )
370
- jinja_env.filters.update(
371
- {
372
- "json_to_dict": json_to_dict,
373
- "urlescape": urlescape,
374
- "urlunescape": urlunescape,
375
- }
376
- )
377
-
378
- return jinja_env.from_string(body)
379
-
380
-
381
- def process_jinja2_template(body, vars=None, extra_curly: bool = False, settings=None):
382
- if vars is None:
383
- vars = {}
384
- vars.update(
385
- {
386
- "vault": lambda p, k, v=None: lookup_secret(
387
- path=p, key=k, version=v, tvars=vars, settings=settings
388
- ),
389
- "github": lambda u, p, r, v=None: lookup_github_file_content(
390
- repo=u, path=p, ref=r, tvars=vars, settings=settings
391
- ),
392
- "urlescape": lambda u, s="/", e=None: urlescape(
393
- string=u, safe=s, encoding=e
394
- ),
395
- "urlunescape": lambda u, e=None: urlunescape(string=u, encoding=e),
396
- "query": lookup_graphql_query_results,
397
- "url": url_makes_sense,
398
- }
399
- )
400
- try:
401
- template = compile_jinja2_template(body, extra_curly)
402
- r = template.render(vars)
403
- except Exception as e:
404
- raise Jinja2TemplateError(e)
405
- return r
406
-
407
-
408
- def process_extracurlyjinja2_template(body, vars=None, env=None, settings=None):
409
- if vars is None:
410
- vars = {}
411
- return process_jinja2_template(body, vars=vars, extra_curly=True, settings=settings)
412
-
413
-
414
- def check_alertmanager_config(data, path, alertmanager_config_key, decode_base64=False):
281
+ def check_alertmanager_config(
282
+ data: Mapping[str, Any],
283
+ path: str,
284
+ alertmanager_config_key: str,
285
+ decode_base64: bool = False,
286
+ ) -> None:
415
287
  try:
416
288
  config = data[alertmanager_config_key]
417
289
  except KeyError:
@@ -419,7 +291,7 @@ def check_alertmanager_config(data, path, alertmanager_config_key, decode_base64
419
291
  f"error validating alertmanager config in {path}: "
420
292
  f"missing key {alertmanager_config_key}"
421
293
  )
422
- raise FetchResourceError(e_msg)
294
+ raise FetchResourceError(e_msg) from None
423
295
 
424
296
  if decode_base64:
425
297
  config = base64.b64decode(config).decode("utf-8")
@@ -431,15 +303,15 @@ def check_alertmanager_config(data, path, alertmanager_config_key, decode_base64
431
303
 
432
304
 
433
305
  def fetch_provider_resource(
434
- resource: dict,
435
- tfunc=None,
436
- tvars=None,
437
- validate_json=False,
438
- validate_alertmanager_config=False,
439
- alertmanager_config_key="alertmanager.yaml",
440
- add_path_to_prom_rules=True,
441
- skip_validation=False,
442
- settings=None,
306
+ resource: Mapping,
307
+ tfunc: Callable | None = None,
308
+ tvars: Mapping[str, Any] | None = None,
309
+ validate_json: bool = False,
310
+ validate_alertmanager_config: bool = False,
311
+ alertmanager_config_key: str = "alertmanager.yaml",
312
+ add_path_to_prom_rules: bool = True,
313
+ skip_validation: bool = False,
314
+ settings: Mapping[str, Any] | None = None,
443
315
  ) -> OR:
444
316
  path = resource["path"]
445
317
  content = resource["content"]
@@ -461,10 +333,10 @@ def fetch_provider_resource(
461
333
  except TypeError:
462
334
  body_type = type(body).__name__
463
335
  e_msg = f"invalid resource type {body_type} found in path: {path}"
464
- raise FetchResourceError(e_msg)
336
+ raise FetchResourceError(e_msg) from None
465
337
  except anymarkup.AnyMarkupError:
466
338
  e_msg = f"Could not parse data. Skipping resource: {path}"
467
- raise FetchResourceError(e_msg)
339
+ raise FetchResourceError(e_msg) from None
468
340
 
469
341
  if validate_json:
470
342
  files = body["data"]
@@ -473,7 +345,7 @@ def fetch_provider_resource(
473
345
  json.loads(file_content)
474
346
  except ValueError:
475
347
  e_msg = f"invalid json in {path} under {file_name}"
476
- raise FetchResourceError(e_msg)
348
+ raise FetchResourceError(e_msg) from None
477
349
 
478
350
  if validate_alertmanager_config:
479
351
  if body["kind"] == "Secret":
@@ -491,6 +363,9 @@ def fetch_provider_resource(
491
363
 
492
364
  if add_path_to_prom_rules:
493
365
  if body["kind"] == "PrometheusRule":
366
+ app_int_base_url = "https://gitlab.cee.redhat.com/service/app-interface"
367
+ if settings and "repoUrl" in settings:
368
+ app_int_base_url = settings["repoUrl"]
494
369
  try:
495
370
  groups = body["spec"]["groups"]
496
371
  for group in groups:
@@ -499,10 +374,9 @@ def fetch_provider_resource(
499
374
  annotations = rule.get("annotations")
500
375
  if not annotations:
501
376
  continue
502
- # TODO(mafriedm): make this better
503
- rule["annotations"][
504
- "html_url"
505
- ] = f"{APP_INT_BASE_URL}/blob/master/resources{path}"
377
+ rule["annotations"]["html_url"] = (
378
+ f"{app_int_base_url}/blob/master/resources{path}"
379
+ )
506
380
  except Exception:
507
381
  logging.warning(
508
382
  "could not add html_url annotation to" + body["metadata"]["name"]
@@ -513,31 +387,35 @@ def fetch_provider_resource(
513
387
  body, QONTRACT_INTEGRATION, QONTRACT_INTEGRATION_VERSION, error_details=path
514
388
  )
515
389
  except ConstructResourceError as e:
516
- raise FetchResourceError(str(e))
390
+ raise FetchResourceError(str(e)) from None
517
391
 
518
392
 
519
393
  def fetch_provider_vault_secret(
520
- path,
521
- version,
522
- name,
523
- labels,
524
- annotations,
525
- type,
526
- integration,
527
- integration_version,
528
- validate_alertmanager_config=False,
529
- alertmanager_config_key="alertmanager.yaml",
530
- settings=None,
394
+ path: str,
395
+ version: str,
396
+ name: str,
397
+ labels: Mapping[str, str] | None,
398
+ annotations: Mapping[str, str],
399
+ type: str,
400
+ integration: str,
401
+ integration_version: str,
402
+ validate_alertmanager_config: bool = False,
403
+ alertmanager_config_key: str = "alertmanager.yaml",
404
+ settings: Mapping[str, Any] | None = None,
531
405
  ) -> OR:
532
406
  # get the fields from vault
533
407
  secret_reader = SecretReader(settings)
534
- raw_data = secret_reader.read_all({"path": path, "version": version})
408
+ raw_data = {
409
+ k: v
410
+ for k, v in secret_reader.read_all({"path": path, "version": version}).items()
411
+ if k not in VAULT_SECRETS_EXCLUDED_KEYS
412
+ }
535
413
 
536
414
  if validate_alertmanager_config:
537
415
  check_alertmanager_config(raw_data, path, alertmanager_config_key)
538
416
 
539
417
  # construct oc resource
540
- body = {
418
+ body: dict[str, Any] = {
541
419
  "apiVersion": "v1",
542
420
  "kind": "Secret",
543
421
  "type": type,
@@ -546,24 +424,42 @@ def fetch_provider_vault_secret(
546
424
  if labels:
547
425
  body["metadata"]["labels"] = labels
548
426
 
427
+ assert_valid_secret_keys(raw_data)
428
+
549
429
  # populate data
550
430
  for k, v in raw_data.items():
551
- if v == "":
552
- continue
553
431
  if k.lower().endswith(QONTRACT_BASE64_SUFFIX):
554
432
  k = k[: -len(QONTRACT_BASE64_SUFFIX)]
555
433
  v = v.replace("\n", "")
556
- elif v is not None:
557
- v = base64.b64encode(v.encode()).decode("utf-8")
434
+ else:
435
+ v = base64_encode_secret_field_value(v)
558
436
  body.setdefault("data", {})[k] = v
559
437
 
560
438
  try:
561
439
  return OR(body, integration, integration_version, error_details=path)
562
440
  except ConstructResourceError as e:
563
- raise FetchResourceError(str(e))
441
+ raise FetchResourceError(str(e)) from None
442
+
443
+
444
+ # check to ensure that all of the keys are valid by looking to see if there are
445
+ # any white space issues. If any issues are uncovered, an exception will be
446
+ # raised.
447
+ # we're receiving the full key: value information, not simply a list of keys.
448
+ def assert_valid_secret_keys(secrets_data: dict[str, str]) -> None:
449
+ for k in secrets_data:
450
+ matches = re.search(KUBERNETES_SECRET_DATA_KEY_RE, k)
451
+ if not matches:
452
+ raise SecretKeyFormatError(
453
+ f"'{k}' is not valid key name for a Secret. a valid Secret key must consist of alphanumeric characters, '-', '_' or '.' (e.g. 'key.name', or 'KEY_NAME', or 'key-name', regex used for validation is '^[-._a-zA-Z0-9]+$')"
454
+ )
564
455
 
565
456
 
566
- def fetch_provider_route(resource: dict, tls_path, tls_version, settings=None) -> OR:
457
+ def fetch_provider_route(
458
+ resource: Mapping,
459
+ tls_path: str | None,
460
+ tls_version: str | None,
461
+ settings: Mapping | None = None,
462
+ ) -> OR:
567
463
  path = resource["path"]
568
464
  openshift_resource = fetch_provider_resource(resource)
569
465
 
@@ -589,9 +485,7 @@ def fetch_provider_route(resource: dict, tls_path, tls_version, settings=None) -
589
485
  tls[k] = v
590
486
  continue
591
487
 
592
- msg = "Route secret '{}' key '{}' not in valid keys {}".format(
593
- tls_path, k, valid_keys
594
- )
488
+ msg = f"Route secret '{tls_path}' key '{k}' not in valid keys {valid_keys}"
595
489
  _locked_info_log(msg)
596
490
 
597
491
  host = openshift_resource.body["spec"].get("host")
@@ -606,12 +500,15 @@ def fetch_provider_route(resource: dict, tls_path, tls_version, settings=None) -
606
500
 
607
501
 
608
502
  def fetch_openshift_resource(
609
- resource, parent, settings=None, skip_validation=False
503
+ resource: Mapping,
504
+ parent: Mapping[str, Any],
505
+ settings: Mapping | None = None,
506
+ skip_validation: bool = False,
610
507
  ) -> OR:
611
508
  provider = resource["provider"]
612
509
  if provider == "resource":
613
510
  path = resource["resource"]["path"]
614
- _locked_debug_log("Processing {}: {}".format(provider, path))
511
+ _locked_debug_log(f"Processing {provider}: {path}")
615
512
  validate_json = resource.get("validate_json") or False
616
513
  add_path_to_prom_rules = resource.get("add_path_to_prom_rules", True)
617
514
  validate_alertmanager_config = (
@@ -631,7 +528,7 @@ def fetch_openshift_resource(
631
528
  )
632
529
  elif provider == "resource-template":
633
530
  path = resource["resource"]["path"]
634
- _locked_debug_log("Processing {}: {}".format(provider, path))
531
+ _locked_debug_log(f"Processing {provider}: {path}")
635
532
  add_path_to_prom_rules = resource.get("add_path_to_prom_rules", True)
636
533
  validate_alertmanager_config = (
637
534
  resource.get("validate_alertmanager_config") or False
@@ -664,12 +561,12 @@ def fetch_openshift_resource(
664
561
  settings=settings,
665
562
  )
666
563
  except Exception as e:
667
- msg = "could not render template at path {}\n{}".format(path, e)
668
- raise ResourceTemplateRenderError(msg)
564
+ msg = f"could not render template at path {path}\n{e}"
565
+ raise ResourceTemplateRenderError(msg) from None
669
566
  elif provider == "vault-secret":
670
567
  path = resource["path"]
671
568
  version = resource["version"]
672
- _locked_debug_log("Processing {}: {} - {}".format(provider, path, version))
569
+ _locked_debug_log(f"Processing {provider}: {path} - {version}")
673
570
  rn = resource["name"]
674
571
  name = path.split("/")[-1] if rn is None else rn
675
572
  rl = resource["labels"]
@@ -699,10 +596,10 @@ def fetch_openshift_resource(
699
596
  settings=settings,
700
597
  )
701
598
  except (SecretVersionNotFound, SecretVersionIsNone) as e:
702
- raise FetchSecretError(e)
599
+ raise FetchSecretError(e) from None
703
600
  elif provider == "route":
704
601
  path = resource["resource"]["path"]
705
- _locked_debug_log("Processing {}: {}".format(provider, path))
602
+ _locked_debug_log(f"Processing {provider}: {path}")
706
603
  tls_path = resource["vault_tls_secret_path"]
707
604
  tls_version = resource["vault_tls_secret_version"]
708
605
  openshift_resource = fetch_provider_route(
@@ -710,7 +607,7 @@ def fetch_openshift_resource(
710
607
  )
711
608
  elif provider == "prometheus-rule":
712
609
  path = resource["resource"]["path"]
713
- _locked_debug_log("Processing {}: {}".format(provider, path))
610
+ _locked_debug_log(f"Processing {provider}: {path}")
714
611
  add_path_to_prom_rules = resource.get("add_path_to_prom_rules", True)
715
612
  tv = {}
716
613
  if resource["variables"]:
@@ -738,8 +635,8 @@ def fetch_openshift_resource(
738
635
  settings=settings,
739
636
  )
740
637
  except Exception as e:
741
- msg = "could not render template at path {}\n{}".format(path, e)
742
- raise ResourceTemplateRenderError(msg)
638
+ msg = f"could not render template at path {path}\n{e}"
639
+ raise ResourceTemplateRenderError(msg) from None
743
640
 
744
641
  else:
745
642
  raise UnknownProviderError(provider)
@@ -753,8 +650,8 @@ def fetch_current_state(
753
650
  cluster: str,
754
651
  namespace: str,
755
652
  kind: str,
756
- resource_names=Iterable[str],
757
- ):
653
+ resource_names: Iterable[str] | None,
654
+ ) -> None:
758
655
  _locked_debug_log(f"Fetching {kind} from {cluster}/{namespace}")
759
656
  if not oc.is_kind_supported(kind):
760
657
  logging.warning(f"[{cluster}] cluster has no API resource {kind}.")
@@ -780,8 +677,8 @@ def fetch_desired_state(
780
677
  resource: Mapping[str, Any],
781
678
  parent: Mapping[str, Any],
782
679
  privileged: bool,
783
- settings: Optional[Mapping[str, Any]] = None,
784
- ):
680
+ settings: Mapping[str, Any] | None = None,
681
+ ) -> None:
785
682
  try:
786
683
  openshift_resource = fetch_openshift_resource(resource, parent, settings)
787
684
  except (
@@ -791,7 +688,7 @@ def fetch_desired_state(
791
688
  UnknownProviderError,
792
689
  ) as e:
793
690
  ri.register_error()
794
- msg = "[{}/{}] {}".format(cluster, namespace, str(e))
691
+ msg = f"[{cluster}/{namespace}] {e!s}"
795
692
  _locked_error_log(msg)
796
693
  return
797
694
 
@@ -809,9 +706,7 @@ def fetch_desired_state(
809
706
  # combination was not initialized, meaning that it shouldn't be
810
707
  # managed. But someone is trying to add it via app-interface
811
708
  ri.register_error()
812
- msg = "[{}/{}] unknown kind: {}. hint: is it missing from managedResourceTypes?".format(
813
- cluster, namespace, openshift_resource.kind
814
- )
709
+ msg = f"[{cluster}/{namespace}] unknown kind: {openshift_resource.kind}. hint: is it missing from managedResourceTypes?"
815
710
  _locked_error_log(msg)
816
711
  return
817
712
  except ResourceKeyExistsError:
@@ -819,9 +714,14 @@ def fetch_desired_state(
819
714
  # a desired resource with the same name and
820
715
  # the same type was already added previously
821
716
  ri.register_error()
822
- msg = ("[{}/{}] desired item already exists: {}/{}.").format(
823
- cluster, namespace, openshift_resource.kind, openshift_resource.name
824
- )
717
+ msg = f"[{cluster}/{namespace}] desired item already exists: {openshift_resource.kind}/{openshift_resource.name}."
718
+ _locked_error_log(msg)
719
+ return
720
+ except ResourceNotManagedError:
721
+ # This is failing because the resource name is
722
+ # not in the list of resource names that are managed
723
+ ri.register_error()
724
+ msg = f"[{cluster}/{namespace}] desired item is not managed: {openshift_resource.kind}/{openshift_resource.name}."
825
725
  _locked_error_log(msg)
826
726
  return
827
727
 
@@ -829,7 +729,7 @@ def fetch_desired_state(
829
729
  def fetch_states(
830
730
  spec: ob.StateSpec,
831
731
  ri: ResourceInventory,
832
- settings: Optional[Mapping[str, Any]] = None,
732
+ settings: Mapping[str, Any] | None = None,
833
733
  ) -> None:
834
734
  try:
835
735
  if isinstance(spec, ob.CurrentStateSpec):
@@ -855,17 +755,17 @@ def fetch_states(
855
755
 
856
756
  except StatusCodeError as e:
857
757
  ri.register_error(cluster=spec.cluster)
858
- logging.error(f"{spec} - exception: {str(e)}")
758
+ logging.error(f"{spec} - exception: {e!s}")
859
759
 
860
760
 
861
761
  def fetch_data(
862
- namespaces,
863
- thread_pool_size,
864
- internal,
865
- use_jump_host,
866
- init_api_resources=False,
867
- overrides=None,
868
- ):
762
+ namespaces: Iterable[Mapping[str, Any]],
763
+ thread_pool_size: int,
764
+ internal: bool | None,
765
+ use_jump_host: bool,
766
+ init_api_resources: bool = False,
767
+ overrides: Iterable[str] | None = None,
768
+ ) -> tuple[OC_Map, ResourceInventory]:
869
769
  ri = ResourceInventory()
870
770
  settings = queries.get_app_interface_settings()
871
771
  logging.debug(f"Overriding keys {overrides}")
@@ -887,20 +787,29 @@ def fetch_data(
887
787
 
888
788
 
889
789
  def filter_namespaces_by_cluster_and_namespace(
890
- namespaces, cluster_name, namespace_name
891
- ):
892
- if cluster_name:
893
- namespaces = [n for n in namespaces if n["cluster"]["name"] == cluster_name]
790
+ namespaces: Sequence[dict[str, Any]],
791
+ cluster_names: Iterable[str] | None,
792
+ exclude_clusters: Iterable[str] | None,
793
+ namespace_name: str | None,
794
+ ) -> Sequence[dict[str, Any]]:
795
+ if cluster_names:
796
+ namespaces = [n for n in namespaces if n["cluster"]["name"] in cluster_names]
797
+ elif exclude_clusters:
798
+ namespaces = [
799
+ n for n in namespaces if n["cluster"]["name"] not in exclude_clusters
800
+ ]
801
+
894
802
  if namespace_name:
895
803
  namespaces = [n for n in namespaces if n["name"] == namespace_name]
804
+
896
805
  return namespaces
897
806
 
898
807
 
899
808
  def canonicalize_namespaces(
900
809
  namespaces: Iterable[dict[str, Any]],
901
- providers: list[str],
902
- resource_schema_filter: Optional[str] = None,
903
- ) -> tuple[list[dict[str, Any]], Optional[list[str]]]:
810
+ providers: Sequence[str],
811
+ resource_schema_filter: str | None = None,
812
+ ) -> tuple[list[dict[str, Any]], list[str] | None]:
904
813
  canonicalized_namespaces = []
905
814
  override = None
906
815
  logging.debug(f"Received providers {providers}")
@@ -924,6 +833,8 @@ def canonicalize_namespaces(
924
833
  override = ["Secret"]
925
834
  elif providers[0] == "route":
926
835
  override = ["Route"]
836
+ elif providers[0] == "prometheus-rule":
837
+ override = ["PrometheusRule"]
927
838
 
928
839
  namespace_info["openshiftResources"] = ors
929
840
  canonicalized_namespaces.append(namespace_info)
@@ -932,16 +843,17 @@ def canonicalize_namespaces(
932
843
 
933
844
 
934
845
  def get_namespaces(
935
- providers: Optional[list[str]] = None,
936
- cluster_name: Optional[str] = None,
937
- namespace_name: Optional[str] = None,
938
- resource_schema_filter: Optional[str] = None,
939
- filter_by_shard: Optional[bool] = True,
940
- ) -> tuple[list[dict[str, Any]], Optional[list[str]]]:
846
+ providers: Sequence[str] | None = None,
847
+ cluster_names: Iterable[str] | None = None,
848
+ exclude_clusters: Iterable[str] | None = None,
849
+ namespace_name: str | None = None,
850
+ resource_schema_filter: str | None = None,
851
+ filter_by_shard: bool | None = True,
852
+ ) -> tuple[list[dict[str, Any]], list[str] | None]:
941
853
  if providers is None:
942
854
  providers = []
943
855
  gqlapi = gql.get_api()
944
- namespaces = [
856
+ namespaces: list[dict[str, Any]] = [
945
857
  namespace_info
946
858
  for namespace_info in gqlapi.query(NAMESPACES_QUERY)["namespaces"]
947
859
  if not ob.is_namespace_deleted(namespace_info)
@@ -952,31 +864,51 @@ def get_namespaces(
952
864
  )
953
865
  )
954
866
  ]
955
- namespaces = filter_namespaces_by_cluster_and_namespace(
956
- namespaces, cluster_name, namespace_name
867
+ _namespaces = filter_namespaces_by_cluster_and_namespace(
868
+ namespaces, cluster_names, exclude_clusters, namespace_name
957
869
  )
958
- return canonicalize_namespaces(namespaces, providers, resource_schema_filter)
870
+ return canonicalize_namespaces(_namespaces, providers, resource_schema_filter)
959
871
 
960
872
 
961
873
  @defer
962
874
  def run(
963
- dry_run,
964
- thread_pool_size=10,
965
- internal=None,
966
- use_jump_host=True,
967
- providers=None,
968
- cluster_name=None,
969
- namespace_name=None,
970
- init_api_resources=False,
971
- defer=None,
972
- ):
875
+ dry_run: bool,
876
+ thread_pool_size: int = 10,
877
+ internal: bool | None = None,
878
+ use_jump_host: bool = True,
879
+ providers: Sequence[str] | None = None,
880
+ cluster_name: Sequence[str] | None = None,
881
+ exclude_cluster: Sequence[str] | None = None,
882
+ namespace_name: str | None = None,
883
+ init_api_resources: bool = False,
884
+ defer: Callable | None = None,
885
+ ) -> ResourceInventory | None:
886
+ # https://click.palletsprojects.com/en/8.1.x/options/#multiple-options
887
+ cluster_names = cluster_name
888
+ exclude_clusters = exclude_cluster
889
+
890
+ if exclude_clusters and not dry_run:
891
+ raise RuntimeError("--exclude-cluster is only supported in dry-run mode")
892
+
893
+ if exclude_clusters and cluster_names:
894
+ raise RuntimeError(
895
+ "--cluster-name and --exclude-cluster can not be used together"
896
+ )
897
+ if cluster_names and len(cluster_names) > 1 and not dry_run:
898
+ raise RuntimeError(
899
+ "Running with multiple clusters is only supported in dry-run mode"
900
+ )
901
+
973
902
  namespaces, overrides = get_namespaces(
974
- providers=providers, cluster_name=cluster_name, namespace_name=namespace_name
903
+ providers=providers,
904
+ cluster_names=cluster_names,
905
+ exclude_clusters=exclude_clusters,
906
+ namespace_name=namespace_name,
975
907
  )
976
908
  if not namespaces:
977
- logging.info(
909
+ logging.debug(
978
910
  "No namespaces found when filtering for "
979
- f"cluster={cluster_name}, namespace={namespace_name}. "
911
+ f"cluster={cluster_names}, namespace={namespace_name}. "
980
912
  "Exiting."
981
913
  )
982
914
  return None
@@ -988,12 +920,14 @@ def run(
988
920
  init_api_resources=init_api_resources,
989
921
  overrides=overrides,
990
922
  )
991
- defer(oc_map.cleanup)
923
+ if defer:
924
+ defer(oc_map.cleanup)
992
925
  if dry_run and QONTRACT_INTEGRATION == "openshift-resources":
993
926
  error = check_cluster_scoped_resources(oc_map, ri, namespaces, None)
994
927
  if error:
995
928
  sys.exit(1)
996
929
 
930
+ ob.publish_metrics(ri, QONTRACT_INTEGRATION)
997
931
  ob.realize_data(dry_run, oc_map, ri, thread_pool_size)
998
932
 
999
933
  if ri.has_error_registered():
@@ -1015,7 +949,7 @@ class CheckNamespaceResources(Protocol):
1015
949
  class CheckClusterScopedResourceNames:
1016
950
  oc_map: OC_Map
1017
951
  ri: ResourceInventory
1018
- namespaces: list[Mapping[str, Any]]
952
+ namespaces: Iterable[Mapping[str, Any]]
1019
953
 
1020
954
  def check(self) -> list[Exception]:
1021
955
  errors: list[Exception] = []
@@ -1064,7 +998,7 @@ class CheckClusterScopedResourceNames:
1064
998
  @dataclass
1065
999
  class CheckClusterScopedResourceDuplicates:
1066
1000
  oc_map: OC_Map
1067
- all_namespaces: Optional[list[Mapping]] = None
1001
+ all_namespaces: Iterable[Mapping] | None = None
1068
1002
 
1069
1003
  def check(self) -> list[Exception]:
1070
1004
  errors: list[Exception] = []
@@ -1086,13 +1020,13 @@ class CheckClusterScopedResourceDuplicates:
1086
1020
 
1087
1021
  def _find_resource_duplicates(
1088
1022
  self, cluster_cs_resources: dict[str, dict[str, dict[str, list[str]]]]
1089
- ) -> list[Tuple[str, str, str, list[str]]]:
1023
+ ) -> list[tuple[str, str, str, list[str]]]:
1090
1024
  # ) -> dict[Tuple[str, str, str], list[str]]:
1091
1025
  """Finds cluster resource duplicates by kind/name.
1092
1026
  :param cluster_cs_resources
1093
1027
  :return: duplicates as [(cluster, kind, name, [namespaces])]
1094
1028
  """
1095
- duplicates: list[Tuple[str, str, str, list[str]]] = []
1029
+ duplicates: list[tuple[str, str, str, list[str]]] = []
1096
1030
 
1097
1031
  for cluster, cluster_resources in cluster_cs_resources.items():
1098
1032
  _kind_name: dict[str, dict[str, list[str]]] = {}
@@ -1111,10 +1045,9 @@ class CheckClusterScopedResourceDuplicates:
1111
1045
  def check_cluster_scoped_resources(
1112
1046
  oc_map: OC_Map,
1113
1047
  ri: ResourceInventory,
1114
- namespaces: list[Mapping[str, Any]],
1115
- all_namespaces: Optional[list[Mapping[str, Any]]] = None,
1048
+ namespaces: Iterable[Mapping[str, Any]],
1049
+ all_namespaces: Iterable[Mapping[str, Any]] | None = None,
1116
1050
  ) -> bool:
1117
-
1118
1051
  checks = [
1119
1052
  CheckClusterScopedResourceNames(oc_map, ri, namespaces),
1120
1053
  CheckClusterScopedResourceDuplicates(oc_map, all_namespaces),
@@ -1134,7 +1067,7 @@ def check_cluster_scoped_resources(
1134
1067
  def get_cluster_scoped_resources(
1135
1068
  oc_map: OC_Map,
1136
1069
  clusters: Iterable[str],
1137
- namespaces: Optional[Iterable[Mapping[str, Any]]] = None,
1070
+ namespaces: Iterable[Mapping[str, Any]] | None = None,
1138
1071
  thread_pool_size: int = 10,
1139
1072
  ) -> dict[str, dict[str, dict[str, list[str]]]]:
1140
1073
  """Returns cluster scoped resources for a list of clusters
@@ -1171,7 +1104,7 @@ def get_cluster_scoped_resources(
1171
1104
  def _get_namespace_cluster_scoped_resources(
1172
1105
  namespace: Mapping,
1173
1106
  oc_map: OC_Map,
1174
- ) -> Tuple[str, str, dict[str, dict[str, Any]]]:
1107
+ ) -> tuple[str, str, dict[str, dict[str, Any]]]:
1175
1108
  """Returns all non-namespaced resources defined in a namespace manifest.
1176
1109
 
1177
1110
  :param namespace: the namespace dict
@@ -1190,7 +1123,7 @@ def _get_namespace_cluster_scoped_resources(
1190
1123
 
1191
1124
 
1192
1125
  def early_exit_desired_state(
1193
- providers: list[str], resource_schema_filter: Optional[str] = None
1126
+ providers: list[str], resource_schema_filter: str | None = None
1194
1127
  ) -> dict[str, Any]:
1195
1128
  settings = queries.get_secret_reader_settings()
1196
1129
  namespaces, _ = get_namespaces(
@@ -1212,20 +1145,28 @@ def early_exit_desired_state(
1212
1145
  settings=settings,
1213
1146
  )
1214
1147
 
1215
- def post_process_ns(ns):
1216
- ns[IDENTIFIER_FIELD_NAME] = f"{ns['cluster']['name']}/{ns['name']}"
1148
+ def post_process_ns(ns: MutableMapping) -> MutableMapping:
1217
1149
  # the sharedResources have been aggreated into the openshiftResources
1218
1150
  # and are no longer needed - speeds up diffing process
1219
1151
  del ns["sharedResources"]
1220
1152
  return ns
1221
1153
 
1154
+ # assemble all namespaces and resources file under a cluster
1155
+ state_for_clusters = defaultdict(list)
1156
+ for ns in namespaces:
1157
+ state_for_clusters[ns["cluster"]["name"]].append(post_process_ns(ns))
1158
+ for res in resources:
1159
+ state_for_clusters[res["cluster"]].append(res)
1160
+
1222
1161
  return {
1223
- "namespaces": [post_process_ns(ns) for ns in namespaces],
1224
- "resources": resources,
1162
+ "state": {
1163
+ cluster: {"shard": cluster, "hash": DeepHash(state).get(state)}
1164
+ for cluster, state in state_for_clusters.items()
1165
+ }
1225
1166
  }
1226
1167
 
1227
1168
 
1228
- def _early_exit_fetch_resource(spec, settings):
1169
+ def _early_exit_fetch_resource(spec: Sequence, settings: Mapping) -> dict[str, str]:
1229
1170
  resource = spec[0]
1230
1171
  ns_info = spec[1]
1231
1172
  cluster_name = ns_info["cluster"]["name"]
@@ -1253,47 +1194,82 @@ def _early_exit_fetch_resource(spec, settings):
1253
1194
 
1254
1195
 
1255
1196
  @contextmanager
1256
- def early_exit_monkey_patch():
1197
+ def early_exit_monkey_patch() -> Generator:
1257
1198
  """Avoid looking outside of app-interface on early-exit pr-check."""
1258
- orig_lookup_secret = lookup_secret
1259
- orig_lookup_github_file_content = lookup_github_file_content
1260
- orig_url_makes_sense = url_makes_sense
1261
- orig_check_alertmanager_config = check_alertmanager_config
1262
-
1263
- try:
1264
- yield _early_exit_monkey_patch_assign(
1265
- lambda path, key, version=None, tvars=None, settings=None: f"vault({path}, {key}, {version})",
1266
- lambda repo, path, ref, tvars=None, settings=None: f"github({repo}, {path}, {ref})",
1267
- lambda url: False,
1268
- lambda data, path, alertmanager_config_key, decode_base64=False: True,
1199
+ with patch.multiple(
1200
+ jinja2_utils,
1201
+ lookup_secret=DEFAULT,
1202
+ lookup_github_file_content=DEFAULT,
1203
+ url_makes_sense=DEFAULT,
1204
+ lookup_s3_object=DEFAULT,
1205
+ list_s3_objects=DEFAULT,
1206
+ ) as mocks:
1207
+ # mock lookup_secret
1208
+ mocks["lookup_secret"].side_effect = (
1209
+ lambda path,
1210
+ key,
1211
+ version=None,
1212
+ tvars=None,
1213
+ allow_not_found=False,
1214
+ settings=None,
1215
+ secret_reader=None: f"vault({path}, {key}, {version}"
1269
1216
  )
1270
- finally:
1271
- _early_exit_monkey_patch_assign(
1272
- orig_lookup_secret,
1273
- orig_lookup_github_file_content,
1274
- orig_url_makes_sense,
1275
- orig_check_alertmanager_config,
1217
+ # needed for jinja2 `is_safe_callable`
1218
+ mocks["lookup_secret"].unsafe_callable = False
1219
+ mocks["lookup_secret"].alters_data = False
1220
+
1221
+ # mock lookup_github_file_content
1222
+ mocks["lookup_github_file_content"].side_effect = (
1223
+ lambda repo,
1224
+ path,
1225
+ ref,
1226
+ tvars=None,
1227
+ settings=None,
1228
+ secret_reader=None: f"github({repo}, {path}, {ref})"
1276
1229
  )
1230
+ # needed for jinja2 `is_safe_callable`
1231
+ mocks["lookup_github_file_content"].unsafe_callable = False
1232
+ mocks["lookup_github_file_content"].alters_data = False
1233
+
1234
+ # mock url_makes_sense
1235
+ mocks["url_makes_sense"].side_effect = lambda url: False
1236
+ # needed for jinja2 `is_safe_callable`
1237
+ mocks["url_makes_sense"].unsafe_callable = False
1238
+ mocks["url_makes_sense"].alters_data = False
1239
+
1240
+ # mock lookup_s3_object
1241
+ mocks["lookup_s3_object"].side_effect = (
1242
+ lambda account_name,
1243
+ bucket_name,
1244
+ path,
1245
+ region_name=None: f"lookup_s3_object({account_name}, {bucket_name}, {path}, {region_name})"
1246
+ )
1247
+ # needed for jinja2 `is_safe_callable`
1248
+ mocks["lookup_s3_object"].unsafe_callable = False
1249
+ mocks["lookup_s3_object"].alters_data = False
1250
+
1251
+ # mock list_s3_objects
1252
+ mocks["list_s3_objects"].side_effect = (
1253
+ lambda account_name,
1254
+ bucket_name,
1255
+ path,
1256
+ region_name=None: f"list_s3_objects({account_name}, {bucket_name}, {path}, {region_name})"
1257
+ )
1258
+ # needed for jinja2 `is_safe_callable`
1259
+ mocks["list_s3_objects"].unsafe_callable = False
1260
+ mocks["list_s3_objects"].alters_data = False
1277
1261
 
1278
-
1279
- def _early_exit_monkey_patch_assign(
1280
- lookup_secret,
1281
- lookup_github_file_content,
1282
- url_makes_sense,
1283
- check_alertmanager_config,
1284
- ):
1285
- sys.modules[__name__].lookup_secret = lookup_secret
1286
- sys.modules[__name__].lookup_github_file_content = lookup_github_file_content
1287
- sys.modules[__name__].url_makes_sense = url_makes_sense
1288
- sys.modules[__name__].check_alertmanager_config = check_alertmanager_config
1262
+ with patch(
1263
+ "reconcile.openshift_resources_base.check_alertmanager_config",
1264
+ return_value=True,
1265
+ ):
1266
+ yield
1289
1267
 
1290
1268
 
1291
1269
  def desired_state_shard_config() -> DesiredStateShardConfig:
1292
1270
  return DesiredStateShardConfig(
1293
1271
  shard_arg_name="cluster_name",
1294
- shard_path_selectors={
1295
- "namespaces[*].cluster.name",
1296
- "resources[*].cluster",
1297
- },
1272
+ shard_arg_is_collection=True,
1273
+ shard_path_selectors={"state.*.shard"},
1298
1274
  sharded_run_review=lambda proposal: len(proposal.proposed_shards) <= 2,
1299
1275
  )