qontract-reconcile 0.10.2.dev349__py3-none-any.whl → 0.10.2.dev414__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (356) hide show
  1. {qontract_reconcile-0.10.2.dev349.dist-info → qontract_reconcile-0.10.2.dev414.dist-info}/METADATA +12 -11
  2. {qontract_reconcile-0.10.2.dev349.dist-info → qontract_reconcile-0.10.2.dev414.dist-info}/RECORD +356 -350
  3. reconcile/acs_rbac.py +2 -2
  4. reconcile/aus/advanced_upgrade_service.py +15 -12
  5. reconcile/aus/base.py +26 -27
  6. reconcile/aus/cluster_version_data.py +15 -5
  7. reconcile/aus/models.py +1 -1
  8. reconcile/automated_actions/config/integration.py +15 -3
  9. reconcile/aws_account_manager/integration.py +8 -8
  10. reconcile/aws_account_manager/reconciler.py +3 -3
  11. reconcile/aws_ami_cleanup/integration.py +8 -12
  12. reconcile/aws_ami_share.py +69 -62
  13. reconcile/aws_cloudwatch_log_retention/integration.py +155 -126
  14. reconcile/aws_ecr_image_pull_secrets.py +2 -2
  15. reconcile/aws_iam_keys.py +7 -41
  16. reconcile/aws_saml_idp/integration.py +12 -4
  17. reconcile/aws_saml_roles/integration.py +32 -25
  18. reconcile/aws_version_sync/integration.py +6 -12
  19. reconcile/change_owners/bundle.py +3 -3
  20. reconcile/change_owners/change_log_tracking.py +3 -2
  21. reconcile/change_owners/change_owners.py +1 -1
  22. reconcile/change_owners/diff.py +2 -4
  23. reconcile/checkpoint.py +11 -3
  24. reconcile/cli.py +33 -8
  25. reconcile/dashdotdb_dora.py +5 -12
  26. reconcile/dashdotdb_slo.py +1 -1
  27. reconcile/database_access_manager.py +123 -117
  28. reconcile/dynatrace_token_provider/integration.py +1 -1
  29. reconcile/endpoints_discovery/integration.py +4 -1
  30. reconcile/endpoints_discovery/merge_request.py +1 -1
  31. reconcile/endpoints_discovery/merge_request_manager.py +9 -11
  32. reconcile/external_resources/factories.py +5 -12
  33. reconcile/external_resources/integration.py +1 -1
  34. reconcile/external_resources/manager.py +24 -10
  35. reconcile/external_resources/meta.py +0 -1
  36. reconcile/external_resources/metrics.py +1 -1
  37. reconcile/external_resources/model.py +13 -13
  38. reconcile/external_resources/reconciler.py +7 -4
  39. reconcile/external_resources/secrets_sync.py +6 -8
  40. reconcile/external_resources/state.py +60 -17
  41. reconcile/fleet_labeler/integration.py +1 -1
  42. reconcile/gabi_authorized_users.py +8 -5
  43. reconcile/gcp_image_mirror.py +2 -2
  44. reconcile/github_org.py +1 -1
  45. reconcile/github_owners.py +4 -0
  46. reconcile/gitlab_housekeeping.py +13 -15
  47. reconcile/gitlab_members.py +6 -12
  48. reconcile/gitlab_mr_sqs_consumer.py +2 -2
  49. reconcile/gitlab_owners.py +15 -11
  50. reconcile/gitlab_permissions.py +8 -12
  51. reconcile/glitchtip_project_alerts/integration.py +3 -1
  52. reconcile/gql_definitions/acs/acs_instances.py +5 -5
  53. reconcile/gql_definitions/acs/acs_policies.py +5 -5
  54. reconcile/gql_definitions/acs/acs_rbac.py +5 -5
  55. reconcile/gql_definitions/advanced_upgrade_service/aus_clusters.py +5 -5
  56. reconcile/gql_definitions/advanced_upgrade_service/aus_organization.py +5 -5
  57. reconcile/gql_definitions/app_interface_metrics_exporter/onboarding_status.py +5 -5
  58. reconcile/gql_definitions/app_sre_tekton_access_revalidation/roles.py +5 -5
  59. reconcile/gql_definitions/app_sre_tekton_access_revalidation/users.py +5 -5
  60. reconcile/gql_definitions/automated_actions/instance.py +46 -7
  61. reconcile/gql_definitions/aws_account_manager/aws_accounts.py +5 -5
  62. reconcile/gql_definitions/aws_ami_cleanup/aws_accounts.py +15 -5
  63. reconcile/gql_definitions/aws_cloudwatch_log_retention/aws_accounts.py +27 -66
  64. reconcile/gql_definitions/aws_saml_idp/aws_accounts.py +15 -5
  65. reconcile/gql_definitions/aws_saml_roles/aws_accounts.py +15 -5
  66. reconcile/gql_definitions/aws_saml_roles/roles.py +5 -5
  67. reconcile/gql_definitions/aws_version_sync/clusters.py +5 -5
  68. reconcile/gql_definitions/aws_version_sync/namespaces.py +5 -5
  69. reconcile/gql_definitions/change_owners/queries/change_types.py +5 -5
  70. reconcile/gql_definitions/change_owners/queries/self_service_roles.py +5 -5
  71. reconcile/gql_definitions/cluster_auth_rhidp/clusters.py +5 -5
  72. reconcile/gql_definitions/common/alerting_services_settings.py +5 -5
  73. reconcile/gql_definitions/common/app_code_component_repos.py +5 -5
  74. reconcile/gql_definitions/common/app_interface_custom_messages.py +5 -5
  75. reconcile/gql_definitions/common/app_interface_dms_settings.py +5 -5
  76. reconcile/gql_definitions/common/app_interface_repo_settings.py +5 -5
  77. reconcile/gql_definitions/common/app_interface_roles.py +5 -5
  78. reconcile/gql_definitions/common/app_interface_state_settings.py +5 -5
  79. reconcile/gql_definitions/common/app_interface_vault_settings.py +5 -5
  80. reconcile/gql_definitions/common/app_quay_repos_escalation_policies.py +5 -5
  81. reconcile/gql_definitions/common/apps.py +5 -5
  82. reconcile/gql_definitions/common/aws_vpc_requests.py +15 -5
  83. reconcile/gql_definitions/common/aws_vpcs.py +5 -5
  84. reconcile/gql_definitions/common/clusters.py +7 -5
  85. reconcile/gql_definitions/common/clusters_minimal.py +5 -5
  86. reconcile/gql_definitions/common/clusters_with_dms.py +5 -5
  87. reconcile/gql_definitions/common/clusters_with_peering.py +5 -5
  88. reconcile/gql_definitions/common/github_orgs.py +5 -5
  89. reconcile/gql_definitions/common/jira_settings.py +5 -5
  90. reconcile/gql_definitions/common/jiralert_settings.py +5 -5
  91. reconcile/gql_definitions/common/ldap_settings.py +5 -5
  92. reconcile/gql_definitions/common/namespaces.py +5 -5
  93. reconcile/gql_definitions/common/namespaces_minimal.py +7 -5
  94. reconcile/gql_definitions/common/ocm_env_telemeter.py +5 -5
  95. reconcile/gql_definitions/common/ocm_environments.py +5 -5
  96. reconcile/gql_definitions/common/pagerduty_instances.py +5 -5
  97. reconcile/gql_definitions/common/pgp_reencryption_settings.py +5 -5
  98. reconcile/gql_definitions/common/pipeline_providers.py +5 -5
  99. reconcile/gql_definitions/common/quay_instances.py +5 -5
  100. reconcile/gql_definitions/common/quay_orgs.py +5 -5
  101. reconcile/gql_definitions/common/reserved_networks.py +5 -5
  102. reconcile/gql_definitions/common/rhcs_provider_settings.py +5 -5
  103. reconcile/gql_definitions/common/saas_files.py +5 -5
  104. reconcile/gql_definitions/common/saas_target_namespaces.py +5 -5
  105. reconcile/gql_definitions/common/saasherder_settings.py +5 -5
  106. reconcile/gql_definitions/common/slack_workspaces.py +5 -5
  107. reconcile/gql_definitions/common/smtp_client_settings.py +5 -5
  108. reconcile/gql_definitions/common/state_aws_account.py +5 -5
  109. reconcile/gql_definitions/common/users.py +5 -5
  110. reconcile/gql_definitions/common/users_with_paths.py +5 -5
  111. reconcile/gql_definitions/cost_report/app_names.py +5 -5
  112. reconcile/gql_definitions/cost_report/cost_namespaces.py +5 -5
  113. reconcile/gql_definitions/cost_report/settings.py +5 -5
  114. reconcile/gql_definitions/dashdotdb_slo/slo_documents_query.py +5 -5
  115. reconcile/gql_definitions/dynatrace_token_provider/dynatrace_bootstrap_tokens.py +5 -5
  116. reconcile/gql_definitions/dynatrace_token_provider/token_specs.py +5 -5
  117. reconcile/gql_definitions/email_sender/apps.py +5 -5
  118. reconcile/gql_definitions/email_sender/emails.py +5 -5
  119. reconcile/gql_definitions/email_sender/users.py +5 -5
  120. reconcile/gql_definitions/endpoints_discovery/apps.py +5 -5
  121. reconcile/gql_definitions/external_resources/aws_accounts.py +5 -5
  122. reconcile/gql_definitions/external_resources/external_resources_modules.py +5 -5
  123. reconcile/gql_definitions/external_resources/external_resources_namespaces.py +89 -6
  124. reconcile/gql_definitions/external_resources/external_resources_settings.py +7 -5
  125. reconcile/gql_definitions/external_resources/fragments/external_resources_module_overrides.py +5 -5
  126. reconcile/gql_definitions/fleet_labeler/fleet_labels.py +5 -5
  127. reconcile/gql_definitions/fragments/aus_organization.py +5 -5
  128. reconcile/gql_definitions/fragments/aws_account_common.py +7 -5
  129. reconcile/gql_definitions/fragments/aws_account_managed.py +5 -5
  130. reconcile/gql_definitions/fragments/aws_account_sso.py +5 -5
  131. reconcile/gql_definitions/fragments/aws_infra_management_account.py +5 -5
  132. reconcile/gql_definitions/fragments/aws_organization.py +33 -0
  133. reconcile/gql_definitions/fragments/aws_vpc.py +5 -5
  134. reconcile/gql_definitions/fragments/aws_vpc_request.py +7 -5
  135. reconcile/gql_definitions/fragments/container_image_mirror.py +5 -5
  136. reconcile/gql_definitions/fragments/deploy_resources.py +5 -5
  137. reconcile/gql_definitions/fragments/disable.py +5 -5
  138. reconcile/gql_definitions/fragments/email_service.py +5 -5
  139. reconcile/gql_definitions/fragments/email_user.py +5 -5
  140. reconcile/gql_definitions/fragments/jumphost_common_fields.py +5 -5
  141. reconcile/gql_definitions/fragments/membership_source.py +5 -5
  142. reconcile/gql_definitions/fragments/minimal_ocm_organization.py +5 -5
  143. reconcile/gql_definitions/fragments/oc_connection_cluster.py +5 -5
  144. reconcile/gql_definitions/fragments/ocm_environment.py +5 -5
  145. reconcile/gql_definitions/fragments/pipeline_provider_retention.py +5 -5
  146. reconcile/gql_definitions/fragments/prometheus_instance.py +5 -5
  147. reconcile/gql_definitions/fragments/resource_limits_requirements.py +5 -5
  148. reconcile/gql_definitions/fragments/resource_requests_requirements.py +5 -5
  149. reconcile/gql_definitions/fragments/resource_values.py +5 -5
  150. reconcile/gql_definitions/fragments/saas_slo_document.py +5 -5
  151. reconcile/gql_definitions/fragments/saas_target_namespace.py +5 -5
  152. reconcile/gql_definitions/fragments/serviceaccount_token.py +5 -5
  153. reconcile/gql_definitions/fragments/terraform_state.py +5 -5
  154. reconcile/gql_definitions/fragments/upgrade_policy.py +5 -5
  155. reconcile/gql_definitions/fragments/user.py +5 -5
  156. reconcile/gql_definitions/fragments/vault_secret.py +5 -5
  157. reconcile/gql_definitions/gcp/gcp_docker_repos.py +5 -5
  158. reconcile/gql_definitions/gcp/gcp_projects.py +5 -5
  159. reconcile/gql_definitions/gitlab_members/gitlab_instances.py +5 -5
  160. reconcile/gql_definitions/gitlab_members/permissions.py +5 -5
  161. reconcile/gql_definitions/glitchtip/glitchtip_instance.py +5 -5
  162. reconcile/gql_definitions/glitchtip/glitchtip_project.py +5 -5
  163. reconcile/gql_definitions/glitchtip_project_alerts/glitchtip_project.py +5 -5
  164. reconcile/gql_definitions/integrations/integrations.py +5 -5
  165. reconcile/gql_definitions/introspection.json +2137 -1053
  166. reconcile/gql_definitions/jenkins_configs/jenkins_configs.py +5 -5
  167. reconcile/gql_definitions/jenkins_configs/jenkins_instances.py +5 -5
  168. reconcile/gql_definitions/jira/jira_servers.py +5 -5
  169. reconcile/gql_definitions/jira_permissions_validator/jira_boards_for_permissions_validator.py +9 -5
  170. reconcile/gql_definitions/jumphosts/jumphosts.py +5 -5
  171. reconcile/gql_definitions/ldap_groups/roles.py +5 -5
  172. reconcile/gql_definitions/ldap_groups/settings.py +5 -5
  173. reconcile/gql_definitions/maintenance/maintenances.py +5 -5
  174. reconcile/gql_definitions/membershipsources/roles.py +5 -5
  175. reconcile/gql_definitions/ocm_labels/clusters.py +5 -5
  176. reconcile/gql_definitions/ocm_labels/organizations.py +5 -5
  177. reconcile/gql_definitions/openshift_cluster_bots/clusters.py +5 -5
  178. reconcile/gql_definitions/openshift_groups/managed_groups.py +5 -5
  179. reconcile/gql_definitions/openshift_groups/managed_roles.py +5 -5
  180. reconcile/gql_definitions/openshift_serviceaccount_tokens/tokens.py +5 -5
  181. reconcile/gql_definitions/quay_membership/quay_membership.py +5 -5
  182. reconcile/gql_definitions/rhcs/certs.py +5 -5
  183. reconcile/gql_definitions/rhidp/organizations.py +5 -5
  184. reconcile/gql_definitions/service_dependencies/jenkins_instance_fragment.py +5 -5
  185. reconcile/gql_definitions/service_dependencies/service_dependencies.py +5 -5
  186. reconcile/gql_definitions/sharding/aws_accounts.py +5 -5
  187. reconcile/gql_definitions/sharding/ocm_organization.py +5 -5
  188. reconcile/gql_definitions/skupper_network/site_controller_template.py +5 -5
  189. reconcile/gql_definitions/skupper_network/skupper_networks.py +5 -5
  190. reconcile/gql_definitions/slack_usergroups/clusters.py +5 -5
  191. reconcile/gql_definitions/slack_usergroups/permissions.py +5 -5
  192. reconcile/gql_definitions/slack_usergroups/users.py +5 -5
  193. reconcile/gql_definitions/slo_documents/slo_documents.py +5 -5
  194. reconcile/gql_definitions/status_board/status_board.py +5 -5
  195. reconcile/gql_definitions/statuspage/statuspages.py +5 -5
  196. reconcile/gql_definitions/templating/template_collection.py +5 -5
  197. reconcile/gql_definitions/templating/templates.py +5 -5
  198. reconcile/gql_definitions/terraform_cloudflare_dns/app_interface_cloudflare_dns_settings.py +5 -5
  199. reconcile/gql_definitions/terraform_cloudflare_dns/terraform_cloudflare_zones.py +5 -5
  200. reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_accounts.py +5 -5
  201. reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_resources.py +5 -5
  202. reconcile/gql_definitions/terraform_cloudflare_users/app_interface_setting_cloudflare_and_vault.py +5 -5
  203. reconcile/gql_definitions/terraform_cloudflare_users/terraform_cloudflare_roles.py +5 -5
  204. reconcile/gql_definitions/terraform_init/aws_accounts.py +19 -5
  205. reconcile/gql_definitions/terraform_repo/terraform_repo.py +5 -5
  206. reconcile/gql_definitions/terraform_resources/database_access_manager.py +5 -5
  207. reconcile/gql_definitions/terraform_resources/terraform_resources_namespaces.py +38 -6
  208. reconcile/gql_definitions/terraform_tgw_attachments/aws_accounts.py +15 -5
  209. reconcile/gql_definitions/unleash_feature_toggles/feature_toggles.py +5 -5
  210. reconcile/gql_definitions/vault_instances/vault_instances.py +5 -5
  211. reconcile/gql_definitions/vault_policies/vault_policies.py +5 -5
  212. reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator.py +5 -5
  213. reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator_peered_cluster_fragment.py +5 -5
  214. reconcile/integrations_manager.py +3 -3
  215. reconcile/jenkins_worker_fleets.py +10 -8
  216. reconcile/jira_permissions_validator.py +237 -122
  217. reconcile/ldap_groups/integration.py +1 -1
  218. reconcile/ocm/types.py +35 -56
  219. reconcile/ocm_aws_infrastructure_access.py +1 -1
  220. reconcile/ocm_clusters.py +4 -4
  221. reconcile/ocm_labels/integration.py +3 -2
  222. reconcile/ocm_machine_pools.py +23 -23
  223. reconcile/openshift_base.py +53 -2
  224. reconcile/openshift_cluster_bots.py +3 -2
  225. reconcile/openshift_namespace_labels.py +1 -1
  226. reconcile/openshift_namespaces.py +97 -101
  227. reconcile/openshift_resources_base.py +6 -2
  228. reconcile/openshift_rhcs_certs.py +5 -5
  229. reconcile/openshift_rolebindings.py +7 -11
  230. reconcile/openshift_saas_deploy.py +6 -7
  231. reconcile/openshift_saas_deploy_change_tester.py +9 -7
  232. reconcile/openshift_saas_deploy_trigger_cleaner.py +3 -5
  233. reconcile/openshift_serviceaccount_tokens.py +2 -2
  234. reconcile/openshift_upgrade_watcher.py +4 -4
  235. reconcile/oum/labelset.py +5 -3
  236. reconcile/oum/models.py +1 -4
  237. reconcile/prometheus_rules_tester/integration.py +3 -3
  238. reconcile/quay_mirror.py +1 -1
  239. reconcile/queries.py +131 -1
  240. reconcile/rhidp/common.py +3 -5
  241. reconcile/rhidp/sso_client/base.py +1 -1
  242. reconcile/saas_auto_promotions_manager/merge_request_manager/renderer.py +1 -1
  243. reconcile/saas_auto_promotions_manager/subscriber.py +4 -3
  244. reconcile/skupper_network/integration.py +2 -2
  245. reconcile/slack_usergroups.py +35 -14
  246. reconcile/sql_query.py +1 -0
  247. reconcile/status_board.py +6 -6
  248. reconcile/statuspage/atlassian.py +7 -7
  249. reconcile/statuspage/integrations/maintenances.py +4 -3
  250. reconcile/statuspage/page.py +4 -9
  251. reconcile/statuspage/status.py +5 -8
  252. reconcile/templates/rosa-classic-cluster-creation.sh.j2 +4 -0
  253. reconcile/templates/rosa-hcp-cluster-creation.sh.j2 +3 -0
  254. reconcile/templating/lib/rendering.py +3 -3
  255. reconcile/templating/renderer.py +4 -3
  256. reconcile/terraform_aws_route53.py +7 -1
  257. reconcile/terraform_cloudflare_dns.py +3 -3
  258. reconcile/terraform_cloudflare_resources.py +5 -5
  259. reconcile/terraform_cloudflare_users.py +3 -2
  260. reconcile/terraform_init/integration.py +187 -23
  261. reconcile/terraform_repo.py +16 -12
  262. reconcile/terraform_resources.py +17 -7
  263. reconcile/terraform_tgw_attachments.py +27 -19
  264. reconcile/terraform_users.py +7 -0
  265. reconcile/terraform_vpc_peerings.py +14 -3
  266. reconcile/terraform_vpc_resources/integration.py +10 -1
  267. reconcile/typed_queries/aws_account_tags.py +41 -0
  268. reconcile/typed_queries/cost_report/app_names.py +1 -1
  269. reconcile/typed_queries/cost_report/cost_namespaces.py +2 -2
  270. reconcile/typed_queries/saas_files.py +13 -13
  271. reconcile/typed_queries/status_board.py +2 -2
  272. reconcile/unleash_feature_toggles/integration.py +4 -2
  273. reconcile/utils/acs/base.py +6 -3
  274. reconcile/utils/acs/policies.py +2 -2
  275. reconcile/utils/aggregated_list.py +4 -3
  276. reconcile/utils/aws_api.py +51 -54
  277. reconcile/utils/aws_api_typed/api.py +38 -9
  278. reconcile/utils/aws_api_typed/cloudformation.py +149 -0
  279. reconcile/utils/aws_api_typed/logs.py +73 -0
  280. reconcile/utils/aws_api_typed/organization.py +4 -2
  281. reconcile/utils/datetime_util.py +67 -0
  282. reconcile/utils/deadmanssnitch_api.py +1 -1
  283. reconcile/utils/differ.py +2 -3
  284. reconcile/utils/early_exit_cache.py +11 -12
  285. reconcile/utils/expiration.py +7 -3
  286. reconcile/utils/external_resource_spec.py +24 -1
  287. reconcile/utils/filtering.py +1 -1
  288. reconcile/utils/gitlab_api.py +7 -5
  289. reconcile/utils/glitchtip/client.py +6 -2
  290. reconcile/utils/glitchtip/models.py +25 -28
  291. reconcile/utils/gql.py +4 -7
  292. reconcile/utils/helm.py +2 -1
  293. reconcile/utils/helpers.py +1 -1
  294. reconcile/utils/instrumented_wrappers.py +1 -1
  295. reconcile/utils/internal_groups/client.py +2 -2
  296. reconcile/utils/internal_groups/models.py +8 -17
  297. reconcile/utils/jinja2/utils.py +6 -101
  298. reconcile/utils/jira_client.py +82 -63
  299. reconcile/utils/jjb_client.py +9 -12
  300. reconcile/utils/jobcontroller/controller.py +1 -1
  301. reconcile/utils/jobcontroller/models.py +17 -1
  302. reconcile/utils/json.py +70 -0
  303. reconcile/utils/membershipsources/app_interface_resolver.py +4 -2
  304. reconcile/utils/membershipsources/models.py +16 -23
  305. reconcile/utils/membershipsources/resolver.py +4 -2
  306. reconcile/utils/merge_request_manager/merge_request_manager.py +4 -4
  307. reconcile/utils/merge_request_manager/parser.py +6 -6
  308. reconcile/utils/metrics.py +5 -5
  309. reconcile/utils/models.py +304 -82
  310. reconcile/utils/mr/app_interface_reporter.py +2 -2
  311. reconcile/utils/mr/base.py +2 -2
  312. reconcile/utils/mr/notificator.py +3 -3
  313. reconcile/utils/mr/update_access_report_base.py +3 -4
  314. reconcile/utils/mr/user_maintenance.py +3 -2
  315. reconcile/utils/oc.py +118 -97
  316. reconcile/utils/oc_filters.py +3 -3
  317. reconcile/utils/ocm/addons.py +0 -1
  318. reconcile/utils/ocm/base.py +17 -20
  319. reconcile/utils/ocm/cluster_groups.py +1 -1
  320. reconcile/utils/ocm/identity_providers.py +2 -2
  321. reconcile/utils/ocm/labels.py +1 -1
  322. reconcile/utils/ocm/products.py +9 -3
  323. reconcile/utils/ocm/search_filters.py +3 -6
  324. reconcile/utils/ocm/service_log.py +4 -6
  325. reconcile/utils/ocm/sre_capability_labels.py +20 -13
  326. reconcile/utils/openshift_resource.py +10 -5
  327. reconcile/utils/output.py +3 -2
  328. reconcile/utils/pagerduty_api.py +10 -7
  329. reconcile/utils/promotion_state.py +6 -11
  330. reconcile/utils/raw_github_api.py +1 -1
  331. reconcile/utils/rhcsv2_certs.py +1 -4
  332. reconcile/utils/runtime/integration.py +2 -3
  333. reconcile/utils/runtime/runner.py +2 -2
  334. reconcile/utils/saasherder/interfaces.py +13 -20
  335. reconcile/utils/saasherder/models.py +25 -21
  336. reconcile/utils/saasherder/saasherder.py +35 -24
  337. reconcile/utils/slack_api.py +26 -4
  338. reconcile/utils/sloth.py +171 -2
  339. reconcile/utils/sqs_gateway.py +2 -1
  340. reconcile/utils/state.py +2 -1
  341. reconcile/utils/structs.py +1 -1
  342. reconcile/utils/terraform_client.py +5 -4
  343. reconcile/utils/terrascript_aws_client.py +171 -114
  344. reconcile/utils/unleash/server.py +2 -8
  345. reconcile/utils/vault.py +5 -12
  346. reconcile/utils/vcs.py +8 -8
  347. reconcile/vault_replication.py +107 -42
  348. tools/app_interface_reporter.py +4 -4
  349. tools/cli_commands/cost_report/cost_management_api.py +3 -3
  350. tools/cli_commands/cost_report/view.py +7 -6
  351. tools/cli_commands/erv2.py +3 -1
  352. tools/cli_commands/systems_and_tools.py +5 -1
  353. tools/qontract_cli.py +31 -18
  354. tools/template_validation.py +3 -1
  355. {qontract_reconcile-0.10.2.dev349.dist-info → qontract_reconcile-0.10.2.dev414.dist-info}/WHEEL +0 -0
  356. {qontract_reconcile-0.10.2.dev349.dist-info → qontract_reconcile-0.10.2.dev414.dist-info}/entry_points.txt +0 -0
