qontract-reconcile 0.10.2.dev310__py3-none-any.whl → 0.10.2.dev439__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.

Potentially problematic release.


This version of qontract-reconcile might be problematic. Click here for more details.

Files changed (400) hide show
  1. {qontract_reconcile-0.10.2.dev310.dist-info → qontract_reconcile-0.10.2.dev439.dist-info}/METADATA +13 -12
  2. {qontract_reconcile-0.10.2.dev310.dist-info → qontract_reconcile-0.10.2.dev439.dist-info}/RECORD +396 -391
  3. reconcile/acs_rbac.py +2 -2
  4. reconcile/aus/advanced_upgrade_service.py +18 -12
  5. reconcile/aus/base.py +134 -32
  6. reconcile/aus/cluster_version_data.py +15 -5
  7. reconcile/aus/models.py +3 -1
  8. reconcile/aus/ocm_addons_upgrade_scheduler_org.py +1 -0
  9. reconcile/aus/ocm_upgrade_scheduler.py +8 -1
  10. reconcile/aus/ocm_upgrade_scheduler_org.py +20 -5
  11. reconcile/aus/version_gates/sts_version_gate_handler.py +54 -1
  12. reconcile/automated_actions/config/integration.py +16 -4
  13. reconcile/aws_account_manager/integration.py +8 -8
  14. reconcile/aws_account_manager/reconciler.py +3 -3
  15. reconcile/aws_ami_cleanup/integration.py +8 -12
  16. reconcile/aws_ami_share.py +69 -62
  17. reconcile/aws_cloudwatch_log_retention/integration.py +155 -126
  18. reconcile/aws_ecr_image_pull_secrets.py +5 -5
  19. reconcile/aws_iam_keys.py +1 -0
  20. reconcile/aws_saml_idp/integration.py +12 -4
  21. reconcile/aws_saml_roles/integration.py +32 -25
  22. reconcile/aws_version_sync/integration.py +125 -84
  23. reconcile/change_owners/bundle.py +3 -3
  24. reconcile/change_owners/change_log_tracking.py +3 -2
  25. reconcile/change_owners/change_owners.py +1 -1
  26. reconcile/change_owners/diff.py +2 -4
  27. reconcile/checkpoint.py +12 -4
  28. reconcile/cli.py +111 -18
  29. reconcile/cluster_deployment_mapper.py +2 -3
  30. reconcile/dashdotdb_dora.py +5 -12
  31. reconcile/dashdotdb_slo.py +1 -1
  32. reconcile/database_access_manager.py +125 -121
  33. reconcile/deadmanssnitch.py +1 -5
  34. reconcile/dynatrace_token_provider/integration.py +1 -1
  35. reconcile/endpoints_discovery/integration.py +4 -1
  36. reconcile/endpoints_discovery/merge_request.py +1 -1
  37. reconcile/endpoints_discovery/merge_request_manager.py +9 -11
  38. reconcile/external_resources/factories.py +5 -12
  39. reconcile/external_resources/integration.py +1 -1
  40. reconcile/external_resources/manager.py +8 -5
  41. reconcile/external_resources/meta.py +0 -1
  42. reconcile/external_resources/metrics.py +1 -1
  43. reconcile/external_resources/model.py +20 -20
  44. reconcile/external_resources/reconciler.py +7 -4
  45. reconcile/external_resources/secrets_sync.py +10 -14
  46. reconcile/external_resources/state.py +26 -16
  47. reconcile/fleet_labeler/integration.py +1 -1
  48. reconcile/gabi_authorized_users.py +8 -5
  49. reconcile/gcp_image_mirror.py +2 -2
  50. reconcile/github_org.py +1 -1
  51. reconcile/github_owners.py +4 -0
  52. reconcile/gitlab_housekeeping.py +13 -15
  53. reconcile/gitlab_members.py +6 -12
  54. reconcile/gitlab_mr_sqs_consumer.py +2 -2
  55. reconcile/gitlab_owners.py +15 -11
  56. reconcile/gitlab_permissions.py +8 -12
  57. reconcile/glitchtip_project_alerts/integration.py +3 -1
  58. reconcile/gql_definitions/acs/acs_instances.py +10 -10
  59. reconcile/gql_definitions/acs/acs_policies.py +5 -5
  60. reconcile/gql_definitions/acs/acs_rbac.py +6 -6
  61. reconcile/gql_definitions/advanced_upgrade_service/aus_clusters.py +32 -32
  62. reconcile/gql_definitions/advanced_upgrade_service/aus_organization.py +26 -26
  63. reconcile/gql_definitions/app_interface_metrics_exporter/onboarding_status.py +6 -7
  64. reconcile/gql_definitions/app_sre_tekton_access_revalidation/roles.py +5 -5
  65. reconcile/gql_definitions/app_sre_tekton_access_revalidation/users.py +5 -5
  66. reconcile/gql_definitions/automated_actions/instance.py +51 -12
  67. reconcile/gql_definitions/aws_account_manager/aws_accounts.py +11 -11
  68. reconcile/gql_definitions/aws_ami_cleanup/aws_accounts.py +20 -10
  69. reconcile/gql_definitions/aws_cloudwatch_log_retention/aws_accounts.py +28 -68
  70. reconcile/gql_definitions/aws_saml_idp/aws_accounts.py +20 -10
  71. reconcile/gql_definitions/aws_saml_roles/aws_accounts.py +20 -10
  72. reconcile/gql_definitions/aws_saml_roles/roles.py +5 -5
  73. reconcile/gql_definitions/aws_version_sync/clusters.py +10 -10
  74. reconcile/gql_definitions/aws_version_sync/namespaces.py +5 -5
  75. reconcile/gql_definitions/change_owners/queries/change_types.py +5 -5
  76. reconcile/gql_definitions/change_owners/queries/self_service_roles.py +9 -9
  77. reconcile/gql_definitions/cluster_auth_rhidp/clusters.py +18 -18
  78. reconcile/gql_definitions/common/alerting_services_settings.py +9 -9
  79. reconcile/gql_definitions/common/app_code_component_repos.py +5 -5
  80. reconcile/gql_definitions/common/app_interface_custom_messages.py +5 -5
  81. reconcile/gql_definitions/common/app_interface_dms_settings.py +5 -5
  82. reconcile/gql_definitions/common/app_interface_repo_settings.py +5 -5
  83. reconcile/gql_definitions/common/app_interface_roles.py +120 -0
  84. reconcile/gql_definitions/common/app_interface_state_settings.py +10 -10
  85. reconcile/gql_definitions/common/app_interface_vault_settings.py +5 -5
  86. reconcile/gql_definitions/common/app_quay_repos_escalation_policies.py +5 -5
  87. reconcile/gql_definitions/common/apps.py +5 -5
  88. reconcile/gql_definitions/common/aws_vpc_requests.py +22 -9
  89. reconcile/gql_definitions/common/aws_vpcs.py +11 -11
  90. reconcile/gql_definitions/common/clusters.py +37 -35
  91. reconcile/gql_definitions/common/clusters_minimal.py +14 -14
  92. reconcile/gql_definitions/common/clusters_with_dms.py +6 -6
  93. reconcile/gql_definitions/common/clusters_with_peering.py +29 -30
  94. reconcile/gql_definitions/common/github_orgs.py +10 -10
  95. reconcile/gql_definitions/common/jira_settings.py +10 -10
  96. reconcile/gql_definitions/common/jiralert_settings.py +5 -5
  97. reconcile/gql_definitions/common/ldap_settings.py +5 -5
  98. reconcile/gql_definitions/common/namespaces.py +42 -44
  99. reconcile/gql_definitions/common/namespaces_minimal.py +15 -13
  100. reconcile/gql_definitions/common/ocm_env_telemeter.py +12 -12
  101. reconcile/gql_definitions/common/ocm_environments.py +19 -19
  102. reconcile/gql_definitions/common/pagerduty_instances.py +9 -9
  103. reconcile/gql_definitions/common/pgp_reencryption_settings.py +6 -6
  104. reconcile/gql_definitions/common/pipeline_providers.py +29 -29
  105. reconcile/gql_definitions/common/quay_instances.py +5 -5
  106. reconcile/gql_definitions/common/quay_orgs.py +5 -5
  107. reconcile/gql_definitions/common/reserved_networks.py +5 -5
  108. reconcile/gql_definitions/common/rhcs_provider_settings.py +5 -5
  109. reconcile/gql_definitions/common/saas_files.py +44 -44
  110. reconcile/gql_definitions/common/saas_target_namespaces.py +10 -10
  111. reconcile/gql_definitions/common/saasherder_settings.py +5 -5
  112. reconcile/gql_definitions/common/slack_workspaces.py +5 -5
  113. reconcile/gql_definitions/common/smtp_client_settings.py +19 -19
  114. reconcile/gql_definitions/common/state_aws_account.py +7 -8
  115. reconcile/gql_definitions/common/users.py +5 -5
  116. reconcile/gql_definitions/common/users_with_paths.py +5 -5
  117. reconcile/gql_definitions/cost_report/app_names.py +5 -5
  118. reconcile/gql_definitions/cost_report/cost_namespaces.py +5 -5
  119. reconcile/gql_definitions/cost_report/settings.py +9 -9
  120. reconcile/gql_definitions/dashdotdb_slo/slo_documents_query.py +43 -43
  121. reconcile/gql_definitions/dynatrace_token_provider/dynatrace_bootstrap_tokens.py +10 -10
  122. reconcile/gql_definitions/dynatrace_token_provider/token_specs.py +5 -5
  123. reconcile/gql_definitions/email_sender/apps.py +5 -5
  124. reconcile/gql_definitions/email_sender/emails.py +8 -8
  125. reconcile/gql_definitions/email_sender/users.py +6 -6
  126. reconcile/gql_definitions/endpoints_discovery/apps.py +10 -10
  127. reconcile/gql_definitions/external_resources/aws_accounts.py +9 -9
  128. reconcile/gql_definitions/external_resources/external_resources_modules.py +23 -23
  129. reconcile/gql_definitions/external_resources/external_resources_namespaces.py +494 -410
  130. reconcile/gql_definitions/external_resources/external_resources_settings.py +28 -26
  131. reconcile/gql_definitions/external_resources/fragments/external_resources_module_overrides.py +5 -5
  132. reconcile/gql_definitions/fleet_labeler/fleet_labels.py +40 -40
  133. reconcile/gql_definitions/fragments/aus_organization.py +5 -5
  134. reconcile/gql_definitions/fragments/aws_account_common.py +7 -5
  135. reconcile/gql_definitions/fragments/aws_account_managed.py +5 -5
  136. reconcile/gql_definitions/fragments/aws_account_sso.py +5 -5
  137. reconcile/gql_definitions/fragments/aws_infra_management_account.py +5 -5
  138. reconcile/gql_definitions/fragments/{aws_vpc_request_subnet.py → aws_organization.py} +12 -8
  139. reconcile/gql_definitions/fragments/aws_vpc.py +5 -5
  140. reconcile/gql_definitions/fragments/aws_vpc_request.py +12 -5
  141. reconcile/gql_definitions/fragments/container_image_mirror.py +5 -5
  142. reconcile/gql_definitions/fragments/deploy_resources.py +5 -5
  143. reconcile/gql_definitions/fragments/disable.py +5 -5
  144. reconcile/gql_definitions/fragments/email_service.py +5 -5
  145. reconcile/gql_definitions/fragments/email_user.py +5 -5
  146. reconcile/gql_definitions/fragments/jumphost_common_fields.py +5 -5
  147. reconcile/gql_definitions/fragments/membership_source.py +5 -5
  148. reconcile/gql_definitions/fragments/minimal_ocm_organization.py +5 -5
  149. reconcile/gql_definitions/fragments/oc_connection_cluster.py +5 -5
  150. reconcile/gql_definitions/fragments/ocm_environment.py +5 -5
  151. reconcile/gql_definitions/fragments/pipeline_provider_retention.py +5 -5
  152. reconcile/gql_definitions/fragments/prometheus_instance.py +5 -5
  153. reconcile/gql_definitions/fragments/resource_limits_requirements.py +5 -5
  154. reconcile/gql_definitions/fragments/resource_requests_requirements.py +5 -5
  155. reconcile/gql_definitions/fragments/resource_values.py +5 -5
  156. reconcile/gql_definitions/fragments/saas_slo_document.py +5 -5
  157. reconcile/gql_definitions/fragments/saas_target_namespace.py +5 -5
  158. reconcile/gql_definitions/fragments/serviceaccount_token.py +5 -5
  159. reconcile/gql_definitions/fragments/terraform_state.py +5 -5
  160. reconcile/gql_definitions/fragments/upgrade_policy.py +5 -5
  161. reconcile/gql_definitions/fragments/user.py +5 -5
  162. reconcile/gql_definitions/fragments/vault_secret.py +5 -5
  163. reconcile/gql_definitions/gcp/gcp_docker_repos.py +9 -9
  164. reconcile/gql_definitions/gcp/gcp_projects.py +9 -9
  165. reconcile/gql_definitions/gitlab_members/gitlab_instances.py +9 -9
  166. reconcile/gql_definitions/gitlab_members/permissions.py +9 -9
  167. reconcile/gql_definitions/glitchtip/glitchtip_instance.py +9 -9
  168. reconcile/gql_definitions/glitchtip/glitchtip_project.py +11 -11
  169. reconcile/gql_definitions/glitchtip_project_alerts/glitchtip_project.py +9 -9
  170. reconcile/gql_definitions/integrations/integrations.py +48 -51
  171. reconcile/gql_definitions/introspection.json +3510 -1865
  172. reconcile/gql_definitions/jenkins_configs/jenkins_configs.py +11 -11
  173. reconcile/gql_definitions/jenkins_configs/jenkins_instances.py +10 -10
  174. reconcile/gql_definitions/jira/jira_servers.py +5 -5
  175. reconcile/gql_definitions/jira_permissions_validator/jira_boards_for_permissions_validator.py +14 -10
  176. reconcile/gql_definitions/jumphosts/jumphosts.py +13 -13
  177. reconcile/gql_definitions/ldap_groups/roles.py +5 -5
  178. reconcile/gql_definitions/ldap_groups/settings.py +9 -9
  179. reconcile/gql_definitions/maintenance/maintenances.py +5 -5
  180. reconcile/gql_definitions/membershipsources/roles.py +5 -5
  181. reconcile/gql_definitions/ocm_labels/clusters.py +18 -19
  182. reconcile/gql_definitions/ocm_labels/organizations.py +5 -5
  183. reconcile/gql_definitions/openshift_cluster_bots/clusters.py +22 -22
  184. reconcile/gql_definitions/openshift_groups/managed_groups.py +5 -5
  185. reconcile/gql_definitions/openshift_groups/managed_roles.py +6 -6
  186. reconcile/gql_definitions/openshift_serviceaccount_tokens/tokens.py +10 -10
  187. reconcile/gql_definitions/quay_membership/quay_membership.py +6 -6
  188. reconcile/gql_definitions/rhcs/certs.py +33 -87
  189. reconcile/gql_definitions/rhcs/openshift_resource_rhcs_cert.py +43 -0
  190. reconcile/gql_definitions/rhidp/organizations.py +18 -18
  191. reconcile/gql_definitions/service_dependencies/jenkins_instance_fragment.py +5 -5
  192. reconcile/gql_definitions/service_dependencies/service_dependencies.py +8 -8
  193. reconcile/gql_definitions/sharding/aws_accounts.py +10 -10
  194. reconcile/gql_definitions/sharding/ocm_organization.py +8 -8
  195. reconcile/gql_definitions/skupper_network/site_controller_template.py +5 -5
  196. reconcile/gql_definitions/skupper_network/skupper_networks.py +10 -10
  197. reconcile/gql_definitions/slack_usergroups/clusters.py +5 -5
  198. reconcile/gql_definitions/slack_usergroups/permissions.py +9 -9
  199. reconcile/gql_definitions/slack_usergroups/users.py +5 -5
  200. reconcile/gql_definitions/slo_documents/slo_documents.py +5 -5
  201. reconcile/gql_definitions/status_board/status_board.py +6 -7
  202. reconcile/gql_definitions/statuspage/statuspages.py +9 -9
  203. reconcile/gql_definitions/templating/template_collection.py +5 -5
  204. reconcile/gql_definitions/templating/templates.py +5 -5
  205. reconcile/gql_definitions/terraform_cloudflare_dns/app_interface_cloudflare_dns_settings.py +6 -6
  206. reconcile/gql_definitions/terraform_cloudflare_dns/terraform_cloudflare_zones.py +11 -11
  207. reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_accounts.py +11 -11
  208. reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_resources.py +20 -25
  209. reconcile/gql_definitions/terraform_cloudflare_users/app_interface_setting_cloudflare_and_vault.py +6 -6
  210. reconcile/gql_definitions/terraform_cloudflare_users/terraform_cloudflare_roles.py +12 -12
  211. reconcile/gql_definitions/terraform_init/aws_accounts.py +23 -9
  212. reconcile/gql_definitions/terraform_repo/terraform_repo.py +9 -9
  213. reconcile/gql_definitions/terraform_resources/database_access_manager.py +5 -5
  214. reconcile/gql_definitions/terraform_resources/terraform_resources_namespaces.py +450 -402
  215. reconcile/gql_definitions/terraform_tgw_attachments/aws_accounts.py +23 -17
  216. reconcile/gql_definitions/unleash_feature_toggles/feature_toggles.py +9 -9
  217. reconcile/gql_definitions/vault_instances/vault_instances.py +61 -61
  218. reconcile/gql_definitions/vault_policies/vault_policies.py +11 -11
  219. reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator.py +8 -8
  220. reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator_peered_cluster_fragment.py +5 -5
  221. reconcile/integrations_manager.py +3 -3
  222. reconcile/jenkins_job_builder.py +1 -1
  223. reconcile/jenkins_worker_fleets.py +80 -11
  224. reconcile/jira_permissions_validator.py +237 -122
  225. reconcile/ldap_groups/integration.py +1 -1
  226. reconcile/ocm/types.py +35 -56
  227. reconcile/ocm_aws_infrastructure_access.py +1 -1
  228. reconcile/ocm_clusters.py +4 -4
  229. reconcile/ocm_labels/integration.py +3 -2
  230. reconcile/ocm_machine_pools.py +33 -27
  231. reconcile/openshift_base.py +122 -10
  232. reconcile/openshift_cluster_bots.py +5 -5
  233. reconcile/openshift_groups.py +5 -0
  234. reconcile/openshift_limitranges.py +1 -1
  235. reconcile/openshift_namespace_labels.py +1 -1
  236. reconcile/openshift_namespaces.py +97 -101
  237. reconcile/openshift_resources_base.py +10 -5
  238. reconcile/openshift_rhcs_certs.py +77 -40
  239. reconcile/openshift_rolebindings.py +230 -130
  240. reconcile/openshift_saas_deploy.py +6 -7
  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 +8 -7
  244. reconcile/openshift_tekton_resources.py +1 -1
  245. reconcile/openshift_upgrade_watcher.py +4 -4
  246. reconcile/openshift_users.py +5 -3
  247. reconcile/oum/labelset.py +5 -3
  248. reconcile/oum/models.py +1 -4
  249. reconcile/oum/providers.py +1 -1
  250. reconcile/prometheus_rules_tester/integration.py +4 -4
  251. reconcile/quay_mirror.py +1 -1
  252. reconcile/queries.py +131 -0
  253. reconcile/requests_sender.py +8 -3
  254. reconcile/resource_scraper.py +1 -5
  255. reconcile/rhidp/common.py +3 -5
  256. reconcile/rhidp/sso_client/base.py +19 -10
  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/sendgrid_teammates.py +20 -9
  260. reconcile/skupper_network/integration.py +2 -2
  261. reconcile/slack_usergroups.py +35 -14
  262. reconcile/sql_query.py +1 -0
  263. reconcile/status.py +2 -2
  264. reconcile/status_board.py +6 -6
  265. reconcile/statuspage/atlassian.py +7 -7
  266. reconcile/statuspage/integrations/maintenances.py +4 -3
  267. reconcile/statuspage/page.py +4 -9
  268. reconcile/statuspage/status.py +5 -8
  269. reconcile/templates/rosa-classic-cluster-creation.sh.j2 +5 -1
  270. reconcile/templates/rosa-hcp-cluster-creation.sh.j2 +4 -1
  271. reconcile/templating/lib/merge_request_manager.py +2 -2
  272. reconcile/templating/lib/rendering.py +3 -3
  273. reconcile/templating/renderer.py +12 -13
  274. reconcile/terraform_aws_route53.py +18 -8
  275. reconcile/terraform_cloudflare_dns.py +3 -3
  276. reconcile/terraform_cloudflare_resources.py +12 -13
  277. reconcile/terraform_cloudflare_users.py +3 -2
  278. reconcile/terraform_init/integration.py +187 -23
  279. reconcile/terraform_repo.py +16 -12
  280. reconcile/terraform_resources.py +18 -10
  281. reconcile/terraform_tgw_attachments.py +28 -20
  282. reconcile/terraform_users.py +27 -22
  283. reconcile/terraform_vpc_peerings.py +15 -3
  284. reconcile/terraform_vpc_resources/integration.py +23 -8
  285. reconcile/typed_queries/app_interface_roles.py +10 -0
  286. reconcile/typed_queries/aws_account_tags.py +41 -0
  287. reconcile/typed_queries/cost_report/app_names.py +1 -1
  288. reconcile/typed_queries/cost_report/cost_namespaces.py +2 -2
  289. reconcile/typed_queries/saas_files.py +13 -13
  290. reconcile/typed_queries/status_board.py +2 -2
  291. reconcile/unleash_feature_toggles/integration.py +4 -2
  292. reconcile/utils/acs/base.py +6 -3
  293. reconcile/utils/acs/policies.py +2 -2
  294. reconcile/utils/aggregated_list.py +4 -3
  295. reconcile/utils/aws_api.py +51 -20
  296. reconcile/utils/aws_api_typed/api.py +38 -9
  297. reconcile/utils/aws_api_typed/cloudformation.py +149 -0
  298. reconcile/utils/aws_api_typed/logs.py +73 -0
  299. reconcile/utils/aws_api_typed/organization.py +4 -2
  300. reconcile/utils/binary.py +7 -12
  301. reconcile/utils/datetime_util.py +67 -0
  302. reconcile/utils/deadmanssnitch_api.py +1 -1
  303. reconcile/utils/differ.py +2 -3
  304. reconcile/utils/early_exit_cache.py +11 -12
  305. reconcile/utils/expiration.py +7 -3
  306. reconcile/utils/external_resource_spec.py +24 -1
  307. reconcile/utils/filtering.py +1 -1
  308. reconcile/utils/gitlab_api.py +7 -5
  309. reconcile/utils/glitchtip/client.py +6 -2
  310. reconcile/utils/glitchtip/models.py +25 -28
  311. reconcile/utils/gpg.py +5 -3
  312. reconcile/utils/gql.py +4 -7
  313. reconcile/utils/helm.py +2 -1
  314. reconcile/utils/helpers.py +1 -1
  315. reconcile/utils/imap_client.py +1 -1
  316. reconcile/utils/instrumented_wrappers.py +1 -1
  317. reconcile/utils/internal_groups/client.py +2 -2
  318. reconcile/utils/internal_groups/models.py +8 -17
  319. reconcile/utils/jenkins_api.py +24 -1
  320. reconcile/utils/jinja2/utils.py +6 -8
  321. reconcile/utils/jira_client.py +82 -63
  322. reconcile/utils/jjb_client.py +78 -46
  323. reconcile/utils/jobcontroller/controller.py +2 -2
  324. reconcile/utils/jobcontroller/models.py +17 -1
  325. reconcile/utils/json.py +74 -0
  326. reconcile/utils/ldap_client.py +4 -3
  327. reconcile/utils/lean_terraform_client.py +3 -1
  328. reconcile/utils/membershipsources/app_interface_resolver.py +4 -2
  329. reconcile/utils/membershipsources/models.py +16 -23
  330. reconcile/utils/membershipsources/resolver.py +4 -2
  331. reconcile/utils/merge_request_manager/merge_request_manager.py +4 -4
  332. reconcile/utils/merge_request_manager/parser.py +6 -6
  333. reconcile/utils/metrics.py +5 -5
  334. reconcile/utils/models.py +304 -82
  335. reconcile/utils/mr/__init__.py +3 -1
  336. reconcile/utils/mr/app_interface_reporter.py +6 -3
  337. reconcile/utils/mr/aws_access.py +1 -1
  338. reconcile/utils/mr/base.py +7 -13
  339. reconcile/utils/mr/clusters_updates.py +4 -2
  340. reconcile/utils/mr/notificator.py +3 -3
  341. reconcile/utils/mr/ocm_upgrade_scheduler_org_updates.py +4 -1
  342. reconcile/utils/mr/promote_qontract.py +28 -12
  343. reconcile/utils/mr/update_access_report_base.py +3 -4
  344. reconcile/utils/mr/user_maintenance.py +7 -6
  345. reconcile/utils/oc.py +445 -336
  346. reconcile/utils/oc_filters.py +3 -3
  347. reconcile/utils/ocm/addons.py +0 -1
  348. reconcile/utils/ocm/base.py +18 -21
  349. reconcile/utils/ocm/cluster_groups.py +1 -1
  350. reconcile/utils/ocm/identity_providers.py +2 -2
  351. reconcile/utils/ocm/labels.py +1 -1
  352. reconcile/utils/ocm/ocm.py +81 -71
  353. reconcile/utils/ocm/products.py +9 -3
  354. reconcile/utils/ocm/search_filters.py +3 -6
  355. reconcile/utils/ocm/service_log.py +4 -6
  356. reconcile/utils/ocm/sre_capability_labels.py +20 -13
  357. reconcile/utils/ocm_base_client.py +4 -4
  358. reconcile/utils/openshift_resource.py +83 -52
  359. reconcile/utils/openssl.py +2 -2
  360. reconcile/utils/output.py +3 -2
  361. reconcile/utils/pagerduty_api.py +10 -7
  362. reconcile/utils/promotion_state.py +6 -11
  363. reconcile/utils/raw_github_api.py +11 -8
  364. reconcile/utils/repo_owners.py +21 -29
  365. reconcile/utils/rhcsv2_certs.py +138 -35
  366. reconcile/utils/rosa/session.py +16 -0
  367. reconcile/utils/runtime/integration.py +2 -3
  368. reconcile/utils/runtime/meta.py +2 -1
  369. reconcile/utils/runtime/runner.py +2 -2
  370. reconcile/utils/saasherder/interfaces.py +13 -20
  371. reconcile/utils/saasherder/models.py +25 -21
  372. reconcile/utils/saasherder/saasherder.py +60 -32
  373. reconcile/utils/secret_reader.py +6 -6
  374. reconcile/utils/sharding.py +1 -1
  375. reconcile/utils/slack_api.py +26 -4
  376. reconcile/utils/sloth.py +224 -0
  377. reconcile/utils/sqs_gateway.py +16 -11
  378. reconcile/utils/state.py +2 -1
  379. reconcile/utils/structs.py +1 -1
  380. reconcile/utils/terraform_client.py +29 -26
  381. reconcile/utils/terrascript_aws_client.py +200 -116
  382. reconcile/utils/three_way_diff_strategy.py +1 -1
  383. reconcile/utils/unleash/server.py +2 -8
  384. reconcile/utils/vault.py +44 -41
  385. reconcile/utils/vcs.py +8 -8
  386. reconcile/vault_replication.py +119 -58
  387. tools/app_interface_reporter.py +4 -4
  388. tools/cli_commands/cost_report/cost_management_api.py +3 -3
  389. tools/cli_commands/cost_report/view.py +7 -6
  390. tools/cli_commands/erv2.py +1 -1
  391. tools/cli_commands/gpg_encrypt.py +4 -1
  392. tools/cli_commands/systems_and_tools.py +5 -1
  393. tools/qontract_cli.py +36 -21
  394. tools/template_validation.py +3 -1
  395. reconcile/gql_definitions/ocm_oidc_idp/__init__.py +0 -0
  396. reconcile/gql_definitions/ocm_subscription_labels/__init__.py +0 -0
  397. reconcile/jenkins/__init__.py +0 -0
  398. reconcile/jenkins/types.py +0 -77
  399. {qontract_reconcile-0.10.2.dev310.dist-info → qontract_reconcile-0.10.2.dev439.dist-info}/WHEEL +0 -0
  400. {qontract_reconcile-0.10.2.dev310.dist-info → qontract_reconcile-0.10.2.dev439.dist-info}/entry_points.txt +0 -0
