qontract-reconcile 0.10.2.dev361__py3-none-any.whl → 0.10.2.dev474__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 (371) hide show
  1. {qontract_reconcile-0.10.2.dev361.dist-info → qontract_reconcile-0.10.2.dev474.dist-info}/METADATA +14 -13
  2. {qontract_reconcile-0.10.2.dev361.dist-info → qontract_reconcile-0.10.2.dev474.dist-info}/RECORD +371 -364
  3. {qontract_reconcile-0.10.2.dev361.dist-info → qontract_reconcile-0.10.2.dev474.dist-info}/WHEEL +1 -1
  4. reconcile/acs_rbac.py +2 -2
  5. reconcile/aus/advanced_upgrade_service.py +18 -12
  6. reconcile/aus/aus_sts_gate_handler.py +59 -0
  7. reconcile/aus/base.py +137 -34
  8. reconcile/aus/cluster_version_data.py +15 -5
  9. reconcile/aus/models.py +3 -1
  10. reconcile/aus/ocm_addons_upgrade_scheduler_org.py +1 -0
  11. reconcile/aus/ocm_upgrade_scheduler.py +8 -1
  12. reconcile/aus/ocm_upgrade_scheduler_org.py +20 -5
  13. reconcile/aus/version_gate_approver.py +1 -16
  14. reconcile/aus/version_gates/sts_version_gate_handler.py +5 -72
  15. reconcile/automated_actions/config/integration.py +16 -4
  16. reconcile/aws_account_manager/integration.py +21 -9
  17. reconcile/aws_account_manager/reconciler.py +3 -3
  18. reconcile/aws_account_manager/utils.py +1 -1
  19. reconcile/aws_ami_cleanup/integration.py +8 -12
  20. reconcile/aws_ami_share.py +69 -62
  21. reconcile/aws_cloudwatch_log_retention/integration.py +155 -126
  22. reconcile/aws_ecr_image_pull_secrets.py +1 -1
  23. reconcile/aws_iam_keys.py +1 -0
  24. reconcile/aws_saml_idp/integration.py +12 -4
  25. reconcile/aws_saml_roles/integration.py +30 -23
  26. reconcile/aws_version_sync/integration.py +6 -12
  27. reconcile/change_owners/README.md +1 -1
  28. reconcile/change_owners/bundle.py +3 -3
  29. reconcile/change_owners/change_log_tracking.py +3 -2
  30. reconcile/change_owners/change_owners.py +108 -42
  31. reconcile/change_owners/decision.py +1 -1
  32. reconcile/change_owners/diff.py +0 -2
  33. reconcile/checkpoint.py +11 -3
  34. reconcile/cli.py +94 -11
  35. reconcile/dashdotdb_dora.py +5 -12
  36. reconcile/dashdotdb_slo.py +1 -1
  37. reconcile/database_access_manager.py +123 -117
  38. reconcile/dynatrace_token_provider/integration.py +1 -1
  39. reconcile/endpoints_discovery/integration.py +4 -1
  40. reconcile/endpoints_discovery/merge_request.py +1 -1
  41. reconcile/endpoints_discovery/merge_request_manager.py +8 -8
  42. reconcile/external_resources/factories.py +4 -6
  43. reconcile/external_resources/integration.py +1 -1
  44. reconcile/external_resources/manager.py +8 -6
  45. reconcile/external_resources/meta.py +0 -1
  46. reconcile/external_resources/metrics.py +1 -1
  47. reconcile/external_resources/model.py +19 -15
  48. reconcile/external_resources/reconciler.py +7 -4
  49. reconcile/external_resources/secrets_sync.py +6 -10
  50. reconcile/external_resources/state.py +26 -16
  51. reconcile/fleet_labeler/integration.py +1 -1
  52. reconcile/gabi_authorized_users.py +5 -2
  53. reconcile/gcp_image_mirror.py +2 -2
  54. reconcile/github_org.py +1 -1
  55. reconcile/github_owners.py +4 -0
  56. reconcile/gitlab_housekeeping.py +13 -15
  57. reconcile/gitlab_members.py +6 -12
  58. reconcile/gitlab_owners.py +15 -11
  59. reconcile/gitlab_permissions.py +8 -12
  60. reconcile/glitchtip_project_alerts/integration.py +3 -1
  61. reconcile/gql_definitions/acs/acs_instances.py +5 -5
  62. reconcile/gql_definitions/acs/acs_policies.py +5 -5
  63. reconcile/gql_definitions/acs/acs_rbac.py +5 -5
  64. reconcile/gql_definitions/advanced_upgrade_service/aus_clusters.py +5 -5
  65. reconcile/gql_definitions/advanced_upgrade_service/aus_organization.py +5 -5
  66. reconcile/gql_definitions/app_interface_metrics_exporter/onboarding_status.py +5 -5
  67. reconcile/gql_definitions/app_sre_tekton_access_revalidation/roles.py +5 -5
  68. reconcile/gql_definitions/app_sre_tekton_access_revalidation/users.py +5 -5
  69. reconcile/gql_definitions/automated_actions/instance.py +46 -7
  70. reconcile/gql_definitions/aws_account_manager/aws_accounts.py +14 -5
  71. reconcile/gql_definitions/aws_ami_cleanup/aws_accounts.py +15 -5
  72. reconcile/gql_definitions/aws_cloudwatch_log_retention/aws_accounts.py +27 -66
  73. reconcile/gql_definitions/aws_saml_idp/aws_accounts.py +15 -5
  74. reconcile/gql_definitions/aws_saml_roles/aws_accounts.py +15 -5
  75. reconcile/gql_definitions/aws_saml_roles/roles.py +5 -5
  76. reconcile/gql_definitions/aws_version_sync/clusters.py +5 -5
  77. reconcile/gql_definitions/aws_version_sync/namespaces.py +5 -5
  78. reconcile/gql_definitions/change_owners/queries/change_types.py +5 -5
  79. reconcile/gql_definitions/change_owners/queries/self_service_roles.py +5 -5
  80. reconcile/gql_definitions/cluster_auth_rhidp/clusters.py +5 -5
  81. reconcile/gql_definitions/common/alerting_services_settings.py +5 -5
  82. reconcile/gql_definitions/common/app_code_component_repos.py +5 -5
  83. reconcile/gql_definitions/common/app_interface_custom_messages.py +5 -5
  84. reconcile/gql_definitions/common/app_interface_dms_settings.py +5 -5
  85. reconcile/gql_definitions/common/app_interface_repo_settings.py +5 -5
  86. reconcile/gql_definitions/common/app_interface_roles.py +5 -5
  87. reconcile/gql_definitions/common/app_interface_state_settings.py +5 -5
  88. reconcile/gql_definitions/common/app_interface_vault_settings.py +5 -5
  89. reconcile/gql_definitions/common/app_quay_repos_escalation_policies.py +5 -5
  90. reconcile/gql_definitions/common/apps.py +5 -5
  91. reconcile/gql_definitions/common/aws_vpc_requests.py +18 -5
  92. reconcile/gql_definitions/common/aws_vpcs.py +5 -5
  93. reconcile/gql_definitions/common/clusters.py +7 -5
  94. reconcile/gql_definitions/common/clusters_minimal.py +5 -5
  95. reconcile/gql_definitions/common/clusters_with_dms.py +5 -5
  96. reconcile/gql_definitions/common/clusters_with_peering.py +5 -5
  97. reconcile/gql_definitions/common/github_orgs.py +5 -5
  98. reconcile/gql_definitions/common/jira_settings.py +5 -5
  99. reconcile/gql_definitions/common/jiralert_settings.py +5 -5
  100. reconcile/gql_definitions/common/ldap_settings.py +5 -5
  101. reconcile/gql_definitions/common/namespaces.py +5 -5
  102. reconcile/gql_definitions/common/namespaces_minimal.py +7 -5
  103. reconcile/gql_definitions/common/ocm_env_telemeter.py +5 -5
  104. reconcile/gql_definitions/common/ocm_environments.py +5 -5
  105. reconcile/gql_definitions/common/pagerduty_instances.py +5 -5
  106. reconcile/gql_definitions/common/pgp_reencryption_settings.py +5 -5
  107. reconcile/gql_definitions/common/pipeline_providers.py +5 -5
  108. reconcile/gql_definitions/common/quay_instances.py +5 -5
  109. reconcile/gql_definitions/common/quay_orgs.py +5 -5
  110. reconcile/gql_definitions/common/reserved_networks.py +5 -5
  111. reconcile/gql_definitions/common/rhcs_provider_settings.py +5 -5
  112. reconcile/gql_definitions/common/saas_files.py +5 -5
  113. reconcile/gql_definitions/common/saas_target_namespaces.py +5 -5
  114. reconcile/gql_definitions/common/saasherder_settings.py +5 -5
  115. reconcile/gql_definitions/common/slack_workspaces.py +5 -5
  116. reconcile/gql_definitions/common/smtp_client_settings.py +5 -5
  117. reconcile/gql_definitions/common/state_aws_account.py +5 -5
  118. reconcile/gql_definitions/common/users.py +5 -5
  119. reconcile/gql_definitions/common/users_with_paths.py +5 -5
  120. reconcile/gql_definitions/cost_report/app_names.py +5 -5
  121. reconcile/gql_definitions/cost_report/cost_namespaces.py +5 -5
  122. reconcile/gql_definitions/cost_report/settings.py +5 -5
  123. reconcile/gql_definitions/dashdotdb_slo/slo_documents_query.py +5 -5
  124. reconcile/gql_definitions/dynatrace_token_provider/dynatrace_bootstrap_tokens.py +5 -5
  125. reconcile/gql_definitions/dynatrace_token_provider/token_specs.py +5 -5
  126. reconcile/gql_definitions/email_sender/apps.py +5 -5
  127. reconcile/gql_definitions/email_sender/emails.py +5 -5
  128. reconcile/gql_definitions/email_sender/users.py +5 -5
  129. reconcile/gql_definitions/endpoints_discovery/apps.py +5 -5
  130. reconcile/gql_definitions/external_resources/aws_accounts.py +5 -5
  131. reconcile/gql_definitions/external_resources/external_resources_modules.py +5 -5
  132. reconcile/gql_definitions/external_resources/external_resources_namespaces.py +38 -7
  133. reconcile/gql_definitions/external_resources/external_resources_settings.py +5 -5
  134. reconcile/gql_definitions/external_resources/fragments/external_resources_module_overrides.py +5 -5
  135. reconcile/gql_definitions/fleet_labeler/fleet_labels.py +5 -5
  136. reconcile/gql_definitions/fragments/aus_organization.py +5 -5
  137. reconcile/gql_definitions/fragments/aws_account_common.py +7 -5
  138. reconcile/gql_definitions/fragments/aws_account_managed.py +5 -5
  139. reconcile/gql_definitions/fragments/aws_account_sso.py +5 -5
  140. reconcile/gql_definitions/fragments/aws_infra_management_account.py +5 -5
  141. reconcile/gql_definitions/fragments/aws_organization.py +33 -0
  142. reconcile/gql_definitions/fragments/aws_vpc.py +5 -5
  143. reconcile/gql_definitions/fragments/aws_vpc_request.py +12 -5
  144. reconcile/gql_definitions/fragments/container_image_mirror.py +5 -5
  145. reconcile/gql_definitions/fragments/deploy_resources.py +5 -5
  146. reconcile/gql_definitions/fragments/disable.py +5 -5
  147. reconcile/gql_definitions/fragments/email_service.py +5 -5
  148. reconcile/gql_definitions/fragments/email_user.py +5 -5
  149. reconcile/gql_definitions/fragments/jumphost_common_fields.py +5 -5
  150. reconcile/gql_definitions/fragments/membership_source.py +5 -5
  151. reconcile/gql_definitions/fragments/minimal_ocm_organization.py +5 -5
  152. reconcile/gql_definitions/fragments/oc_connection_cluster.py +5 -5
  153. reconcile/gql_definitions/fragments/ocm_environment.py +5 -5
  154. reconcile/gql_definitions/fragments/pipeline_provider_retention.py +5 -5
  155. reconcile/gql_definitions/fragments/prometheus_instance.py +5 -5
  156. reconcile/gql_definitions/fragments/resource_limits_requirements.py +5 -5
  157. reconcile/gql_definitions/fragments/resource_requests_requirements.py +5 -5
  158. reconcile/gql_definitions/fragments/resource_values.py +5 -5
  159. reconcile/gql_definitions/fragments/saas_slo_document.py +5 -5
  160. reconcile/gql_definitions/fragments/saas_target_namespace.py +5 -5
  161. reconcile/gql_definitions/fragments/serviceaccount_token.py +5 -5
  162. reconcile/gql_definitions/fragments/terraform_state.py +5 -5
  163. reconcile/gql_definitions/fragments/upgrade_policy.py +5 -5
  164. reconcile/gql_definitions/fragments/user.py +5 -5
  165. reconcile/gql_definitions/fragments/vault_secret.py +5 -5
  166. reconcile/gql_definitions/gcp/gcp_docker_repos.py +5 -5
  167. reconcile/gql_definitions/gcp/gcp_projects.py +5 -5
  168. reconcile/gql_definitions/gitlab_members/gitlab_instances.py +5 -5
  169. reconcile/gql_definitions/gitlab_members/permissions.py +5 -5
  170. reconcile/gql_definitions/glitchtip/glitchtip_instance.py +5 -5
  171. reconcile/gql_definitions/glitchtip/glitchtip_project.py +5 -5
  172. reconcile/gql_definitions/glitchtip_project_alerts/glitchtip_project.py +5 -5
  173. reconcile/gql_definitions/integrations/integrations.py +5 -5
  174. reconcile/gql_definitions/introspection.json +775 -136
  175. reconcile/gql_definitions/jenkins_configs/jenkins_configs.py +5 -5
  176. reconcile/gql_definitions/jenkins_configs/jenkins_instances.py +5 -5
  177. reconcile/gql_definitions/jira/jira_servers.py +5 -5
  178. reconcile/gql_definitions/jira_permissions_validator/jira_boards_for_permissions_validator.py +9 -5
  179. reconcile/gql_definitions/jumphosts/jumphosts.py +5 -5
  180. reconcile/gql_definitions/ldap_groups/roles.py +5 -5
  181. reconcile/gql_definitions/ldap_groups/settings.py +5 -5
  182. reconcile/gql_definitions/maintenance/maintenances.py +5 -5
  183. reconcile/gql_definitions/membershipsources/roles.py +5 -5
  184. reconcile/gql_definitions/ocm_labels/clusters.py +5 -5
  185. reconcile/gql_definitions/ocm_labels/organizations.py +5 -5
  186. reconcile/gql_definitions/openshift_cluster_bots/clusters.py +5 -5
  187. reconcile/gql_definitions/openshift_groups/managed_groups.py +5 -5
  188. reconcile/gql_definitions/openshift_groups/managed_roles.py +5 -5
  189. reconcile/gql_definitions/openshift_serviceaccount_tokens/tokens.py +5 -5
  190. reconcile/gql_definitions/quay_membership/quay_membership.py +5 -5
  191. reconcile/gql_definitions/rhcs/certs.py +25 -79
  192. reconcile/gql_definitions/rhcs/openshift_resource_rhcs_cert.py +43 -0
  193. reconcile/gql_definitions/rhidp/organizations.py +5 -5
  194. reconcile/gql_definitions/service_dependencies/jenkins_instance_fragment.py +5 -5
  195. reconcile/gql_definitions/service_dependencies/service_dependencies.py +5 -5
  196. reconcile/gql_definitions/sharding/aws_accounts.py +5 -5
  197. reconcile/gql_definitions/sharding/ocm_organization.py +5 -5
  198. reconcile/gql_definitions/skupper_network/site_controller_template.py +5 -5
  199. reconcile/gql_definitions/skupper_network/skupper_networks.py +5 -5
  200. reconcile/gql_definitions/slack_usergroups/clusters.py +5 -5
  201. reconcile/gql_definitions/slack_usergroups/permissions.py +5 -5
  202. reconcile/gql_definitions/slack_usergroups/users.py +5 -5
  203. reconcile/gql_definitions/slo_documents/slo_documents.py +5 -5
  204. reconcile/gql_definitions/status_board/status_board.py +5 -5
  205. reconcile/gql_definitions/statuspage/statuspages.py +5 -5
  206. reconcile/gql_definitions/templating/template_collection.py +5 -5
  207. reconcile/gql_definitions/templating/templates.py +5 -5
  208. reconcile/gql_definitions/terraform_cloudflare_dns/app_interface_cloudflare_dns_settings.py +5 -5
  209. reconcile/gql_definitions/terraform_cloudflare_dns/terraform_cloudflare_zones.py +5 -5
  210. reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_accounts.py +5 -5
  211. reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_resources.py +5 -5
  212. reconcile/gql_definitions/terraform_cloudflare_users/app_interface_setting_cloudflare_and_vault.py +5 -5
  213. reconcile/gql_definitions/terraform_cloudflare_users/terraform_cloudflare_roles.py +5 -5
  214. reconcile/gql_definitions/terraform_init/aws_accounts.py +19 -5
  215. reconcile/gql_definitions/terraform_repo/terraform_repo.py +5 -5
  216. reconcile/gql_definitions/terraform_resources/database_access_manager.py +5 -5
  217. reconcile/gql_definitions/terraform_resources/terraform_resources_namespaces.py +37 -7
  218. reconcile/gql_definitions/terraform_tgw_attachments/aws_accounts.py +15 -5
  219. reconcile/gql_definitions/unleash_feature_toggles/feature_toggles.py +5 -5
  220. reconcile/gql_definitions/vault_instances/vault_instances.py +5 -5
  221. reconcile/gql_definitions/vault_policies/vault_policies.py +5 -5
  222. reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator.py +8 -5
  223. reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator_peered_cluster_fragment.py +6 -5
  224. reconcile/integrations_manager.py +3 -3
  225. reconcile/jenkins_worker_fleets.py +10 -8
  226. reconcile/jira_permissions_validator.py +237 -122
  227. reconcile/ldap_groups/integration.py +1 -1
  228. reconcile/ocm/types.py +35 -57
  229. reconcile/ocm_aws_infrastructure_access.py +1 -1
  230. reconcile/ocm_clusters.py +4 -4
  231. reconcile/ocm_labels/integration.py +3 -2
  232. reconcile/ocm_machine_pools.py +33 -27
  233. reconcile/openshift_base.py +113 -4
  234. reconcile/openshift_cluster_bots.py +1 -1
  235. reconcile/openshift_namespace_labels.py +1 -1
  236. reconcile/openshift_namespaces.py +96 -101
  237. reconcile/openshift_resources_base.py +6 -2
  238. reconcile/openshift_rhcs_certs.py +74 -37
  239. reconcile/openshift_rolebindings.py +7 -11
  240. reconcile/openshift_saas_deploy.py +4 -5
  241. reconcile/openshift_saas_deploy_change_tester.py +9 -7
  242. reconcile/openshift_saas_deploy_trigger_cleaner.py +3 -5
  243. reconcile/openshift_serviceaccount_tokens.py +2 -2
  244. reconcile/openshift_upgrade_watcher.py +4 -4
  245. reconcile/oum/labelset.py +5 -3
  246. reconcile/oum/models.py +1 -4
  247. reconcile/prometheus_rules_tester/integration.py +3 -3
  248. reconcile/quay_base.py +25 -6
  249. reconcile/quay_membership.py +55 -29
  250. reconcile/quay_mirror.py +1 -1
  251. reconcile/quay_mirror_org.py +6 -4
  252. reconcile/quay_permissions.py +81 -75
  253. reconcile/quay_repos.py +35 -37
  254. reconcile/queries.py +132 -1
  255. reconcile/rhidp/common.py +3 -5
  256. reconcile/rhidp/sso_client/base.py +16 -5
  257. reconcile/saas_auto_promotions_manager/merge_request_manager/renderer.py +1 -1
  258. reconcile/saas_auto_promotions_manager/subscriber.py +4 -3
  259. reconcile/skupper_network/integration.py +2 -2
  260. reconcile/slack_usergroups.py +35 -14
  261. reconcile/sql_query.py +1 -0
  262. reconcile/status_board.py +6 -6
  263. reconcile/statuspage/atlassian.py +7 -7
  264. reconcile/statuspage/integrations/maintenances.py +4 -3
  265. reconcile/statuspage/page.py +4 -9
  266. reconcile/statuspage/status.py +5 -8
  267. reconcile/templates/rosa-classic-cluster-creation.sh.j2 +1 -1
  268. reconcile/templates/rosa-hcp-cluster-creation.sh.j2 +1 -1
  269. reconcile/templating/lib/rendering.py +3 -3
  270. reconcile/templating/renderer.py +2 -2
  271. reconcile/templating/validator.py +4 -4
  272. reconcile/terraform_aws_route53.py +7 -1
  273. reconcile/terraform_cloudflare_dns.py +3 -3
  274. reconcile/terraform_cloudflare_resources.py +5 -5
  275. reconcile/terraform_cloudflare_users.py +3 -2
  276. reconcile/terraform_init/integration.py +187 -23
  277. reconcile/terraform_repo.py +16 -12
  278. reconcile/terraform_resources.py +6 -6
  279. reconcile/terraform_tgw_attachments.py +27 -19
  280. reconcile/terraform_users.py +7 -0
  281. reconcile/terraform_vpc_peerings.py +14 -3
  282. reconcile/terraform_vpc_resources/integration.py +20 -8
  283. reconcile/terraform_vpc_resources/merge_request.py +12 -2
  284. reconcile/terraform_vpc_resources/merge_request_manager.py +43 -19
  285. reconcile/typed_queries/aws_account_tags.py +41 -0
  286. reconcile/typed_queries/cost_report/app_names.py +1 -1
  287. reconcile/typed_queries/cost_report/cost_namespaces.py +2 -2
  288. reconcile/typed_queries/saas_files.py +20 -15
  289. reconcile/typed_queries/status_board.py +2 -2
  290. reconcile/unleash_feature_toggles/integration.py +4 -2
  291. reconcile/utils/acs/base.py +6 -3
  292. reconcile/utils/acs/policies.py +2 -2
  293. reconcile/utils/aws_api.py +51 -20
  294. reconcile/utils/aws_api_typed/api.py +38 -9
  295. reconcile/utils/aws_api_typed/cloudformation.py +149 -0
  296. reconcile/utils/aws_api_typed/logs.py +73 -0
  297. reconcile/utils/aws_api_typed/organization.py +4 -2
  298. reconcile/utils/binary.py +7 -12
  299. reconcile/utils/datetime_util.py +67 -0
  300. reconcile/utils/deadmanssnitch_api.py +1 -1
  301. reconcile/utils/differ.py +2 -3
  302. reconcile/utils/early_exit_cache.py +11 -12
  303. reconcile/utils/environ.py +5 -0
  304. reconcile/utils/expiration.py +7 -3
  305. reconcile/utils/external_resource_spec.py +2 -0
  306. reconcile/utils/filtering.py +1 -1
  307. reconcile/utils/gitlab_api.py +19 -5
  308. reconcile/utils/glitchtip/client.py +6 -2
  309. reconcile/utils/glitchtip/models.py +25 -28
  310. reconcile/utils/gql.py +4 -7
  311. reconcile/utils/helpers.py +1 -1
  312. reconcile/utils/instrumented_wrappers.py +1 -1
  313. reconcile/utils/internal_groups/client.py +2 -2
  314. reconcile/utils/internal_groups/models.py +8 -17
  315. reconcile/utils/jinja2/utils.py +6 -101
  316. reconcile/utils/jira_client.py +82 -63
  317. reconcile/utils/jjb_client.py +26 -13
  318. reconcile/utils/jobcontroller/controller.py +2 -2
  319. reconcile/utils/jobcontroller/models.py +17 -1
  320. reconcile/utils/json.py +43 -1
  321. reconcile/utils/membershipsources/app_interface_resolver.py +4 -2
  322. reconcile/utils/membershipsources/models.py +16 -23
  323. reconcile/utils/membershipsources/resolver.py +4 -2
  324. reconcile/utils/merge_request_manager/merge_request_manager.py +4 -4
  325. reconcile/utils/merge_request_manager/parser.py +6 -6
  326. reconcile/utils/metrics.py +5 -5
  327. reconcile/utils/models.py +304 -82
  328. reconcile/utils/mr/app_interface_reporter.py +2 -2
  329. reconcile/utils/mr/notificator.py +3 -3
  330. reconcile/utils/mr/update_access_report_base.py +3 -4
  331. reconcile/utils/mr/user_maintenance.py +3 -2
  332. reconcile/utils/oc.py +252 -201
  333. reconcile/utils/oc_filters.py +3 -3
  334. reconcile/utils/ocm/addons.py +0 -1
  335. reconcile/utils/ocm/base.py +17 -20
  336. reconcile/utils/ocm/cluster_groups.py +1 -1
  337. reconcile/utils/ocm/identity_providers.py +2 -2
  338. reconcile/utils/ocm/labels.py +1 -1
  339. reconcile/utils/ocm/products.py +8 -8
  340. reconcile/utils/ocm/search_filters.py +3 -6
  341. reconcile/utils/ocm/service_log.py +4 -6
  342. reconcile/utils/ocm/sre_capability_labels.py +20 -13
  343. reconcile/utils/openshift_resource.py +8 -3
  344. reconcile/utils/pagerduty_api.py +10 -7
  345. reconcile/utils/promotion_state.py +6 -11
  346. reconcile/utils/quay_api.py +74 -87
  347. reconcile/utils/raw_github_api.py +1 -1
  348. reconcile/utils/rhcsv2_certs.py +86 -23
  349. reconcile/utils/rosa/session.py +16 -0
  350. reconcile/utils/runtime/integration.py +2 -3
  351. reconcile/utils/runtime/runner.py +2 -2
  352. reconcile/utils/saasherder/interfaces.py +13 -20
  353. reconcile/utils/saasherder/models.py +23 -20
  354. reconcile/utils/saasherder/saasherder.py +50 -27
  355. reconcile/utils/slack_api.py +2 -2
  356. reconcile/utils/sloth.py +171 -2
  357. reconcile/utils/structs.py +1 -1
  358. reconcile/utils/terraform_client.py +5 -4
  359. reconcile/utils/terrascript_aws_client.py +274 -124
  360. reconcile/utils/unleash/server.py +2 -8
  361. reconcile/utils/vault.py +5 -12
  362. reconcile/utils/vcs.py +8 -8
  363. reconcile/vault_replication.py +107 -42
  364. reconcile/vpc_peerings_validator.py +13 -0
  365. tools/app_interface_reporter.py +4 -4
  366. tools/cli_commands/cost_report/cost_management_api.py +3 -3
  367. tools/cli_commands/cost_report/view.py +7 -6
  368. tools/cli_commands/erv2.py +1 -1
  369. tools/qontract_cli.py +28 -17
  370. tools/template_validation.py +3 -1
  371. {qontract_reconcile-0.10.2.dev361.dist-info → qontract_reconcile-0.10.2.dev474.dist-info}/entry_points.txt +0 -0