@@ -16,7 +16,7 @@ from collections.abc import (
16
16
  Sequence,
17
17
  )
18
18
  from contextlib import suppress
19
- from datetime import UTC, datetime, timedelta
19
+ from datetime import datetime, timedelta
20
20
  from types import TracebackType
21
21
  from typing import Any
22
22
 
@@ -37,10 +37,12 @@ from sretoolbox.utils import (
37
37
  from reconcile.github_org import get_default_config
38
38
  from reconcile.status import RunningState
39
39
  from reconcile.utils import helm
40
+ from reconcile.utils.datetime_util import utc_now
40
41
  from reconcile.utils.github_api import GithubRepositoryApi
41
42
  from reconcile.utils.gitlab_api import GitLabApi
42
43
  from reconcile.utils.jenkins_api import JenkinsApi, JobBuildState
43
44
  from reconcile.utils.jjb_client import JJB
45
+ from reconcile.utils.json import json_dumps
44
46
  from reconcile.utils.oc import (
45
47
  OCLocal,
46
48
  StatusCodeError,
@@ -763,7 +765,8 @@ class SaasHerder:
763
765
  case "gitlab":
764
766
  if not self.gitlab:
765
767
  raise Exception("gitlab is not initialized")
766
- project = self.gitlab.get_project(url)
768
+ if not (project := self.gitlab.get_project(url)):
769
+ raise Exception(f"Could not find gitlab project for {url}")
767
770
  content = self.gitlab.get_raw_file(
768
771
  project=project,
769
772
  path=path,
@@ -799,7 +802,8 @@ class SaasHerder:
799
802
  case "gitlab":
800
803
  if not self.gitlab:
801
804
  raise Exception("gitlab is not initialized")
802
- project = self.gitlab.get_project(url)
805
+ if not (project := self.gitlab.get_project(url)):
806
+ raise Exception(f"Could not find gitlab project for {url}")
803
807
  dir_contents = self.gitlab.get_directory_contents(
804
808
  project,
805
809
  ref=commit_sha,
@@ -824,7 +828,8 @@ class SaasHerder:
824
828
  case "gitlab":
825
829
  if not self.gitlab:
826
830
  raise Exception("gitlab is not initialized")
827
- project = self.gitlab.get_project(url)
831
+ if not (project := self.gitlab.get_project(url)):
832
+ raise Exception(f"Could not find gitlab project for {url}")
828
833
  commits = project.commits.list(ref_name=ref, per_page=1, page=1)
829
834
  return commits[0].id
830
835
  case _:
@@ -1177,13 +1182,13 @@ class SaasHerder:
1177
1182
  images_list = threaded.run(
1178
1183
  self._collect_images, resources, self.available_thread_pool_size
1179
1184
  )
1180
- images = set(itertools.chain.from_iterable(images_list))
1181
- self.images.update(images)
1182
- if not images:
1185
+ images_set = set(itertools.chain.from_iterable(images_list))
1186
+ self.images.update(images_set)
1187
+ if not images_set:
1183
1188
  return False # no errors
1184
1189
  images = threaded.run(
1185
1190
  self._get_image,
1186
- images,
1191
+ images_set,
1187
1192
  self.available_thread_pool_size,
1188
1193
  image_patterns=spec.image_patterns,
1189
1194
  image_auth=spec.image_auth,
@@ -1248,7 +1253,9 @@ class SaasHerder:
1248
1253
  self.saas_files,
1249
1254
  self.thread_pool_size,
1250
1255
  )
1251
- desired_state_specs = list(itertools.chain.from_iterable(results))
1256
+ desired_state_specs: list[TargetSpec] = list(
1257
+ itertools.chain.from_iterable(results)
1258
+ )
1252
1259
  promotions = threaded.run(
1253
1260
  self.populate_desired_state_saas_file,
1254
1261
  desired_state_specs,
@@ -1865,7 +1872,7 @@ class SaasHerder:
1865
1872
  @staticmethod
1866
1873
  def get_target_config_hash(target_config: Any) -> str:
1867
1874
  m = hashlib.sha256()
1868
- m.update(json.dumps(target_config, sort_keys=True).encode("utf-8"))
1875
+ m.update(json_dumps(target_config).encode("utf-8"))
1869
1876
  digest = m.hexdigest()[:16]
1870
1877
  return digest
1871
1878
 
@@ -1896,21 +1903,23 @@ class SaasHerder:
1896
1903
  name=target.name,
1897
1904
  ref=target.ref,
1898
1905
  promotion=(
1899
- target.promotion.dict(by_alias=True) if target.promotion else None
1906
+ target.promotion.model_dump(by_alias=True) if target.promotion else None
1900
1907
  ),
1901
1908
  secretParameters=(
1902
- [p.dict(by_alias=True) for p in target.secret_parameters]
1909
+ [p.model_dump(by_alias=True) for p in target.secret_parameters]
1903
1910
  if target.secret_parameters
1904
1911
  else None
1905
1912
  ),
1906
1913
  slos=(
1907
- [slo.dict(by_alias=True) for slo in target.slos]
1914
+ [slo.model_dump(by_alias=True) for slo in target.slos]
1908
1915
  if target.slos
1909
1916
  else None
1910
1917
  ),
1911
- upstream=(target.upstream.dict(by_alias=True) if target.upstream else None),
1918
+ upstream=(
1919
+ target.upstream.model_dump(by_alias=True) if target.upstream else None
1920
+ ),
1912
1921
  images=(
1913
- [i.dict(by_alias=True) for i in target.images]
1922
+ [i.model_dump(by_alias=True) for i in target.images]
1914
1923
  if target.images
1915
1924
  else None
1916
1925
  ),
@@ -1921,14 +1930,14 @@ class SaasHerder:
1921
1930
  # before the GQL classes are introduced, the parameters attribute
1922
1931
  # was a json string. Keep it that way to be backwards compatible.
1923
1932
  saas_file_parameters=(
1924
- json.dumps(saas_file.parameters, separators=(",", ":"))
1933
+ json_dumps(saas_file.parameters, compact=True)
1925
1934
  if saas_file.parameters is not None
1926
1935
  else None
1927
1936
  ),
1928
1937
  # before the GQL classes are introduced, the parameters attribute
1929
1938
  # was a json string. Keep it that way to be backwards compatible.
1930
1939
  parameters=(
1931
- json.dumps(target.parameters, separators=(",", ":"))
1940
+ json_dumps(target.parameters, compact=True)
1932
1941
  if target.parameters is not None
1933
1942
  else None
1934
1943
  ),
@@ -1939,23 +1948,23 @@ class SaasHerder:
1939
1948
  # before the GQL classes are introduced, the parameters attribute
1940
1949
  # was a json string. Keep it that way to be backwards compatible.
1941
1950
  rt_parameters=(
1942
- json.dumps(resource_template.parameters, separators=(",", ":"))
1951
+ json_dumps(resource_template.parameters, compact=True)
1943
1952
  if resource_template.parameters is not None
1944
1953
  else None
1945
1954
  ),
1946
1955
  )
1947
1956
  if saas_file.managed_resource_names:
1948
1957
  state_content["saas_file_managed_resource_names"] = [
1949
- m.dict() for m in saas_file.managed_resource_names
1958
+ m.model_dump() for m in saas_file.managed_resource_names
1950
1959
  ]
1951
1960
  # include secret parameters from resource template and saas file
1952
1961
  if resource_template.secret_parameters:
1953
1962
  state_content["rt_secretparameters"] = [
1954
- p.dict() for p in resource_template.secret_parameters
1963
+ p.model_dump() for p in resource_template.secret_parameters
1955
1964
  ]
1956
1965
  if saas_file.secret_parameters:
1957
1966
  state_content["saas_file_secretparameters"] = [
1958
- p.dict() for p in saas_file.secret_parameters
1967
+ p.model_dump() for p in saas_file.secret_parameters
1959
1968
  ]
1960
1969
  return state_content
1961
1970
 
@@ -2024,7 +2033,7 @@ class SaasHerder:
2024
2033
  if promotion.commit_sha in self.hotfix_versions.get(promotion.url, set()):
2025
2034
  return True
2026
2035
 
2027
- now = datetime.now(UTC)
2036
+ now = utc_now()
2028
2037
  passed_soak_days = timedelta(days=0)
2029
2038
 
2030
2039
  for channel in promotion.subscribe:
@@ -2126,7 +2135,7 @@ class SaasHerder:
2126
2135
  if not (self.state and self._promotion_state):
2127
2136
  raise Exception("state is not initialized")
2128
2137
 
2129
- now = datetime.now(UTC)
2138
+ now = utc_now()
2130
2139
  for promotion in self.promotions:
2131
2140
  if promotion is None:
2132
2141
  continue
@@ -2238,7 +2247,9 @@ class SaasHerder:
2238
2247
  for rt in saas_file.resource_templates:
2239
2248
  for target in rt.targets:
2240
2249
  template_vars = {
2241
- "resource": {"namespace": target.namespace.dict(by_alias=True)}
2250
+ "resource": {
2251
+ "namespace": target.namespace.model_dump(by_alias=True)
2252
+ }
2242
2253
  }
2243
2254
  if target.parameters:
2244
2255
  for param in target.parameters:
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import json
4
4
  import logging
5
+ import os
5
6
  from typing import (
6
7
  TYPE_CHECKING,
7
8
  Any,
@@ -26,6 +27,23 @@ if TYPE_CHECKING:
26
27
  MAX_RETRIES = 5
27
28
  TIMEOUT = 30
28
29
 
30
+ # Slack API base URLs for different workspace types
31
+ SLACK_API_BASE_URL = "https://slack.com/api/"
32
+ SLACK_GOV_API_BASE_URL = "https://slack-gov.com/api/"
33
+
34
+
35
+ def is_gov_slack_workspace() -> bool:
36
+ """
37
+ Determine if a workspace is a government Slack workspace.
38
+
39
+ :return: True if it's a gov-slack workspace, False otherwise
40
+ """
41
+ # Check GOV_SLACK environment variable from OpenShift YAML configuration
42
+ # If not set, defaults to False (regular Slack)
43
+ gov_slack_env = os.getenv("GOV_SLACK", "false")
44
+
45
+ return gov_slack_env.lower() == "true"
46
+
29
47
 
30
48
  class UserNotFoundError(Exception):
31
49
  pass
@@ -53,14 +71,14 @@ class HasClientGlobalConfig(Protocol):
53
71
  max_retries: int | None
54
72
  timeout: int | None
55
73
 
56
- def dict(self) -> dict[str, int | None]: ...
74
+ def model_dump(self) -> dict[str, int | None]: ...
57
75
 
58
76
 
59
77
  class HasClientMethodConfig(Protocol):
60
78
  name: str
61
79
  args: Any
62
80
 
63
- def dict(self) -> dict[str, str]: ...
81
+ def model_dump(self) -> dict[str, str]: ...
64
82
 
65
83
 
66
84
  class HasClientConfig(Protocol):
@@ -165,7 +183,6 @@ class SlackApi:
165
183
  api_config: SlackApiConfig | None = None,
166
184
  init_usergroups: bool = True,
167
185
  channel: str | None = None,
168
- slack_url: str | None = None,
169
186
  **chat_kwargs: Any,
170
187
  ) -> None:
171
188
  """
@@ -187,10 +204,15 @@ class SlackApi:
187
204
  else:
188
205
  self.config = SlackApiConfig()
189
206
 
207
+ # Determine the appropriate Slack API base URL based on GOV_SLACK environment variable
208
+ base_url = (
209
+ SLACK_GOV_API_BASE_URL if is_gov_slack_workspace() else SLACK_API_BASE_URL
210
+ )
211
+
190
212
  self._sc = WebClient(
191
213
  token=token,
192
214
  timeout=self.config.timeout,
193
- base_url=slack_url or WebClient.BASE_URL,
215
+ base_url=base_url,
194
216
  )
195
217
  self._configure_client_retry()
196
218
 
reconcile/utils/sloth.py CHANGED
@@ -1,5 +1,9 @@
1
+ import subprocess
2
+ import tempfile
1
3
  from io import StringIO
2
- from typing import NotRequired, TypedDict
4
+ from typing import Any, NotRequired, TypedDict
5
+
6
+ import yaml
3
7
 
4
8
  from reconcile.utils.ruamel import create_ruamel_instance
5
9
 
@@ -21,6 +25,44 @@ class PrometheusRuleSpec(TypedDict):
21
25
  groups: list[PrometheusRuleGroup]
22
26
 
23
27
 
28
+ class SLOParametersDict(TypedDict):
29
+ window: str
30
+
31
+
32
+ class SLO(TypedDict):
33
+ name: str
34
+ SLIType: str
35
+ SLISpecification: str
36
+ SLOTarget: float
37
+ SLOTargetUnit: str
38
+ SLOParameters: SLOParametersDict
39
+ SLODetails: str
40
+ dashboard: str
41
+ expr: str
42
+ SLIErrorQuery: NotRequired[str]
43
+ SLITotalQuery: NotRequired[str]
44
+
45
+
46
+ class App(TypedDict):
47
+ name: str
48
+
49
+
50
+ class SLODocument(TypedDict):
51
+ name: str
52
+ app: App
53
+ slos: NotRequired[list[SLO]]
54
+
55
+
56
+ class SlothGenerateError(Exception):
57
+ def __init__(self, msg: Any):
58
+ super().__init__("sloth generate failed: " + str(msg))
59
+
60
+
61
+ class SlothInputError(Exception):
62
+ def __init__(self, msg: Any):
63
+ super().__init__("sloth input validation failed: " + str(msg))
64
+
65
+
24
66
  def process_sloth_output(output_file_path: str) -> str:
25
67
  ruamel_instance = create_ruamel_instance()
26
68
  with open(output_file_path, encoding="utf-8") as f:
@@ -49,7 +91,134 @@ def process_sloth_output(output_file_path: str) -> str:
49
91
  rule["annotations"] = annotations
50
92
  else:
51
93
  rule.pop("annotations", None)
52
-
53
94
  with StringIO() as s:
54
95
  ruamel_instance.dump(data, s)
55
96
  return s.getvalue()
97
+
98
+
99
+ def run_sloth(spec: dict[str, Any]) -> str:
100
+ with (
101
+ tempfile.NamedTemporaryFile(
102
+ encoding="utf-8", mode="w", suffix=".yml"
103
+ ) as input_file,
104
+ tempfile.NamedTemporaryFile(
105
+ encoding="utf-8", mode="w", suffix=".yml"
106
+ ) as output_file,
107
+ ):
108
+ yaml.dump(spec, input_file, allow_unicode=True)
109
+ cmd = ["sloth", "generate", "-i", input_file.name, "-o", output_file.name]
110
+ try:
111
+ subprocess.run(cmd, capture_output=True, check=True, text=True)
112
+ except subprocess.CalledProcessError as e:
113
+ error_msg = f"{e}"
114
+ if e.stdout:
115
+ error_msg += f"\nstdout: {e.stdout}"
116
+ if e.stderr:
117
+ error_msg += f"\nstderr: {e.stderr}"
118
+ raise SlothGenerateError(error_msg) from e
119
+ return process_sloth_output(output_file.name)
120
+
121
+
122
+ def get_slo_target(slo: SLO) -> float:
123
+ """
124
+ Ensure SLO target unit aligns with format expected by sloth for 'Objective' attribute
125
+ https://pkg.go.dev/github.com/slok/sloth/pkg/prometheus/api/v1#section-readme
126
+ """
127
+ val = float(slo["SLOTarget"])
128
+ return val * (100.0 if slo.get("SLOTargetUnit") == "percent_0_1" else 1.0)
129
+
130
+
131
+ def generate_sloth_rules(
132
+ slo_document: SLODocument,
133
+ version: str = "prometheus/v1",
134
+ ) -> str:
135
+ """Generate Prometheus rules for an slo_document_v1 using sloth
136
+
137
+ Args:
138
+ slo_document query:
139
+ {
140
+ slo_docs: slo_document_v1(filter: {name: "foo"}) {
141
+ name
142
+ app {
143
+ name
144
+ }
145
+ slos {
146
+ name
147
+ SLIType
148
+ SLOTargetUnit
149
+ SLOParameters {
150
+ window
151
+ }
152
+ expr
153
+ SLOTarget
154
+ SLIErrorQuery
155
+ SLITotalQuery
156
+ SLODetails
157
+ dashboard
158
+ }
159
+ }
160
+ }
161
+ version: Spec version (default: "prometheus/v1")
162
+
163
+ Returns:
164
+ Generated Prometheus rules as YAML string
165
+ """
166
+ if not slo_document.get("slos"):
167
+ raise SlothInputError("SLO document has no SLOs defined")
168
+
169
+ service = slo_document["app"]["name"]
170
+ # only process SLOs that have both error and total queries defined
171
+ slo_input = [
172
+ {
173
+ "name": slo["name"],
174
+ "objective": get_slo_target(slo),
175
+ "description": f"{slo['name']} SLO for {service}",
176
+ "sli": {
177
+ "events": {
178
+ "error_query": slo["SLIErrorQuery"].replace(
179
+ "{{window}}", "{{.window}}"
180
+ ),
181
+ "total_query": slo["SLITotalQuery"].replace(
182
+ "{{window}}", "{{.window}}"
183
+ ),
184
+ }
185
+ },
186
+ "alerting": {
187
+ "name": f"{service.title()}{slo['name'].title()}",
188
+ "annotations": {
189
+ "summary": f"High error rate on {service} {slo['name']}",
190
+ "message": f"High error rate on {service} {slo['name']}",
191
+ "runbook": slo["SLODetails"],
192
+ "dashboard": slo["dashboard"],
193
+ },
194
+ "page_alert": {
195
+ "labels": {
196
+ "severity": "critical",
197
+ "service": service,
198
+ "slo": slo["name"],
199
+ }
200
+ },
201
+ "ticket_alert": {
202
+ "labels": {
203
+ "severity": "high",
204
+ "service": service,
205
+ "slo": slo["name"],
206
+ }
207
+ },
208
+ },
209
+ }
210
+ for slo in slo_document["slos"]
211
+ if slo.get("SLIErrorQuery") and slo.get("SLITotalQuery")
212
+ ]
213
+
214
+ if not slo_input:
215
+ raise SlothInputError(
216
+ "No SLOs found with both SLIErrorQuery and SLITotalQuery defined"
217
+ )
218
+
219
+ spec = {
220
+ "version": version,
221
+ "service": service,
222
+ "slos": slo_input,
223
+ }
224
+ return run_sloth(spec)
@@ -4,6 +4,7 @@ from collections.abc import Iterable, Mapping
4
4
  from typing import Any, Self
5
5
 
6
6
  from reconcile.utils.aws_api import AWSApi
7
+ from reconcile.utils.json import json_dumps
7
8
  from reconcile.utils.secret_reader import SecretReader
8
9
 
9
10
 
@@ -54,7 +55,7 @@ class SQSGateway:
54
55
  return queue_account_name[0]
55
56
 
56
57
  def send_message(self, body: Mapping[str, Any]) -> None:
57
- self.sqs.send_message(QueueUrl=self.queue_url, MessageBody=json.dumps(body))
58
+ self.sqs.send_message(QueueUrl=self.queue_url, MessageBody=json_dumps(body))
58
59
 
59
60
  def receive_messages(
60
61
  self,
reconcile/utils/state.py CHANGED
@@ -28,6 +28,7 @@ from reconcile.typed_queries.app_interface_vault_settings import (
28
28
  )
29
29
  from reconcile.typed_queries.get_state_aws_account import get_state_aws_account
30
30
  from reconcile.utils.aws_api import aws_config_file_path
31
+ from reconcile.utils.json import json_dumps
31
32
  from reconcile.utils.secret_reader import (
32
33
  SecretReaderBase,
33
34
  create_secret_reader,
@@ -355,7 +356,7 @@ class State:
355
356
  self.client.put_object(
356
357
  Bucket=self.bucket,
357
358
  Key=f"{self.state_path}/{key}",
358
- Body=json.dumps(value),
359
+ Body=json_dumps(value),
359
360
  Metadata=metadata or {},
360
361
  )
361
362
 
@@ -1,4 +1,4 @@
1
- from pydantic.dataclasses import dataclass
1
+ from dataclasses import dataclass
2
2
 
3
3
 
4
4
  @dataclass
@@ -36,6 +36,7 @@ from reconcile.typed_queries.app_interface_custom_messages import (
36
36
  )
37
37
  from reconcile.utils.aws_api import AWSApi
38
38
  from reconcile.utils.aws_helper import get_region_from_availability_zone
39
+ from reconcile.utils.datetime_util import ensure_utc, utc_now
39
40
  from reconcile.utils.external_resource_spec import (
40
41
  ExternalResourceSpec,
41
42
  ExternalResourceSpecInventory,
@@ -222,7 +223,7 @@ class TerraformClient:
222
223
  if disable_deletions_detected:
223
224
  raise RuntimeError("Terraform plan has disabled deletions detected")
224
225
 
225
- @retry(no_retry_exceptions=RdsUpgradeValidationError)
226
+ @retry(no_retry_exceptions=(RdsUpgradeValidationError,))
226
227
  def terraform_plan(
227
228
  self, spec: TerraformSpec, enable_deletion: bool
228
229
  ) -> tuple[bool, list[AccountUser], bool]:
@@ -419,11 +420,11 @@ class TerraformClient:
419
420
  deletion_approvals = account.get("deletionApprovals")
420
421
  if not deletion_approvals:
421
422
  return False
422
- now = datetime.utcnow()
423
+ now = utc_now()
423
424
  for da in deletion_approvals:
424
425
  try:
425
- expiration = datetime.strptime(
426
- da["expiration"], DATE_FORMAT
426
+ expiration = ensure_utc(
427
+ datetime.strptime(da["expiration"], DATE_FORMAT) # noqa: DTZ007
427
428
  ) + timedelta(days=1)
428
429
  except ValueError:
429
430
  raise DeletionApprovalExpirationValueError(