@@ -61,7 +61,7 @@ def normalize_object(item: OR) -> OR:
61
61
  validate_k8s_object=False,
62
62
  )
63
63
 
64
- annotations = n.body.get("metadata").get("annotations") or {}
64
+ annotations = n.body.get("metadata", {}).get("annotations") or {}
65
65
  metadata["annotations"] = {
66
66
  k: v for k, v in annotations.items() if k not in NORMALIZE_IGNORE_ANNOTATIONS
67
67
  }
@@ -24,30 +24,24 @@ class Environment(BaseModel):
24
24
  return self.name == other
25
25
 
26
26
 
27
- class FeatureToggle(BaseModel):
27
+ class FeatureToggle(BaseModel, validate_by_name=True, validate_by_alias=True):
28
28
  name: str
29
29
  type: FeatureToggleType = FeatureToggleType.release
30
30
  description: str | None = None
31
31
  impression_data: bool = Field(False, alias="impressionData")
32
32
  environments: list[Environment]
33
33
 
34
- class Config:
35
- allow_population_by_field_name = True
36
-
37
34
  def __eq__(self, other: object) -> bool:
38
35
  if isinstance(other, FeatureToggle):
39
36
  return self.name == other.name
40
37
  return self.name == other
41
38
 
42
39
 
43
- class Project(BaseModel):
40
+ class Project(BaseModel, validate_by_name=True, validate_by_alias=True):
44
41
  pk: str = Field(alias="id")