@@ -6,6 +6,8 @@ from typing import (
6
6
  cast,
7
7
  )
8
8
 
9
+ from reconcile.utils.datetime_util import ensure_utc, utc_now
10
+
9
11
  DATE_FORMAT = "%Y-%m-%d"
10
12
 
11
13
 
@@ -17,12 +19,14 @@ DictsOrRoles = TypeVar("DictsOrRoles", bound=Iterable[FilterableRole] | Iterable
17
19
 
18
20
 
19
21
  def date_expired(date: str) -> bool:
20
- exp_date = datetime.datetime.strptime(date, DATE_FORMAT).date()
21
- current_date = datetime.datetime.utcnow().date()
22
+ exp_date = ensure_utc(datetime.datetime.strptime(date, DATE_FORMAT)).date() # noqa: DTZ007
23
+ current_date = utc_now().date()
22
24
  return current_date >= exp_date
23
25
 
24
26
 
25
- def filter(roles: DictsOrRoles | None) -> DictsOrRoles:
27
+ def filter[DictsOrRoles: Iterable[FilterableRole] | Iterable[dict]](
28
+ roles: DictsOrRoles | None,
29
+ ) -> DictsOrRoles:
26
30
  """Filters roles and returns the ones which are not yet expired."""
27
31
  filtered = []
28
32
  for r in roles or []:
@@ -157,6 +157,8 @@ class ExternalResourceSpec:
157
157
  tags["cost-center"] = cost_center
158
158
  if service_phase := self.namespace["environment"].get("servicePhase"):
159
159
  tags["service-phase"] = service_phase
160
+ if cost_center := self.namespace["environment"].get("costCenter"):
161
+ tags["cost-center"] = cost_center
160
162
 
161
163
  resource_tags_str = self.resource.get("tags")
162
164
  if resource_tags_str:
@@ -6,7 +6,7 @@ KeyType = TypeVar("KeyType")
6
6
  ValueType = TypeVar("ValueType")
7
7
 
8
8
 
9
- def remove_none_values_from_dict(
9
+ def remove_none_values_from_dict[KeyType, ValueType](
10
10
  a_dict: dict[KeyType, ValueType | None],
11
11
  ) -> dict[KeyType, ValueType]:
12
12
  """
@@ -263,13 +263,13 @@ class GitLabApi:
263
263
  # we can determine if a pending MR exists based on the title
264
264
  return any(mr.title == title for mr in mrs)
265
265
 
266
- @retry()
266
+ @retry(no_retry_exceptions=(RuntimeError,))
267
267
  def get_project_maintainers(
268
268
  self, repo_url: str | None = None, query: dict | None = None
269
- ) -> list[str] | None:
269
+ ) -> list[str]:
270
270
  project = self.project if repo_url is None else self.get_project(repo_url)
271
271
  if project is None:
272
- return None
272
+ raise RuntimeError("project not found")
273
273
  members = project.members_all.list(iterator=True, query_parameters=query or {})
274
274
  return [m.username for m in members if m.access_level >= 40]
275
275
 
@@ -444,6 +444,8 @@ class GitLabApi:
444
444
  def get_merge_request_comments(
445
445
  merge_request: ProjectMergeRequest,
446
446
  include_description: bool = False,
447
+ include_approvals: bool = False,
448
+ approval_body: str = "",
447
449
  ) -> list[Comment]:
448
450
  comments = []
449
451
  if include_description:
@@ -455,6 +457,16 @@ class GitLabApi:
455
457
  created_at=merge_request.created_at,
456
458
  )
457
459
  )
460
+ if include_approvals:
461
+ comments.extend(
462
+ Comment(
463
+ id=approval["user"]["id"],
464
+ username=approval["user"]["username"],
465
+ body=approval_body,
466
+ created_at=approval["approved_at"],
467
+ )
468
+ for approval in merge_request.approvals.get().approved_by
469
+ )
458
470
  comments.extend(
459
471
  Comment(
460
472
  id=note.id,
@@ -826,14 +838,16 @@ class GitLabApi:
826
838
  )
827
839
 
828
840
  def get_commit_sha(self, ref: str, repo_url: str) -> str:
829
- project = self.get_project(repo_url)
841
+ if not (project := self.get_project(repo_url)):
842
+ raise ValueError(f"Project not found for repo_url: {repo_url}")
830
843
  commits = project.commits.list(ref_name=ref, per_page=1, page=1)
831
844
  return commits[0].id
832
845
 
833
846
  def repository_compare(
834
847
  self, repo_url: str, ref_from: str, ref_to: str
835
848
  ) -> list[dict[str, Any]]:
836
- project = self.get_project(repo_url)
849
+ if not (project := self.get_project(repo_url)):
850
+ raise ValueError(f"Project not found for repo_url: {repo_url}")
837
851
  response: Any = project.repository_compare(ref_from, ref_to)
838
852
  return response.get("commits", [])
839
853
 
@@ -164,7 +164,9 @@ class GlitchtipClient(ApiBase):
164
164
  return ProjectAlert(
165
165
  **self._post(
166
166
  f"/api/0/projects/{organization_slug}/{project_slug}/alerts/",
167
- data=alert.dict(by_alias=True, exclude_unset=True, exclude_none=True),
167
+ data=alert.model_dump(
168
+ mode="json", by_alias=True, exclude_unset=True, exclude_none=True
169
+ ),
168
170
  )
169
171
  )
170
172
 
@@ -183,7 +185,9 @@ class GlitchtipClient(ApiBase):
183
185
  return ProjectAlert(
184
186
  **self._put(
185
187
  f"/api/0/projects/{organization_slug}/{project_slug}/alerts/{alert.pk}/",
186
- data=alert.dict(by_alias=True, exclude_unset=True, exclude_none=True),
188
+ data=alert.model_dump(
189
+ mode="json", by_alias=True, exclude_unset=True, exclude_none=True
190
+ ),
187
191
  )
188
192
  )
189
193
 
@@ -3,13 +3,13 @@ from __future__ import annotations
3
3
  import re
4
4
  from datetime import datetime
5
5
  from enum import Enum
6
- from typing import TYPE_CHECKING, Any
6
+ from typing import TYPE_CHECKING, Any, Self
7
7
 
8
8
  from pydantic import (
9
9
  BaseModel,
10
10
  Field,
11
- root_validator,
12
- validator,
11
+ field_validator,
12
+ model_validator,
13
13
  )
14
14
 
15
15
  if TYPE_CHECKING:
@@ -49,7 +49,8 @@ class Team(BaseModel):
49
49
  slug: str = ""
50
50
  users: list[User] = []
51
51
 
52
- @root_validator(pre=True)
52
+ @model_validator(mode="before")
53
+ @classmethod
53
54
  def name_xor_slug_must_be_set(
54
55
  cls, values: MutableMapping[str, Any]
55
56
  ) -> MutableMapping[str, Any]:
@@ -58,11 +59,11 @@ class Team(BaseModel):
58
59
  ), "name xor slug must be set!"
59
60
  return values
60
61
 
61
- @root_validator
62
- def slugify(cls, values: MutableMapping[str, Any]) -> MutableMapping[str, Any]:
63
- values["slug"] = values.get("slug") or slugify(values.get("name", ""))
64
- values["name"] = slugify(values.get("name", "")) or values.get("slug")
65
- return values
62
+ @model_validator(mode="after")
63
+ def slugify(self) -> Self:
64
+ self.slug = self.slug or slugify(self.name)
65
+ self.name = slugify(self.name) or self.slug
66
+ return self
66
67
 
67
68
  def __lt__(self, other: Team) -> bool:
68
69
  return self.slug < other.slug
@@ -86,16 +87,15 @@ class RecipientType(Enum):
86
87
  WEBHOOK = "webhook"
87
88
 
88
89
 
89
- class ProjectAlertRecipient(BaseModel):
90
+ class ProjectAlertRecipient(
91
+ BaseModel, validate_by_name=True, validate_by_alias=True, use_enum_values=True
92
+ ):
90
93
  pk: int | None = Field(None, alias="id")
91
94
  recipient_type: RecipientType = Field(..., alias="recipientType")
92
95
  url: str = ""
93
96
 
94
- class Config:
95
- allow_population_by_field_name = True
96
- use_enum_values = True
97
-
98
- @validator("recipient_type")
97
+ @field_validator("recipient_type")
98
+ @classmethod
99
99
  def recipient_type_enforce_enum_type(cls, v: str | RecipientType) -> RecipientType:
100
100
  if isinstance(v, RecipientType):
101
101
  return v
@@ -113,17 +113,15 @@ class ProjectAlertRecipient(BaseModel):
113
113
  return hash((self.recipient_type, self.url))
114
114
 
115
115
 
116
- class ProjectAlert(BaseModel):
116
+ class ProjectAlert(BaseModel, validate_by_name=True, validate_by_alias=True):
117
117
  pk: int | None = Field(None, alias="id")
118
118
  name: str
119
119
  timespan_minutes: int = Field(..., alias="timespanMinutes")
120
120
  quantity: int
121
121
  recipients: list[ProjectAlertRecipient] = Field([], alias="alertRecipients")
122
122
 
123
- class Config:
124
- allow_population_by_field_name = True
125
-
126
- @root_validator
123
+ @model_validator(mode="before")
124
+ @classmethod
127
125
  def empty_name(cls, values: MutableMapping[str, Any]) -> MutableMapping[str, Any]:
128
126
  # name is an empty string if the alert was created manually because it can't be set via UI
129
127
  # use the pk instead.
@@ -141,20 +139,18 @@ class ProjectAlert(BaseModel):
141
139
  )
142
140
 
143
141
 
144
- class Project(BaseModel):
142
+ class Project(BaseModel, validate_by_name=True, validate_by_alias=True):
145
143
  pk: int | None = Field(None, alias="id")
146
144
  name: str
147
145
  slug: str = ""
148
- platform: str | None
146
+ platform: str | None = None
149
147
  teams: list[Team] = []
150
148
  alerts: list[ProjectAlert] = []
151
149
  event_throttle_rate: int = Field(0, alias="eventThrottleRate")
152
150
  organization: Organization | None = None
153
151
 
154
- class Config:
155
- allow_population_by_field_name = True
156
-
157
- @root_validator
152
+ @model_validator(mode="before")
153
+ @classmethod
158
154
  def slugify(cls, values: MutableMapping[str, Any]) -> MutableMapping[str, Any]:
159
155
  values["slug"] = values.get("slug") or slugify(values["name"])
160
156
  return values
@@ -195,7 +191,8 @@ class Organization(BaseModel):
195
191
  teams: list[Team] = []
196
192
  users: list[User] = []
197
193
 
198
- @root_validator
194
+ @model_validator(mode="before")
195
+ @classmethod
199
196
  def slugify(cls, values: MutableMapping[str, Any]) -> MutableMapping[str, Any]:
200
197
  values["slug"] = values.get("slug") or slugify(values["name"])
201
198
  return values
@@ -212,4 +209,4 @@ class Organization(BaseModel):
212
209
  return hash(self.name)
213
210
 
214
211
 
215
- Project.update_forward_refs()
212
+ Project.model_rebuild()
reconcile/utils/gql.py CHANGED
@@ -109,7 +109,7 @@ class GqlApi:
109
109
  if int_name:
110
110
  integrations = self.query(INTEGRATIONS_QUERY, skip_validation=True)
111
111
 
112
- for integration in integrations["integrations"]:
112
+ for integration in integrations["integrations"] if integrations else []:
113
113
  if integration["name"] == int_name:
114
114
  self._valid_schemas = integration["schemas"]
115
115
  break
@@ -142,7 +142,7 @@ class GqlApi:
142
142
  query: str,
143
143
  variables: dict[str, Any] | None = None,
144
144
  skip_validation: bool = False,
145
- ) -> dict[str, Any] | None:
145
+ ) -> dict[str, Any]:
146
146
  try:
147
147
  result = self.client.execute(
148
148
  gql(query), variables, get_execution_result=True
@@ -172,11 +172,8 @@ class GqlApi:
172
172
  if forbidden_schemas:
173
173
  raise GqlApiErrorForbiddenSchemaError(forbidden_schemas)
174
174
 
175
- # This is to appease mypy. This exception won't be thrown as this condition
176
- # is already handled above with AssertionError
177
- if result["data"] is None:
178
- raise GqlApiError("`data` not received in GraphQL payload")
179
-
175
+ # make mypy happy
176
+ assert "data" in result and result["data"] is not None
180
177
  return result["data"]
181
178
 
182
179
  def get_template(self, path: str) -> dict[str, str]:
@@ -47,7 +47,7 @@ def flatten(
47
47
  Item = TypeVar("Item")
48
48
 
49
49
 
50
- def find_duplicates(items: Iterable[Item]) -> list[Item]:
50
+ def find_duplicates[Item](items: Iterable[Item]) -> list[Item]:
51
51
  return [item for item, count in Counter(items).items() if count > 1]
52
52
 
53
53
 
@@ -40,7 +40,7 @@ class InstrumentedImage(Image):
40
40
 
41
41
 
42
42
  class InstrumentedSkopeo(Skopeo):
43
- def copy(self, *args: Any, **kwargs: Any) -> bytes | str:
43
+ def copy(self, *args: Any, **kwargs: Any) -> None:
44
44
  metrics.copy_count.labels(
45
45
  integration=INTEGRATION_NAME, shard=SHARDS, shard_id=SHARD_ID
46
46
  ).inc()
@@ -140,7 +140,7 @@ class InternalGroupsClient:
140
140
  with self._api as api:
141
141
  return Group(
142
142
  **api.create_group(
143
- data=group.dict(by_alias=True),
143
+ data=group.model_dump(by_alias=True),
144
144
  )
145
145
  )
146
146
 
@@ -155,6 +155,6 @@ class InternalGroupsClient:
155
155
  return Group(
156
156
  **api.update_group(
157
157
  name=group.name,
158
- data=group.dict(by_alias=True),
158
+ data=group.model_dump(by_alias=True),
159
159
  )
160
160
  )
@@ -27,7 +27,7 @@ class Entity(BaseModel):
27
27
  return hash(self.id)
28
28
 
29
29
 
30
- class Group(BaseModel):
30
+ class Group(BaseModel, validate_by_name=True, validate_by_alias=True):
31
31
  name: str
32
32
  description: str
33
33
  member_approval_type: str = Field("self-service", alias="memberApprovalType")
@@ -35,16 +35,18 @@ class Group(BaseModel):
35
35
  owners: list[Entity]
36
36
  display_name: str = Field(..., alias="displayName")
37
37
  notes: str | None = None
38
- rover_group_member_query: str | None = Field(None, alias="roverGroupMemberQuery")
38
+ rover_group_member_query: str | None = Field(
39
+ None, alias="roverGroupMemberQuery", exclude=True
40
+ )
39
41
  rover_group_inclusions: list[Entity] | None = Field(
40
- None, alias="roverGroupInclusions"
42
+ None, alias="roverGroupInclusions", exclude=True
41
43
  )
42
44
  rover_group_exclusions: list[Entity] | None = Field(
43
- None, alias="roverGroupExclusions"
45
+ None, alias="roverGroupExclusions", exclude=True
44
46
  )
45
47
  members: list[Entity] = []
46
- member_of: list[str] | None = Field(None, alias="memberOf")
47
- namespace: str | None = None
48
+ member_of: list[str] | None = Field(None, alias="memberOf", exclude=True)
49
+ namespace: str | None = Field(None, exclude=True)
48
50
 
49
51
  def __eq__(self, other: object) -> bool:
50
52
  if not isinstance(other, Group):
@@ -58,14 +60,3 @@ class Group(BaseModel):
58
60
  and self.notes == other.notes
59
61
  and set(self.members) == set(other.members)
60
62
  )
61
-
62
- class Config:
63
- allow_population_by_field_name = True
64
- # exclude read-only fields in the json/dict dumps
65
- fields = {
66
- "rover_group_member_query": {"exclude": True},
67
- "rover_group_inclusions": {"exclude": True},
68
- "rover_group_exclusions": {"exclude": True},
69
- "member_of": {"exclude": True},
70
- "namespace": {"exclude": True},
71
- }
@@ -1,13 +1,10 @@
1
1
  import datetime
2
2
  import os
3
- import subprocess
4
- import tempfile
5
3
  from collections.abc import Mapping
6
4
  from functools import cache
7
5
  from typing import Any, Self
8
6
 
9
7
  import jinja2
10
- import yaml
11
8
  from github import Github
12
9
  from jinja2.sandbox import SandboxedEnvironment
13
10
  from pydantic import BaseModel
@@ -18,6 +15,7 @@ from reconcile.checkpoint import url_makes_sense
18
15
  from reconcile.github_org import get_default_config
19
16
  from reconcile.utils import gql
20
17
  from reconcile.utils.aws_api import AWSApi
18
+ from reconcile.utils.datetime_util import utc_now
21
19
  from reconcile.utils.github_api import GithubRepositoryApi
22
20
  from reconcile.utils.helpers import flatten
23
21
  from reconcile.utils.jinja2.extensions import B64EncodeExtension, RaiseErrorExtension
@@ -38,7 +36,7 @@ from reconcile.utils.secret_reader import (
38
36
  SecretReader,
39
37
  SecretReaderBase,
40
38
  )
41
- from reconcile.utils.sloth import process_sloth_output
39
+ from reconcile.utils.sloth import generate_sloth_rules
42
40
  from reconcile.utils.vault import SecretFieldNotFoundError
43
41
 
44
42
 
@@ -47,14 +45,11 @@ class Jinja2TemplateError(Exception):
47
45
  super().__init__("error processing jinja2 template: " + str(msg))
48
46
 
49
47
 
50
- class TemplateRenderOptions(BaseModel):
48
+ class TemplateRenderOptions(BaseModel, frozen=True):
51
49
  trim_blocks: bool
52
50
  lstrip_blocks: bool
53
51
  keep_trailing_newline: bool
54
52
 
55
- class Config:
56
- frozen = True
57
-
58
53
  @classmethod
59
54
  def create(
60
55
  cls,
@@ -77,7 +72,7 @@ def compile_jinja2_template(
77
72
  ) -> Any:
78
73
  if not template_render_options:
79
74
  template_render_options = TemplateRenderOptions.create()
80
- env: dict[str, Any] = template_render_options.dict()
75
+ env: dict[str, Any] = template_render_options.model_dump()
81
76
  if extra_curly:
82
77
  env.update({
83
78
  "block_start_string": "{{%",
@@ -192,89 +187,6 @@ def list_s3_objects(
192
187
  )
193
188
 
194
189
 
195
- def sloth_alerts(
196
- service: str,
197
- slo_name: str,
198
- objective: float,
199
- error_query: str,
200
- total_query: str,
201
- version: str = "prometheus/v1",
202
- ) -> str:
203
- """Generate Prometheus rules using sloth: https://sloth.dev
204
-
205
- Args:
206
- service: Service name identifier
207
- slo_name: Name of the SLO
208
- objective: Target percentage (e.g. 99.9)
209
- error_query: Prometheus query for error events
210
- total_query: Prometheus query for total events
211
- version: Spec version (default: "prometheus/v1")
212
-
213
- Returns:
214
- Generated Prometheus rules as YAML string
215
- """
216
- # Build the SLO definition
217
- slo = {
218
- "name": slo_name,
219
- "objective": objective,
220
- "description": f"{slo_name} SLO for {service}",
221
- "sli": {
222
- "events": {
223
- "error_query": error_query.replace("{{window}}", "{{.window}}"),
224
- "total_query": total_query.replace("{{window}}", "{{.window}}"),
225
- }
226
- },
227
- "alerting": {
228
- "name": f"{service.title()}{slo_name.title()}",
229
- "annotations": {
230
- "summary": f"High error rate on '{service}' {slo_name}",
231
- "message": f"High error rate on '{service}' {slo_name}",
232
- },
233
- "page_alert": {
234
- "labels": {
235
- "severity": "critical",
236
- "service": service,
237
- "slo": slo_name,
238
- }
239
- },
240
- "ticket_alert": {
241
- "labels": {
242
- "severity": "medium",
243
- "service": service,
244
- "slo": slo_name,
245
- }
246
- },
247
- },
248
- }
249
-
250
- spec = {
251
- "version": version,
252
- "service": service,
253
- "slos": [slo],
254
- }
255
-
256
- with (
257
- tempfile.NamedTemporaryFile(
258
- encoding="utf-8", mode="w", suffix=".yml"
259
- ) as input_file,
260
- tempfile.NamedTemporaryFile(
261
- encoding="utf-8", mode="w", suffix=".yml"
262
- ) as output_file,
263
- ):
264
- yaml.dump(spec, input_file, allow_unicode=True)
265
- cmd = ["sloth", "generate", "-i", input_file.name, "-o", output_file.name]
266
- try:
267
- subprocess.run(cmd, capture_output=True, check=True, text=True)
268
- except subprocess.CalledProcessError as e:
269
- error_msg = f"{e}"
270
- if e.stdout:
271
- error_msg += f"\nstdout: {e.stdout}"
272
- if e.stderr:
273
- error_msg += f"\nstderr: {e.stderr}"
274
- raise SlothGenerateError(error_msg) from e
275
- return process_sloth_output(output_file.name)
276
-
277
-
278
190
  @retry()
279
191
  def lookup_secret(
280
192
  path: str,
@@ -345,10 +257,8 @@ def process_jinja2_template(
345
257
  "s3": lookup_s3_object,
346
258
  "s3_ls": list_s3_objects,
347
259
  "flatten_dict": flatten,
348
- "yesterday": lambda: (datetime.datetime.now() - datetime.timedelta(1)).strftime(
349
- "%Y-%m-%d"
350
- ),
351
- "sloth_alerts": sloth_alerts,
260
+ "yesterday": lambda: (utc_now() - datetime.timedelta(1)).strftime("%Y-%m-%d"),
261
+ "sloth_alerts": generate_sloth_rules,
352
262
  })
353
263
  if "_template_mocks" in vars:
354
264
  for k, v in vars["_template_mocks"].items():
@@ -381,11 +291,6 @@ def process_extracurlyjinja2_template(
381
291
  )
382
292
 
383
293
 
384
- class SlothGenerateError(Exception):
385
- def __init__(self, msg: Any):
386
- super().__init__("sloth generate failed: " + str(msg))
387
-
388
-
389
294
  class FetchSecretError(Exception):
390
295
  def __init__(self, msg: Any):
391
296
  super().__init__("error fetching secret: " + str(msg))