45
42
  name: str
46
43
  feature_toggles: list[FeatureToggle] = []
47
44
 
48
- class Config:
49
- allow_population_by_field_name = True
50
-
51
45
 
52
46
  class TokenAuth(BearerTokenAuth):
53
47
  def __call__(self, r: requests.PreparedRequest) -> requests.PreparedRequest:
reconcile/utils/vault.py CHANGED
@@ -1,10 +1,12 @@
1
1
  import base64
2
+ import builtins
2
3
  import logging
3
4
  import os
4
5
  import threading
5
6
  import time
6
7
  from collections.abc import Mapping
7
8
  from functools import lru_cache
9
+ from typing import Any, Self
8
10
 
9
11
  import hvac
10
12
  import requests
@@ -49,7 +51,7 @@ class VaultConnectionError(Exception):
49
51
  SECRET_VERSION_LATEST = "LATEST"
50
52
 
51
53
 
52
- class _VaultClient:
54
+ class VaultClient:
53
55
  """
54
56
  A class representing a Vault client. Allows read/write operations.
55
57
  The client caches read requests in-memory if the request is made
@@ -57,6 +59,28 @@ class _VaultClient:
57
59
  and a version (no invalidation required).
58
60
  """
59
61
 
62
+ _instance = None
63
+ _lock = threading.Lock()
64
+
65
+ @classmethod
66
+ def get_instance(cls) -> Self:
67
+ with cls._lock:
68
+ if cls._instance is None:
69
+ cls._instance = cls()
70
+ return cls._instance
71
+
72
+ try:
73
+ is_authenticated = cls._instance._client.is_authenticated()
74
+ except requests.exceptions.ConnectionError:
75
+ is_authenticated = False
76
+
77
+ if is_authenticated:
78
+ return cls._instance
79
+
80
+ cls._instance.close()
81
+ cls._instance = cls()
82
+ return cls._instance
83
+
60
84
  def __init__(
61
85
  self,
62
86
  server: str | None = None,
@@ -122,13 +146,13 @@ class _VaultClient:
122
146
  t = threading.Thread(target=self._auto_refresh_client_auth, daemon=True)
123
147
  t.start()
124
148
 
125
- def __enter__(self):
149
+ def __enter__(self) -> Self:
126
150
  return self
127
151
 
128
- def __exit__(self, *exc):
152
+ def __exit__(self, *exc: Any) -> None:
129
153
  self.close()
130
154
 
131
- def close(self):
155
+ def close(self) -> None:
132
156
  """
133
157
  Close the client and release any resources associated with it.
134
158
  """
@@ -137,7 +161,7 @@ class _VaultClient:
137
161
  self._client.adapter.close()
138
162
  self._closed = True
139
163
 
140
- def _auto_refresh_client_auth(self):
164
+ def _auto_refresh_client_auth(self) -> None:
141
165
  """
142
166
  Thread that periodically refreshes the vault token
143
167
  """
@@ -150,7 +174,7 @@ class _VaultClient:
150
174
  LOG.debug("auto refresh client auth")
151
175
  self._refresh_client_auth()
152
176
 
153
- def _refresh_client_auth(self):
177
+ def _refresh_client_auth(self) -> None:
154
178
  if self.kube_auth_enabled:
155
179
  # must read each time to account for sa token refresh
156
180
  with open(self.kube_sa_token_path, encoding="locale") as f:
@@ -166,7 +190,7 @@ class _VaultClient:
166
190
  self._client.auth_approle(self.role_id, self.secret_id)
167
191
 
168
192
  @retry()
169
- def read_all_with_version(self, secret: Mapping) -> tuple[Mapping, str | None]:
193
+ def read_all_with_version(self, secret: Mapping) -> tuple[dict, int | None]:
170
194
  """Returns a dictionary of keys and values in a Vault secret and the
171
195
  version of the secret, for V1 secrets, version will be None.
172
196
 
@@ -176,7 +200,7 @@ class _VaultClient:
176
200
  a v2 KV engine)
177
201
  """
178
202
  secret_path = secret["path"]
179
- secret_version = secret.get("version")
203
+ secret_version = secret.get("version") or SECRET_VERSION_LATEST
180
204
 
181
205
  kv_version = self._get_mount_version_by_secret_path(secret_path)
182
206
 
@@ -203,12 +227,12 @@ class _VaultClient:
203
227
  """
204
228
  return self.read_all_with_version(secret)[0]
205
229
 
206
- def _get_mount_version_by_secret_path(self, path):
230
+ def _get_mount_version_by_secret_path(self, path: str) -> int:
207
231
  path_split = path.split("/")
208
232
  mount_point = path_split[0]
209
233
  return self._get_mount_version(mount_point)
210
234
 
211
- def __get_mount_version(self, mount_point):
235
+ def __get_mount_version(self, mount_point: str) -> int:
212
236
  try:
213
237
  self._client.secrets.kv.v2.read_configuration(mount_point)
214
238
  version = 2
@@ -217,7 +241,9 @@ class _VaultClient:
217
241
 
218
242
  return version
219
243
 
220
- def __read_all_v2(self, path: str, version: str | None) -> tuple[dict, str | None]:
244
+ def __read_all_v2(
245
+ self, path: str, version: str | None
246
+ ) -> tuple[dict[str, Any], int]:
221
247
  path_split = path.split("/")
222
248
  mount_point = path_split[0]
223
249
  read_path = "/".join(path_split[1:])
@@ -248,7 +274,7 @@ class _VaultClient:
248
274
  secret_version = secret["data"]["metadata"]["version"]
249
275
  return data, secret_version
250
276
 
251
- def _read_all_v1(self, path):
277
+ def _read_all_v1(self, path: str) -> Any:
252
278
  try:
253
279
  secret = self._client.read(path)
254
280
  except hvac.exceptions.Forbidden:
@@ -261,7 +287,7 @@ class _VaultClient:
261
287
  return secret["data"]
262
288
 
263
289
  @retry()
264
- def read(self, secret):
290
+ def read(self, secret: Mapping[str, Any]) -> Any:
265
291
  """Returns a value of a key in a Vault secret.
266
292
 
267
293
  The input secret is a dictionary which contains the following fields:
@@ -293,7 +319,7 @@ class _VaultClient:
293
319
  else data
294
320
  )
295
321
 
296
- def _read_v2(self, path, field, version):
322
+ def _read_v2(self, path: str, field: str, version: str | None) -> Any:
297
323
  data, _ = self._read_all_v2(path, version)
298
324
  try:
299
325
  secret_field = data[field]
@@ -301,7 +327,7 @@ class _VaultClient:
301
327
  raise SecretFieldNotFoundError(f"{path}/{field} ({version})") from None
302
328
  return secret_field
303
329
 
304
- def _read_v1(self, path, field):
330
+ def _read_v1(self, path: str, field: str) -> Any:
305
331
  data = self._read_all_v1(path)
306
332
  try:
307
333
  secret_field = data[field]
@@ -360,7 +386,7 @@ class _VaultClient:
360
386
  msg = f"permission denied accessing secret '{path}'"
361
387
  raise SecretAccessForbiddenError(msg) from None
362
388
 
363
- def _write_v1(self, path, data):
389
+ def _write_v1(self, path: str, data: dict[str, Any]) -> None:
364
390
  try:
365
391
  self._client.write(path, **data)
366
392
  except hvac.exceptions.Forbidden:
@@ -395,7 +421,7 @@ class _VaultClient:
395
421
  return []
396
422
  return path_list["data"]["keys"] or []
397
423
 
398
- def list_all(self, path):
424
+ def list_all(self, path: str) -> builtins.list[str]:
399
425
  """Returns a list of secrets in a given path and
400
426
  all its subpaths."""
401
427
  secrets = []
@@ -415,32 +441,9 @@ class _VaultClient:
415
441
  raise ValueError("deleting V2 secrets is not supported yet")
416
442
  self._delete_v1(path)
417
443
 
418
- def _delete_v1(self, path):
444
+ def _delete_v1(self, path: str) -> None:
419
445
  try:
420
446
  self._client.delete(path)
421
447
  except hvac.exceptions.Forbidden:
422
448
  msg = f"permission denied accessing secret '{path}'"
423
449
  raise SecretAccessForbiddenError(msg) from None
424
-
425
-
426
- class VaultClient:
427
- _instance_lock = threading.Lock()
428
- _instance = None
429
-
430
- def __new__(cls, *args, **kwargs):
431
- with cls._instance_lock:
432
- if cls._instance is None:
433
- cls._instance = _VaultClient(*args, **kwargs)
434
- return cls._instance
435
-
436
- try:
437
- is_authenticated = cls._instance._client.is_authenticated()
438
- except requests.exceptions.ConnectionError:
439
- is_authenticated = False
440
-
441
- if not is_authenticated:
442
- cls._instance.close()
443
- cls._instance = _VaultClient(*args, **kwargs)
444
- return cls._instance
445
-
446
- return cls._instance
reconcile/utils/vcs.py CHANGED
@@ -140,7 +140,7 @@ class VCS:
140
140
  gitlab_instances: Iterable[GitlabInstanceV1],
141
141
  ) -> GitLabApi:
142
142
  return GitLabApi(
143
- next(iter(gitlab_instances)).dict(by_alias=True),
143
+ next(iter(gitlab_instances)).model_dump(by_alias=True),
144
144
  secret_reader=self._secret_reader,
145
145
  )
146
146
 
@@ -150,7 +150,7 @@ class VCS:
150
150
  app_interface_repo_url: str,
151
151
  ) -> GitLabApi:
152
152
  return GitLabApi(
153
- next(iter(gitlab_instances)).dict(by_alias=True),
153
+ next(iter(gitlab_instances)).model_dump(by_alias=True),
154
154
  secret_reader=self._secret_reader,
155
155
  project_url=app_interface_repo_url,
156
156
  )
@@ -221,26 +221,26 @@ class VCS:
221
221
  match repo_info.platform:
222
222
  case "github":
223
223
  github = self._init_github(repo_url=repo_url, auth_code=auth_code)
224
- data = github.compare(commit_from=commit_from, commit_to=commit_to)
225
224
  return [
226
225
  Commit(
227
226
  repo=repo_url,
228
227
  sha=gh_commit.sha,
229
228
  date=gh_commit.commit.committer.date,
230
229
  )
231
- for gh_commit in data
230
+ for gh_commit in github.compare(
231
+ commit_from=commit_from, commit_to=commit_to
232
+ )
232
233
  ]
233
234
  case "gitlab":
234
- data = self._gitlab_instance.repository_compare(
235
- repo_url=repo_url, ref_from=commit_from, ref_to=commit_to
236
- )
237
235
  return [
238
236
  Commit(
239
237
  repo=repo_url,
240
238
  sha=gl_commit["id"],
241
239
  date=datetime.fromisoformat(gl_commit["committed_date"]),
242
240
  )
243
- for gl_commit in data
241
+ for gl_commit in self._gitlab_instance.repository_compare(
242
+ repo_url=repo_url, ref_from=commit_from, ref_to=commit_to
243
+ )
244
244
  ]
245
245
  case _:
246
246
  raise ValueError(f"Unsupported repository URL: {repo_url}")
@@ -1,9 +1,6 @@
1
1
  import logging
2
2
  import re
3
3
  from collections.abc import Iterable
4
- from typing import (
5
- cast,
6
- )
7
4
 
8
5
  from reconcile.gql_definitions.jenkins_configs import jenkins_configs
9
6
  from reconcile.gql_definitions.jenkins_configs.jenkins_configs import (
@@ -30,7 +27,6 @@ from reconcile.utils.vault import (
30
27
  SecretNotFoundError,
31
28
  SecretVersionNotFoundError,
32
29
  VaultClient,
33
- _VaultClient,
34
30
  )
35
31
 
36
32
  QONTRACT_INTEGRATION = "vault-replication"
@@ -51,8 +47,8 @@ class VaultInvalidPolicyError(Exception):
51
47
 
52
48
  def deep_copy_versions(
53
49
  dry_run: bool,
54
- source_vault: _VaultClient,
55
- dest_vault: _VaultClient,
50
+ source_vault: VaultClient,
51
+ dest_vault: VaultClient,
56
52
  current_dest_version: int,
57
53
  current_source_version: int,
58
54
  path: str,
@@ -88,9 +84,57 @@ def deep_copy_versions(
88
84
  dest_vault.write(secret=write_dict, decode_base64=False, force=True)
89
85
 
90
86
 
87
+ def _handle_missing_destination_secret(
88
+ dry_run: bool,
89
+ source_vault: VaultClient,
90
+ dest_vault: VaultClient,
91
+ source_data: dict,
92
+ source_version: int | None,
93
+ path: str,
94
+ ) -> None:
95
+ """Handles replication when destination secret is missing or has no accessible versions.
96
+
97
+ This covers two scenarios:
98
+ 1. Secret doesn't exist at all in destination vault (SecretNotFoundError)
99
+ 2. Secret exists but all versions are deleted in KV v2 (SecretVersionNotFoundError)
100
+
101
+ For both cases, we replicate from source starting from version 0 (or copy directly for v1).
102
+
103
+ Args:
104
+ dry_run: Whether this is a dry run
105
+ source_vault: Source vault client (needed for v2 deep copy)
106
+ dest_vault: Destination vault client
107
+ source_data: Already retrieved source secret data
108
+ source_version: Source secret version (None for v1 secrets)
109
+ path: Secret path
110
+ """
111
+ if source_version is None:
112
+ # v1 secret - just copy it over using the already-retrieved source data
113
+ logging.info(["replicate_vault_secret", "Copying v1 secret", path])
114
+ if not dry_run:
115
+ write_dict = {"path": path, "data": source_data}
116
+ dest_vault.write(secret=write_dict, decode_base64=False, force=True)
117
+ else:
118
+ # v2 secret - deep copy all versions starting from 0
119
+ # Note: deep_copy_versions will read individual versions from source as needed
120
+ logging.info([
121
+ "replicate_vault_secret",
122
+ "Deep copying v2 secret versions",
123
+ path,
124
+ ])
125
+ deep_copy_versions(
126
+ dry_run=dry_run,
127
+ source_vault=source_vault,
128
+ dest_vault=dest_vault,
129
+ current_dest_version=0,
130
+ current_source_version=source_version,
131
+ path=path,
132
+ )
133
+
134
+
91
135
  def write_dummy_versions(
92
136
  dry_run: bool,
93
- dest_vault: _VaultClient,
137
+ dest_vault: VaultClient,
94
138
  secret_version: int,
95
139
  path: str,
96
140
  ) -> None:
@@ -112,7 +156,7 @@ def write_dummy_versions(
112
156
 
113
157
 
114
158
  def copy_vault_secret(
115
- dry_run: bool, source_vault: _VaultClient, dest_vault: _VaultClient, path: str
159
+ dry_run: bool, source_vault: VaultClient, dest_vault: VaultClient, path: str
116
160
  ) -> None:
117
161
  """Copies a secret from the source vault to the destination vault"""
118
162
  secret_dict = {"path": path, "version": "LATEST"}
@@ -137,48 +181,65 @@ def copy_vault_secret(
137
181
 
138
182
  try:
139
183
  dest_data, dest_version = dest_vault.read_all_with_version(secret_dict)
140
- if dest_version is None and version is None:
141
- # v1 secrets don't have version
142
- if source_data == dest_data:
143
- # If the secret is the same in both vaults, we don't need
144
- # to copy it again
145
- return
146
-
147
- secret, _ = source_vault.read_all_with_version(secret_dict)
148
- write_dict = {"path": path, "data": secret}
149
- logging.info(["replicate_vault_secret", path])
150
- if not dry_run:
151
- # Using force=True to write the secret to force the vault client even
152
- # if the data is the same as the previous version. This happens in
153
- # some secrets even tho the library does not create it
154
- dest_vault.write(secret=write_dict, decode_base64=False, force=True)
155
- elif dest_version < version:
156
- deep_copy_versions(
157
- dry_run=dry_run,
158
- source_vault=source_vault,
159
- dest_vault=dest_vault,
160
- current_dest_version=dest_version,
161
- current_source_version=version,
162
- path=path,
163
- )
164
- except (SecretVersionNotFoundError, SecretNotFoundError):
165
- logging.info(["replicate_vault_secret", "Secret not found", path])
166
- # Handle v1 secrets where version is None and we don't need to deep sync.
167
- if version is None:
168
- logging.info(["replicate_vault_secret", path])
169
- if not dry_run:
170
- secret, _ = source_vault.read_all_with_version(secret_dict)
171
- write_dict = {"path": path, "data": secret}
172
- dest_vault.write(secret=write_dict, decode_base64=False, force=True)
173
- else:
174
- deep_copy_versions(
175
- dry_run=dry_run,
176
- source_vault=source_vault,
177
- dest_vault=dest_vault,
178
- current_dest_version=0,
179
- current_source_version=version,
180
- path=path,
181
- )
184
+ except SecretVersionNotFoundError:
185
+ # Handle KV v2 case where secret metadata exists but latest version is deleted
186
+ # This occurs when someone manually deletes the latest version but the secret
187
+ # metadata still exists in Vault. This should only happen for v2 secrets.
188
+ logging.info([
189
+ "replicate_vault_secret",
190
+ "KV v2 latest version deleted, replicating all versions",
191
+ path,
192
+ ])
193
+ _handle_missing_destination_secret(
194
+ dry_run=dry_run,
195
+ source_vault=source_vault,
196
+ dest_vault=dest_vault,
197
+ source_data=source_data,
198
+ source_version=version,
199
+ path=path,
200
+ )
201
+ return
202
+ except SecretNotFoundError:
203
+ # Handle case where secret doesn't exist at all in destination vault
204
+ logging.info([
205
+ "replicate_vault_secret",
206
+ "Secret not found in destination",
207
+ path,
208
+ ])
209
+ _handle_missing_destination_secret(
210
+ dry_run=dry_run,
211
+ source_vault=source_vault,
212
+ dest_vault=dest_vault,
213
+ source_data=source_data,
214
+ source_version=version,
215
+ path=path,
216
+ )
217
+ return
218
+
219
+ # If we reach here, we successfully read the destination secret
220
+ if dest_version is None or version is None:
221
+ # v1 secrets don't have version
222
+ if source_data == dest_data:
223
+ # If the secret is the same in both vaults, we don't need
224
+ # to copy it again
225
+ return
226
+
227
+ write_dict = {"path": path, "data": source_data}
228
+ logging.info(["replicate_vault_secret", path])
229
+ if not dry_run:
230
+ # Using force=True to write the secret to force the vault client even
231
+ # if the data is the same as the previous version. This happens in
232
+ # some secrets even tho the library does not create it
233
+ dest_vault.write(secret=write_dict, decode_base64=False, force=True)
234
+ elif dest_version < version:
235
+ deep_copy_versions(
236
+ dry_run=dry_run,
237
+ source_vault=source_vault,
238
+ dest_vault=dest_vault,
239
+ current_dest_version=dest_version,
240
+ current_source_version=version,
241
+ path=path,
242
+ )
182
243
 
183
244
 
184
245
  def check_invalid_paths(
@@ -228,7 +289,7 @@ def get_policy_paths(
228
289
 
229
290
 
230
291
  def get_policy_secret_list(
231
- vault_instance: _VaultClient, policy_paths: Iterable[str]
292
+ vault_instance: VaultClient, policy_paths: Iterable[str]
232
293
  ) -> list[str]:
233
294
  """Returns a list of secrets to be copied from the given policy"""
234
295
  secrets = set()
@@ -249,7 +310,7 @@ def get_policy_secret_list(
249
310
 
250
311
 
251
312
  def get_jenkins_secret_list(
252
- vault_instance: _VaultClient,
313
+ vault_instance: VaultClient,
253
314
  jenkins_instance: str,
254
315
  query_data: JenkinsConfigsQueryData,
255
316
  ) -> list[str]:
@@ -294,7 +355,7 @@ def get_vault_credentials(
294
355
  """Returns a dictionary with the credentials used to authenticate with Vault,
295
356
  retrieved from the values present on AppInterface and comming from Vault itself."""
296
357
  vault_creds = {}
297
- vault = cast("_VaultClient", VaultClient())
358
+ vault = VaultClient.get_instance()
298
359
 
299
360
  if not isinstance(
300
361
  vault_auth,
@@ -324,8 +385,8 @@ def get_vault_credentials(
324
385
 
325
386
  def replicate_paths(
326
387
  dry_run: bool,
327
- source_vault: _VaultClient,
328
- dest_vault: _VaultClient,
388
+ source_vault: VaultClient,
389
+ dest_vault: VaultClient,
329
390
  replications: VaultReplicationConfigV1,
330
391
  ) -> None:
331
392
  """For each path present in the definition of the vault instance, replicate
@@ -435,16 +496,16 @@ def run(dry_run: bool) -> None:
435
496
  replication.dest_auth, replication.vault_instance.address
436
497
  )
437
498
 
438
- # Private class _VaultClient is used because the public class is
499
+ # Private class VaultClient is used because the public class is
439
500
  # defined as a singleton, and we need to create multiple instances
440
501
  # as the source vault is different than the replication.
441
502
  with (
442
- _VaultClient(
503
+ VaultClient(
443
504
  server=source_creds["server"],
444
505
  role_id=source_creds["role_id"],
445
506
  secret_id=source_creds["secret_id"],
446
507
  ) as source_vault,
447
- _VaultClient(
508
+ VaultClient(
448
509
  server=dest_creds["server"],
449
510
  role_id=dest_creds["role_id"],
450
511
  secret_id=dest_creds["secret_id"],
@@ -4,7 +4,6 @@ import os
4
4
  import textwrap
5
5
  from collections.abc import Mapping, MutableMapping
6
6
  from datetime import (
7
- UTC,
8
7
  datetime,
9
8
  )
10
9
 
@@ -29,6 +28,7 @@ from reconcile.cli import (
29
28
  )
30
29
  from reconcile.jenkins_job_builder import init_jjb
31
30
  from reconcile.utils.constants import DEFAULT_THREAD_POOL_SIZE
31
+ from reconcile.utils.datetime_util import ensure_utc, utc_now
32
32
  from reconcile.utils.mr import CreateAppInterfaceReporter
33
33
  from reconcile.utils.runtime.environment import init_env
34
34
  from reconcile.utils.secret_reader import SecretReader
@@ -189,8 +189,8 @@ def get_apps_data(
189
189
  apps = queries.get_apps()
190
190
  jjb = init_jjb(secret_reader)
191
191
  jenkins_map = jenkins_base.get_jenkins_map()
192
- time_limit = date - relativedelta(months=month_delta)
193
- timestamp_limit = int(time_limit.replace(tzinfo=UTC).timestamp())
192
+ time_limit = ensure_utc(date) - relativedelta(months=month_delta)
193
+ timestamp_limit = int(time_limit.timestamp())
194
194
 
195
195
  secret_content = secret_reader.read_all({"path": DASHDOTDB_SECRET})
196
196
  dashdotdb_url = secret_content["url"]
@@ -411,7 +411,7 @@ def main(
411
411
  ) -> None:
412
412
  init_env(log_level=log_level, config_file=configfile)
413
413
 
414
- now = datetime.now()
414
+ now = utc_now()
415
415
  apps = get_apps_data(now, thread_pool_size=thread_pool_size)
416
416
 
417
417
  reports = [Report(app, now).to_message() for app in apps]
@@ -74,7 +74,7 @@ class CostManagementApi(ApiBase):
74
74
  timeout=self.read_timeout,
75
75
  )
76
76
  response.raise_for_status()
77
- return AwsReportCostResponse.parse_obj(response.json())
77
+ return AwsReportCostResponse.model_validate(response.json())
78
78
 
79
79
  def get_openshift_costs_report(
80
80
  self,
@@ -97,7 +97,7 @@ class CostManagementApi(ApiBase):
97
97
  timeout=self.read_timeout,
98
98
  )
99
99
  response.raise_for_status()
100
- return OpenShiftReportCostResponse.parse_obj(response.json())
100
+ return OpenShiftReportCostResponse.model_validate(response.json())
101
101
 
102
102
  def get_openshift_cost_optimization_report(
103
103
  self,
@@ -120,7 +120,7 @@ class CostManagementApi(ApiBase):
120
120
  response.raise_for_status()
121
121
 
122
122
  data = self._get_paginated(response)
123
- return OpenShiftCostOptimizationReportResponse.parse_obj(data)
123
+ return OpenShiftCostOptimizationReportResponse.model_validate(data)
124
124
 
125
125
  def _get_paginated(
126
126
  self,