qontract-reconcile 0.10.2.dev394__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 (308) hide show
  1. {qontract_reconcile-0.10.2.dev394.dist-info → qontract_reconcile-0.10.2.dev414.dist-info}/METADATA +4 -3
  2. {qontract_reconcile-0.10.2.dev394.dist-info → qontract_reconcile-0.10.2.dev414.dist-info}/RECORD +308 -308
  3. reconcile/acs_rbac.py +2 -2
  4. reconcile/aus/advanced_upgrade_service.py +15 -12
  5. reconcile/aus/base.py +9 -13
  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 +6 -6
  10. reconcile/aws_account_manager/reconciler.py +3 -3
  11. reconcile/aws_ami_cleanup/integration.py +2 -5
  12. reconcile/aws_ami_share.py +69 -62
  13. reconcile/aws_saml_idp/integration.py +5 -3
  14. reconcile/aws_saml_roles/integration.py +23 -22
  15. reconcile/aws_version_sync/integration.py +6 -12
  16. reconcile/change_owners/bundle.py +3 -3
  17. reconcile/change_owners/change_log_tracking.py +3 -2
  18. reconcile/change_owners/change_owners.py +1 -1
  19. reconcile/dashdotdb_dora.py +1 -1
  20. reconcile/dashdotdb_slo.py +1 -1
  21. reconcile/database_access_manager.py +8 -9
  22. reconcile/dynatrace_token_provider/integration.py +1 -1
  23. reconcile/endpoints_discovery/integration.py +4 -1
  24. reconcile/endpoints_discovery/merge_request.py +1 -1
  25. reconcile/endpoints_discovery/merge_request_manager.py +1 -1
  26. reconcile/external_resources/integration.py +1 -1
  27. reconcile/external_resources/manager.py +19 -7
  28. reconcile/external_resources/metrics.py +1 -1
  29. reconcile/external_resources/model.py +6 -6
  30. reconcile/external_resources/reconciler.py +7 -4
  31. reconcile/external_resources/secrets_sync.py +2 -2
  32. reconcile/external_resources/state.py +56 -14
  33. reconcile/fleet_labeler/integration.py +1 -1
  34. reconcile/gcp_image_mirror.py +2 -2
  35. reconcile/github_org.py +1 -1
  36. reconcile/github_owners.py +4 -0
  37. reconcile/gitlab_members.py +6 -12
  38. reconcile/gitlab_permissions.py +8 -12
  39. reconcile/glitchtip_project_alerts/integration.py +3 -1
  40. reconcile/gql_definitions/acs/acs_instances.py +5 -5
  41. reconcile/gql_definitions/acs/acs_policies.py +5 -5
  42. reconcile/gql_definitions/acs/acs_rbac.py +5 -5
  43. reconcile/gql_definitions/advanced_upgrade_service/aus_clusters.py +5 -5
  44. reconcile/gql_definitions/advanced_upgrade_service/aus_organization.py +5 -5
  45. reconcile/gql_definitions/app_interface_metrics_exporter/onboarding_status.py +5 -5
  46. reconcile/gql_definitions/app_sre_tekton_access_revalidation/roles.py +5 -5
  47. reconcile/gql_definitions/app_sre_tekton_access_revalidation/users.py +5 -5
  48. reconcile/gql_definitions/automated_actions/instance.py +46 -7
  49. reconcile/gql_definitions/aws_account_manager/aws_accounts.py +5 -5
  50. reconcile/gql_definitions/aws_ami_cleanup/aws_accounts.py +5 -5
  51. reconcile/gql_definitions/aws_cloudwatch_log_retention/aws_accounts.py +5 -5
  52. reconcile/gql_definitions/aws_saml_idp/aws_accounts.py +5 -5
  53. reconcile/gql_definitions/aws_saml_roles/aws_accounts.py +5 -5
  54. reconcile/gql_definitions/aws_saml_roles/roles.py +5 -5
  55. reconcile/gql_definitions/aws_version_sync/clusters.py +5 -5
  56. reconcile/gql_definitions/aws_version_sync/namespaces.py +5 -5
  57. reconcile/gql_definitions/change_owners/queries/change_types.py +5 -5
  58. reconcile/gql_definitions/change_owners/queries/self_service_roles.py +5 -5
  59. reconcile/gql_definitions/cluster_auth_rhidp/clusters.py +5 -5
  60. reconcile/gql_definitions/common/alerting_services_settings.py +5 -5
  61. reconcile/gql_definitions/common/app_code_component_repos.py +5 -5
  62. reconcile/gql_definitions/common/app_interface_custom_messages.py +5 -5
  63. reconcile/gql_definitions/common/app_interface_dms_settings.py +5 -5
  64. reconcile/gql_definitions/common/app_interface_repo_settings.py +5 -5
  65. reconcile/gql_definitions/common/app_interface_roles.py +5 -5
  66. reconcile/gql_definitions/common/app_interface_state_settings.py +5 -5
  67. reconcile/gql_definitions/common/app_interface_vault_settings.py +5 -5
  68. reconcile/gql_definitions/common/app_quay_repos_escalation_policies.py +5 -5
  69. reconcile/gql_definitions/common/apps.py +5 -5
  70. reconcile/gql_definitions/common/aws_vpc_requests.py +5 -5
  71. reconcile/gql_definitions/common/aws_vpcs.py +5 -5
  72. reconcile/gql_definitions/common/clusters.py +5 -5
  73. reconcile/gql_definitions/common/clusters_minimal.py +5 -5
  74. reconcile/gql_definitions/common/clusters_with_dms.py +5 -5
  75. reconcile/gql_definitions/common/clusters_with_peering.py +5 -5
  76. reconcile/gql_definitions/common/github_orgs.py +5 -5
  77. reconcile/gql_definitions/common/jira_settings.py +5 -5
  78. reconcile/gql_definitions/common/jiralert_settings.py +5 -5
  79. reconcile/gql_definitions/common/ldap_settings.py +5 -5
  80. reconcile/gql_definitions/common/namespaces.py +5 -5
  81. reconcile/gql_definitions/common/namespaces_minimal.py +7 -5
  82. reconcile/gql_definitions/common/ocm_env_telemeter.py +5 -5
  83. reconcile/gql_definitions/common/ocm_environments.py +5 -5
  84. reconcile/gql_definitions/common/pagerduty_instances.py +5 -5
  85. reconcile/gql_definitions/common/pgp_reencryption_settings.py +5 -5
  86. reconcile/gql_definitions/common/pipeline_providers.py +5 -5
  87. reconcile/gql_definitions/common/quay_instances.py +5 -5
  88. reconcile/gql_definitions/common/quay_orgs.py +5 -5
  89. reconcile/gql_definitions/common/reserved_networks.py +5 -5
  90. reconcile/gql_definitions/common/rhcs_provider_settings.py +5 -5
  91. reconcile/gql_definitions/common/saas_files.py +5 -5
  92. reconcile/gql_definitions/common/saas_target_namespaces.py +5 -5
  93. reconcile/gql_definitions/common/saasherder_settings.py +5 -5
  94. reconcile/gql_definitions/common/slack_workspaces.py +5 -5
  95. reconcile/gql_definitions/common/smtp_client_settings.py +5 -5
  96. reconcile/gql_definitions/common/state_aws_account.py +5 -5
  97. reconcile/gql_definitions/common/users.py +5 -5
  98. reconcile/gql_definitions/common/users_with_paths.py +5 -5
  99. reconcile/gql_definitions/cost_report/app_names.py +5 -5
  100. reconcile/gql_definitions/cost_report/cost_namespaces.py +5 -5
  101. reconcile/gql_definitions/cost_report/settings.py +5 -5
  102. reconcile/gql_definitions/dashdotdb_slo/slo_documents_query.py +5 -5
  103. reconcile/gql_definitions/dynatrace_token_provider/dynatrace_bootstrap_tokens.py +5 -5
  104. reconcile/gql_definitions/dynatrace_token_provider/token_specs.py +5 -5
  105. reconcile/gql_definitions/email_sender/apps.py +5 -5
  106. reconcile/gql_definitions/email_sender/emails.py +5 -5
  107. reconcile/gql_definitions/email_sender/users.py +5 -5
  108. reconcile/gql_definitions/endpoints_discovery/apps.py +5 -5
  109. reconcile/gql_definitions/external_resources/aws_accounts.py +5 -5
  110. reconcile/gql_definitions/external_resources/external_resources_modules.py +5 -5
  111. reconcile/gql_definitions/external_resources/external_resources_namespaces.py +5 -5
  112. reconcile/gql_definitions/external_resources/external_resources_settings.py +5 -5
  113. reconcile/gql_definitions/external_resources/fragments/external_resources_module_overrides.py +5 -5
  114. reconcile/gql_definitions/fleet_labeler/fleet_labels.py +5 -5
  115. reconcile/gql_definitions/fragments/aus_organization.py +5 -5
  116. reconcile/gql_definitions/fragments/aws_account_common.py +5 -5
  117. reconcile/gql_definitions/fragments/aws_account_managed.py +5 -5
  118. reconcile/gql_definitions/fragments/aws_account_sso.py +5 -5
  119. reconcile/gql_definitions/fragments/aws_infra_management_account.py +5 -5
  120. reconcile/gql_definitions/fragments/aws_organization.py +5 -5
  121. reconcile/gql_definitions/fragments/aws_vpc.py +5 -5
  122. reconcile/gql_definitions/fragments/aws_vpc_request.py +5 -5
  123. reconcile/gql_definitions/fragments/container_image_mirror.py +5 -5
  124. reconcile/gql_definitions/fragments/deploy_resources.py +5 -5
  125. reconcile/gql_definitions/fragments/disable.py +5 -5
  126. reconcile/gql_definitions/fragments/email_service.py +5 -5
  127. reconcile/gql_definitions/fragments/email_user.py +5 -5
  128. reconcile/gql_definitions/fragments/jumphost_common_fields.py +5 -5
  129. reconcile/gql_definitions/fragments/membership_source.py +5 -5
  130. reconcile/gql_definitions/fragments/minimal_ocm_organization.py +5 -5
  131. reconcile/gql_definitions/fragments/oc_connection_cluster.py +5 -5
  132. reconcile/gql_definitions/fragments/ocm_environment.py +5 -5
  133. reconcile/gql_definitions/fragments/pipeline_provider_retention.py +5 -5
  134. reconcile/gql_definitions/fragments/prometheus_instance.py +5 -5
  135. reconcile/gql_definitions/fragments/resource_limits_requirements.py +5 -5
  136. reconcile/gql_definitions/fragments/resource_requests_requirements.py +5 -5
  137. reconcile/gql_definitions/fragments/resource_values.py +5 -5
  138. reconcile/gql_definitions/fragments/saas_slo_document.py +5 -5
  139. reconcile/gql_definitions/fragments/saas_target_namespace.py +5 -5
  140. reconcile/gql_definitions/fragments/serviceaccount_token.py +5 -5
  141. reconcile/gql_definitions/fragments/terraform_state.py +5 -5
  142. reconcile/gql_definitions/fragments/upgrade_policy.py +5 -5
  143. reconcile/gql_definitions/fragments/user.py +5 -5
  144. reconcile/gql_definitions/fragments/vault_secret.py +5 -5
  145. reconcile/gql_definitions/gcp/gcp_docker_repos.py +5 -5
  146. reconcile/gql_definitions/gcp/gcp_projects.py +5 -5
  147. reconcile/gql_definitions/gitlab_members/gitlab_instances.py +5 -5
  148. reconcile/gql_definitions/gitlab_members/permissions.py +5 -5
  149. reconcile/gql_definitions/glitchtip/glitchtip_instance.py +5 -5
  150. reconcile/gql_definitions/glitchtip/glitchtip_project.py +5 -5
  151. reconcile/gql_definitions/glitchtip_project_alerts/glitchtip_project.py +5 -5
  152. reconcile/gql_definitions/integrations/integrations.py +5 -5
  153. reconcile/gql_definitions/introspection.json +231 -0
  154. reconcile/gql_definitions/jenkins_configs/jenkins_configs.py +5 -5
  155. reconcile/gql_definitions/jenkins_configs/jenkins_instances.py +5 -5
  156. reconcile/gql_definitions/jira/jira_servers.py +5 -5
  157. reconcile/gql_definitions/jira_permissions_validator/jira_boards_for_permissions_validator.py +5 -5
  158. reconcile/gql_definitions/jumphosts/jumphosts.py +5 -5
  159. reconcile/gql_definitions/ldap_groups/roles.py +5 -5
  160. reconcile/gql_definitions/ldap_groups/settings.py +5 -5
  161. reconcile/gql_definitions/maintenance/maintenances.py +5 -5
  162. reconcile/gql_definitions/membershipsources/roles.py +5 -5
  163. reconcile/gql_definitions/ocm_labels/clusters.py +5 -5
  164. reconcile/gql_definitions/ocm_labels/organizations.py +5 -5
  165. reconcile/gql_definitions/openshift_cluster_bots/clusters.py +5 -5
  166. reconcile/gql_definitions/openshift_groups/managed_groups.py +5 -5
  167. reconcile/gql_definitions/openshift_groups/managed_roles.py +5 -5
  168. reconcile/gql_definitions/openshift_serviceaccount_tokens/tokens.py +5 -5
  169. reconcile/gql_definitions/quay_membership/quay_membership.py +5 -5
  170. reconcile/gql_definitions/rhcs/certs.py +5 -5
  171. reconcile/gql_definitions/rhidp/organizations.py +5 -5
  172. reconcile/gql_definitions/service_dependencies/jenkins_instance_fragment.py +5 -5
  173. reconcile/gql_definitions/service_dependencies/service_dependencies.py +5 -5
  174. reconcile/gql_definitions/sharding/aws_accounts.py +5 -5
  175. reconcile/gql_definitions/sharding/ocm_organization.py +5 -5
  176. reconcile/gql_definitions/skupper_network/site_controller_template.py +5 -5
  177. reconcile/gql_definitions/skupper_network/skupper_networks.py +5 -5
  178. reconcile/gql_definitions/slack_usergroups/clusters.py +5 -5
  179. reconcile/gql_definitions/slack_usergroups/permissions.py +5 -5
  180. reconcile/gql_definitions/slack_usergroups/users.py +5 -5
  181. reconcile/gql_definitions/slo_documents/slo_documents.py +5 -5
  182. reconcile/gql_definitions/status_board/status_board.py +5 -5
  183. reconcile/gql_definitions/statuspage/statuspages.py +5 -5
  184. reconcile/gql_definitions/templating/template_collection.py +5 -5
  185. reconcile/gql_definitions/templating/templates.py +5 -5
  186. reconcile/gql_definitions/terraform_cloudflare_dns/app_interface_cloudflare_dns_settings.py +5 -5
  187. reconcile/gql_definitions/terraform_cloudflare_dns/terraform_cloudflare_zones.py +5 -5
  188. reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_accounts.py +5 -5
  189. reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_resources.py +5 -5
  190. reconcile/gql_definitions/terraform_cloudflare_users/app_interface_setting_cloudflare_and_vault.py +5 -5
  191. reconcile/gql_definitions/terraform_cloudflare_users/terraform_cloudflare_roles.py +5 -5
  192. reconcile/gql_definitions/terraform_init/aws_accounts.py +5 -5
  193. reconcile/gql_definitions/terraform_repo/terraform_repo.py +5 -5
  194. reconcile/gql_definitions/terraform_resources/database_access_manager.py +5 -5
  195. reconcile/gql_definitions/terraform_resources/terraform_resources_namespaces.py +5 -5
  196. reconcile/gql_definitions/terraform_tgw_attachments/aws_accounts.py +5 -5
  197. reconcile/gql_definitions/unleash_feature_toggles/feature_toggles.py +5 -5
  198. reconcile/gql_definitions/vault_instances/vault_instances.py +5 -5
  199. reconcile/gql_definitions/vault_policies/vault_policies.py +5 -5
  200. reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator.py +5 -5
  201. reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator_peered_cluster_fragment.py +5 -5
  202. reconcile/integrations_manager.py +3 -3
  203. reconcile/jenkins_worker_fleets.py +9 -8
  204. reconcile/jira_permissions_validator.py +2 -2
  205. reconcile/ldap_groups/integration.py +1 -1
  206. reconcile/ocm/types.py +35 -57
  207. reconcile/ocm_aws_infrastructure_access.py +1 -1
  208. reconcile/ocm_clusters.py +4 -4
  209. reconcile/ocm_labels/integration.py +3 -2
  210. reconcile/ocm_machine_pools.py +23 -23
  211. reconcile/openshift_base.py +53 -2
  212. reconcile/openshift_cluster_bots.py +1 -1
  213. reconcile/openshift_namespace_labels.py +1 -1
  214. reconcile/openshift_namespaces.py +97 -101
  215. reconcile/openshift_resources_base.py +6 -2
  216. reconcile/openshift_rhcs_certs.py +5 -5
  217. reconcile/openshift_rolebindings.py +7 -11
  218. reconcile/openshift_saas_deploy.py +4 -5
  219. reconcile/openshift_saas_deploy_change_tester.py +9 -7
  220. reconcile/openshift_serviceaccount_tokens.py +2 -2
  221. reconcile/openshift_upgrade_watcher.py +1 -1
  222. reconcile/oum/labelset.py +5 -3
  223. reconcile/oum/models.py +1 -4
  224. reconcile/prometheus_rules_tester/integration.py +3 -3
  225. reconcile/quay_mirror.py +1 -1
  226. reconcile/queries.py +6 -0
  227. reconcile/rhidp/common.py +3 -5
  228. reconcile/rhidp/sso_client/base.py +1 -1
  229. reconcile/saas_auto_promotions_manager/merge_request_manager/renderer.py +1 -1
  230. reconcile/skupper_network/integration.py +2 -2
  231. reconcile/slack_usergroups.py +31 -11
  232. reconcile/status_board.py +6 -6
  233. reconcile/statuspage/atlassian.py +7 -7
  234. reconcile/statuspage/page.py +4 -9
  235. reconcile/templating/lib/rendering.py +3 -3
  236. reconcile/templating/renderer.py +2 -2
  237. reconcile/terraform_cloudflare_dns.py +3 -3
  238. reconcile/terraform_cloudflare_resources.py +5 -5
  239. reconcile/terraform_cloudflare_users.py +3 -2
  240. reconcile/terraform_init/integration.py +2 -2
  241. reconcile/terraform_repo.py +16 -12
  242. reconcile/terraform_resources.py +6 -6
  243. reconcile/terraform_tgw_attachments.py +20 -18
  244. reconcile/terraform_vpc_resources/integration.py +3 -1
  245. reconcile/typed_queries/cost_report/app_names.py +1 -1
  246. reconcile/typed_queries/cost_report/cost_namespaces.py +2 -2
  247. reconcile/typed_queries/saas_files.py +11 -11
  248. reconcile/typed_queries/status_board.py +2 -2
  249. reconcile/unleash_feature_toggles/integration.py +4 -2
  250. reconcile/utils/acs/base.py +6 -3
  251. reconcile/utils/acs/policies.py +2 -2
  252. reconcile/utils/aws_api.py +51 -20
  253. reconcile/utils/aws_api_typed/organization.py +4 -2
  254. reconcile/utils/deadmanssnitch_api.py +1 -1
  255. reconcile/utils/early_exit_cache.py +8 -10
  256. reconcile/utils/gitlab_api.py +7 -5
  257. reconcile/utils/glitchtip/client.py +6 -2
  258. reconcile/utils/glitchtip/models.py +25 -28
  259. reconcile/utils/gql.py +4 -7
  260. reconcile/utils/instrumented_wrappers.py +1 -1
  261. reconcile/utils/internal_groups/client.py +2 -2
  262. reconcile/utils/internal_groups/models.py +8 -17
  263. reconcile/utils/jinja2/utils.py +2 -5
  264. reconcile/utils/jobcontroller/controller.py +1 -1
  265. reconcile/utils/jobcontroller/models.py +17 -1
  266. reconcile/utils/json.py +39 -1
  267. reconcile/utils/membershipsources/app_interface_resolver.py +4 -2
  268. reconcile/utils/membershipsources/models.py +16 -23
  269. reconcile/utils/membershipsources/resolver.py +4 -2
  270. reconcile/utils/merge_request_manager/merge_request_manager.py +1 -1
  271. reconcile/utils/merge_request_manager/parser.py +4 -4
  272. reconcile/utils/metrics.py +5 -5
  273. reconcile/utils/models.py +304 -82
  274. reconcile/utils/mr/notificator.py +1 -1
  275. reconcile/utils/mr/user_maintenance.py +3 -2
  276. reconcile/utils/oc.py +112 -92
  277. reconcile/utils/ocm/addons.py +0 -1
  278. reconcile/utils/ocm/base.py +17 -20
  279. reconcile/utils/ocm/cluster_groups.py +1 -1
  280. reconcile/utils/ocm/identity_providers.py +2 -2
  281. reconcile/utils/ocm/labels.py +1 -1
  282. reconcile/utils/ocm/products.py +8 -8
  283. reconcile/utils/ocm/service_log.py +1 -1
  284. reconcile/utils/ocm/sre_capability_labels.py +20 -13
  285. reconcile/utils/openshift_resource.py +5 -0
  286. reconcile/utils/pagerduty_api.py +5 -2
  287. reconcile/utils/promotion_state.py +6 -11
  288. reconcile/utils/raw_github_api.py +1 -1
  289. reconcile/utils/rhcsv2_certs.py +1 -4
  290. reconcile/utils/runtime/integration.py +1 -1
  291. reconcile/utils/saasherder/interfaces.py +13 -20
  292. reconcile/utils/saasherder/models.py +23 -20
  293. reconcile/utils/saasherder/saasherder.py +26 -17
  294. reconcile/utils/slack_api.py +2 -2
  295. reconcile/utils/structs.py +1 -1
  296. reconcile/utils/terraform_client.py +1 -1
  297. reconcile/utils/terrascript_aws_client.py +47 -43
  298. reconcile/utils/unleash/server.py +2 -8
  299. reconcile/utils/vault.py +4 -11
  300. reconcile/utils/vcs.py +8 -8
  301. reconcile/vault_replication.py +1 -1
  302. tools/cli_commands/cost_report/cost_management_api.py +3 -3
  303. tools/cli_commands/cost_report/view.py +7 -6
  304. tools/cli_commands/erv2.py +3 -1
  305. tools/qontract_cli.py +6 -5
  306. tools/template_validation.py +3 -1
  307. {qontract_reconcile-0.10.2.dev394.dist-info → qontract_reconcile-0.10.2.dev414.dist-info}/WHEEL +0 -0
  308. {qontract_reconcile-0.10.2.dev394.dist-info → qontract_reconcile-0.10.2.dev414.dist-info}/entry_points.txt +0 -0
@@ -76,7 +76,7 @@ class RawGithubApi:
76
76
  if login is not None
77
77
  ]
78
78
 
79
- def team_invitations(self, org_id: str, team_id: str) -> list[str]:
79
+ def team_invitations(self, org_id: str | int, team_id: str | int) -> list[str]:
80
80
  invitations = self.query(f"/organizations/{org_id}/team/{team_id}/invitations")
81
81
 
82
82
  return [
@@ -9,15 +9,12 @@ from cryptography.x509.oid import NameOID
9
9
  from pydantic import BaseModel, Field
10
10
 
11
11
 
12
- class RhcsV2Cert(BaseModel):
12
+ class RhcsV2Cert(BaseModel, validate_by_name=True, validate_by_alias=True):
13
13
  certificate: str = Field(alias="tls.crt")
14
14
  private_key: str = Field(alias="tls.key")
15
15
  ca_cert: str = Field(alias="ca.crt")
16
16
  expiration_timestamp: int
17
17
 
18
- class Config:
19
- allow_population_by_field_name = True
20
-
21
18
 
22
19
  def extract_cert(text: str) -> re.Match:
23
20
  # The CA webform returns an HTML page with inline JS that builds an array of “outputList”
@@ -119,7 +119,7 @@ class PydanticRunParams(RunParams, BaseModel):
119
119
  def copy_and_update(
120
120
  self: PydanticRunParamsSelfTypeVar, update: dict[str, Any]
121
121
  ) -> PydanticRunParamsSelfTypeVar:
122
- return self.copy(update=update)
122
+ return self.model_copy(update=update)
123
123
 
124
124
  def get(self, field: str) -> Any:
125
125
  return getattr(self, field)
@@ -1,7 +1,7 @@
1
1
  # ruff: noqa: N801
2
2
  from __future__ import annotations
3
3
 
4
- from collections.abc import Mapping, Sequence, Set
4
+ from collections.abc import Sequence
5
5
  from typing import (
6
6
  TYPE_CHECKING,
7
7
  Any,
@@ -12,6 +12,8 @@ from typing import (
12
12
  from reconcile.utils import oc_connection_parameters
13
13
 
14
14
  if TYPE_CHECKING:
15
+ from pydantic.main import IncEx
16
+
15
17
  from reconcile.gql_definitions.fragments.saas_slo_document import SLODocument
16
18
  from reconcile.utils.secret_reader import HasSecret
17
19
 
@@ -27,18 +29,12 @@ class SaasFileSecretParameters(Protocol):
27
29
  @property
28
30
  def secret(self) -> HasSecret: ...
29
31
 
30
- def dict(
31
- self,
32
- *,
33
- by_alias: bool = False,
34
- include: AbstractSetIntStr | MappingIntStrAny | None = None,
32
+ def model_dump(
33
+ self, *, by_alias: bool = False, include: IncEx | None = None
35
34
  ) -> dict[str, Any]: ...
36
35
 
37
36
 
38
37
  SaasSecretParameters = Sequence[SaasFileSecretParameters] | None
39
- # Taken from pydantic.typing
40
- AbstractSetIntStr = Set[int | str]
41
- MappingIntStrAny = Mapping[int | str, Any]
42
38
 
43
39
 
44
40
  @runtime_checkable
@@ -212,11 +208,8 @@ class SaasResourceTemplateTargetNamespace(Protocol):
212
208
  @property
213
209
  def cluster(self) -> oc_connection_parameters.Cluster: ...
214
210
 
215
- def dict(
216
- self,
217
- *,
218
- by_alias: bool = False,
219
- include: AbstractSetIntStr | MappingIntStrAny | None = None,
211
+ def model_dump(
212
+ self, *, by_alias: bool = False, include: IncEx | None = None
220
213
  ) -> dict[str, Any]: ...
221
214
 
222
215
 
@@ -249,7 +242,7 @@ class SaasResourceTemplateTargetPromotion(Protocol):
249
242
  @property
250
243
  def promotion_data(self) -> Sequence[SaasPromotionData] | None: ...
251
244
 
252
- def dict(self, *, by_alias: bool = False) -> dict[str, Any]: ...
245
+ def model_dump(self, *, by_alias: bool = False) -> dict[str, Any]: ...
253
246
 
254
247
 
255
248
  class Channel(Protocol):
@@ -274,7 +267,7 @@ class SaasPromotion(Protocol):
274
267
  @property
275
268
  def subscribe(self) -> list[Channel] | None: ...
276
269
 
277
- def dict(self, *, by_alias: bool = False) -> dict[str, Any]: ...
270
+ def model_dump(self, *, by_alias: bool = False) -> dict[str, Any]: ...
278
271
 
279
272
 
280
273
  class SaasResourceTemplateTarget_SaasSecretParameters(Protocol):
@@ -295,7 +288,7 @@ class SaasResourceTemplateTargetUpstream(Protocol):
295
288
  @property
296
289
  def instance(self) -> SaasJenkinsInstance: ...
297
290
 
298
- def dict(self, *, by_alias: bool = False) -> dict[str, Any]: ...
291
+ def model_dump(self, *, by_alias: bool = False) -> dict[str, Any]: ...
299
292
 
300
293
 
301
294
  class SaasQuayInstance(Protocol):
@@ -315,7 +308,7 @@ class SaasResourceTemplateTargetImage(Protocol):
315
308
  @property
316
309
  def org(self) -> SaasQuayOrg: ...
317
310
 
318
- def dict(self, *, by_alias: bool = False) -> dict[str, Any]: ...
311
+ def model_dump(self, *, by_alias: bool = False) -> dict[str, Any]: ...
319
312
 
320
313
 
321
314
  class SaasResourceTemplateTarget(HasParameters, HasSecretParameters, Protocol):
@@ -344,7 +337,7 @@ class SaasResourceTemplateTarget(HasParameters, HasSecretParameters, Protocol):
344
337
  self, parent_saas_file_name: str, parent_resource_template_name: str
345
338
  ) -> str: ...
346
339
 
347
- def dict(self, *, by_alias: bool = False) -> dict[str, Any]: ...
340
+ def model_dump(self, *, by_alias: bool = False) -> dict[str, Any]: ...
348
341
 
349
342
 
350
343
  class SaasResourceTemplate(HasParameters, HasSecretParameters, Protocol):
@@ -381,7 +374,7 @@ class ManagedResourceName(Protocol):
381
374
  resource: str
382
375
  resource_names: list[str]
383
376
 
384
- def dict(self) -> dict[str, str]: ...
377
+ def model_dump(self) -> dict[str, str]: ...
385
378
 
386
379
 
387
380
  class SaasFile(HasParameters, HasSecretParameters, Protocol):
@@ -7,10 +7,7 @@ from enum import Enum
7
7
  from typing import Any, NotRequired, TypedDict
8
8
 
9
9
  from github import Github
10
- from pydantic import (
11
- BaseModel,
12
- Field,
13
- )
10
+ from pydantic import BaseModel, Field, model_validator
14
11
 
15
12
  from reconcile.gql_definitions.fragments.saas_slo_document import (
16
13
  SLODocument,
@@ -207,7 +204,12 @@ TriggerSpecUnion = (
207
204
  )
208
205
 
209
206
 
210
- class Namespace(BaseModel):
207
+ class Namespace(
208
+ BaseModel,
209
+ validate_by_name=True,
210
+ validate_by_alias=True,
211
+ arbitrary_types_allowed=True,
212
+ ):
211
213
  name: str
212
214
  environment: SaasEnvironment
213
215
  app: SaasApp
@@ -217,29 +219,19 @@ class Namespace(BaseModel):
217
219
  ..., alias="managedResourceNames"
218
220
  )
219
221
 
220
- class Config:
221
- arbitrary_types_allowed = True
222
- allow_population_by_field_name = True
223
-
224
222
 
225
- class PromotionChannelData(BaseModel):
223
+ class PromotionChannelData(BaseModel, validate_by_name=True, validate_by_alias=True):
226
224
  q_type: str = Field(..., alias="type")
227
225
 
228
- class Config:
229
- allow_population_by_field_name = True
230
226
 
231
-
232
- class ParentSaasPromotion(BaseModel):
227
+ class ParentSaasPromotion(BaseModel, validate_by_name=True, validate_by_alias=True):
233
228
  q_type: str = Field(..., alias="type")
234
- parent_saas: str | None
235
- target_config_hash: str | None
236
-
237
- class Config:
238
- allow_population_by_field_name = True
229
+ parent_saas: str | None = None
230
+ target_config_hash: str | None = None
239
231
 
240
232
 
241
233
  class PromotionData(BaseModel):
242
- channel: str | None
234
+ channel: str | None = None
243
235
  data: list[ParentSaasPromotion | PromotionChannelData] | None = None
244
236
 
245
237
 
@@ -264,6 +256,17 @@ class Promotion(BaseModel):
264
256
  saas_file_paths: list[str] | None = None
265
257
  target_paths: list[str] | None = None
266
258
 
259
+ @model_validator(mode="before")
260
+ @classmethod
261
+ def handle_gql_classes(cls, data: Any) -> Any:
262
+ if data.get("promotion_data"):
263
+ data["promotion_data"] = [
264
+ # convert a GQL class to a dict
265
+ item.model_dump() if hasattr(item, "model_dump") else item
266
+ for item in data["promotion_data"]
267
+ ]
268
+ return data
269
+
267
270
 
268
271
  @dataclass
269
272
  class ImageAuth:
@@ -765,7 +765,8 @@ class SaasHerder:
765
765
  case "gitlab":
766
766
  if not self.gitlab:
767
767
  raise Exception("gitlab is not initialized")
768
- 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}")
769
770
  content = self.gitlab.get_raw_file(
770
771
  project=project,
771
772
  path=path,
@@ -801,7 +802,8 @@ class SaasHerder:
801
802
  case "gitlab":
802
803
  if not self.gitlab:
803
804
  raise Exception("gitlab is not initialized")
804
- 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}")
805
807
  dir_contents = self.gitlab.get_directory_contents(
806
808
  project,
807
809
  ref=commit_sha,
@@ -826,7 +828,8 @@ class SaasHerder:
826
828
  case "gitlab":
827
829
  if not self.gitlab:
828
830
  raise Exception("gitlab is not initialized")
829
- 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}")
830
833
  commits = project.commits.list(ref_name=ref, per_page=1, page=1)
831
834
  return commits[0].id
832
835
  case _:
@@ -1179,13 +1182,13 @@ class SaasHerder:
1179
1182
  images_list = threaded.run(
1180
1183
  self._collect_images, resources, self.available_thread_pool_size
1181
1184
  )
1182
- images = set(itertools.chain.from_iterable(images_list))
1183
- self.images.update(images)
1184
- if not images:
1185
+ images_set = set(itertools.chain.from_iterable(images_list))
1186
+ self.images.update(images_set)
1187
+ if not images_set:
1185
1188
  return False # no errors
1186
1189
  images = threaded.run(
1187
1190
  self._get_image,
1188
- images,
1191
+ images_set,
1189
1192
  self.available_thread_pool_size,
1190
1193
  image_patterns=spec.image_patterns,
1191
1194
  image_auth=spec.image_auth,
@@ -1250,7 +1253,9 @@ class SaasHerder:
1250
1253
  self.saas_files,
1251
1254
  self.thread_pool_size,
1252
1255
  )
1253
- desired_state_specs = list(itertools.chain.from_iterable(results))
1256
+ desired_state_specs: list[TargetSpec] = list(
1257
+ itertools.chain.from_iterable(results)
1258
+ )
1254
1259
  promotions = threaded.run(
1255
1260
  self.populate_desired_state_saas_file,
1256
1261
  desired_state_specs,
@@ -1898,21 +1903,23 @@ class SaasHerder:
1898
1903
  name=target.name,
1899
1904
  ref=target.ref,
1900
1905
  promotion=(
1901
- target.promotion.dict(by_alias=True) if target.promotion else None
1906
+ target.promotion.model_dump(by_alias=True) if target.promotion else None
1902
1907
  ),
1903
1908
  secretParameters=(
1904
- [p.dict(by_alias=True) for p in target.secret_parameters]
1909
+ [p.model_dump(by_alias=True) for p in target.secret_parameters]
1905
1910
  if target.secret_parameters
1906
1911
  else None
1907
1912
  ),
1908
1913
  slos=(
1909
- [slo.dict(by_alias=True) for slo in target.slos]
1914
+ [slo.model_dump(by_alias=True) for slo in target.slos]
1910
1915
  if target.slos
1911
1916
  else None
1912
1917
  ),
1913
- 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
+ ),
1914
1921
  images=(
1915
- [i.dict(by_alias=True) for i in target.images]
1922
+ [i.model_dump(by_alias=True) for i in target.images]
1916
1923
  if target.images
1917
1924
  else None
1918
1925
  ),
@@ -1948,16 +1955,16 @@ class SaasHerder:
1948
1955
  )
1949
1956
  if saas_file.managed_resource_names:
1950
1957
  state_content["saas_file_managed_resource_names"] = [
1951
- m.dict() for m in saas_file.managed_resource_names
1958
+ m.model_dump() for m in saas_file.managed_resource_names
1952
1959
  ]
1953
1960
  # include secret parameters from resource template and saas file
1954
1961
  if resource_template.secret_parameters:
1955
1962
  state_content["rt_secretparameters"] = [
1956
- p.dict() for p in resource_template.secret_parameters
1963
+ p.model_dump() for p in resource_template.secret_parameters
1957
1964
  ]
1958
1965
  if saas_file.secret_parameters:
1959
1966
  state_content["saas_file_secretparameters"] = [
1960
- p.dict() for p in saas_file.secret_parameters
1967
+ p.model_dump() for p in saas_file.secret_parameters
1961
1968
  ]
1962
1969
  return state_content
1963
1970
 
@@ -2240,7 +2247,9 @@ class SaasHerder:
2240
2247
  for rt in saas_file.resource_templates:
2241
2248
  for target in rt.targets:
2242
2249
  template_vars = {
2243
- "resource": {"namespace": target.namespace.dict(by_alias=True)}
2250
+ "resource": {
2251
+ "namespace": target.namespace.model_dump(by_alias=True)
2252
+ }
2244
2253
  }
2245
2254
  if target.parameters:
2246
2255
  for param in target.parameters:
@@ -71,14 +71,14 @@ class HasClientGlobalConfig(Protocol):
71
71
  max_retries: int | None
72
72
  timeout: int | None
73
73
 
74
- def dict(self) -> dict[str, int | None]: ...
74
+ def model_dump(self) -> dict[str, int | None]: ...
75
75
 
76
76
 
77
77
  class HasClientMethodConfig(Protocol):
78
78
  name: str
79
79
  args: Any
80
80
 
81
- def dict(self) -> dict[str, str]: ...
81
+ def model_dump(self) -> dict[str, str]: ...
82
82
 
83
83
 
84
84
  class HasClientConfig(Protocol):
@@ -1,4 +1,4 @@
1
- from pydantic.dataclasses import dataclass
1
+ from dataclasses import dataclass
2
2
 
3
3
 
4
4
  @dataclass
@@ -223,7 +223,7 @@ class TerraformClient:
223
223
  if disable_deletions_detected:
224
224
  raise RuntimeError("Terraform plan has disabled deletions detected")
225
225
 
226
- @retry(no_retry_exceptions=RdsUpgradeValidationError)
226
+ @retry(no_retry_exceptions=(RdsUpgradeValidationError,))
227
227
  def terraform_plan(
228
228
  self, spec: TerraformSpec, enable_deletion: bool
229
229
  ) -> tuple[bool, list[AccountUser], bool]:
@@ -1058,7 +1058,9 @@ class TerrascriptClient:
1058
1058
  ignore_changes = (
1059
1059
  "all" if "all" in lifecycle.ignore_changes else lifecycle.ignore_changes
1060
1060
  )
1061
- return lifecycle.dict(by_alias=True) | {"ignore_changes": ignore_changes}
1061
+ return lifecycle.model_dump(by_alias=True) | {
1062
+ "ignore_changes": ignore_changes
1063
+ }
1062
1064
  return None
1063
1065
 
1064
1066
  def populate_additional_providers(
@@ -1424,7 +1426,7 @@ class TerrascriptClient:
1424
1426
  req_account_name = req_account.name
1425
1427
  # Accepter's side of the connection - the cluster's account
1426
1428
  acc_account = accepter.account
1427
- acc_alias = self.get_provider_alias(acc_account.dict(by_alias=True))
1429
+ acc_alias = self.get_provider_alias(acc_account.model_dump(by_alias=True))
1428
1430
  acc_uid = acc_account.uid
1429
1431
  if acc_account.assume_role:
1430
1432
  acc_uid = awsh.get_account_uid_from_arn(acc_account.assume_role)
@@ -2210,6 +2212,43 @@ class TerrascriptClient:
2210
2212
  letters_and_digits = string.ascii_letters + string.digits
2211
2213
  return "".join(random.choice(letters_and_digits) for i in range(string_length))
2212
2214
 
2215
+ @staticmethod
2216
+ def _build_tf_resource_s3_lifecycle_rules(
2217
+ versioning: bool,
2218
+ common_values: Mapping[str, Any],
2219
+ ) -> list[dict]:
2220
+ lifecycle_rules = common_values.get("lifecycle_rules") or []
2221
+ if versioning and not any(
2222
+ "noncurrent_version_expiration" in lr for lr in lifecycle_rules
2223
+ ):
2224
+ # Add a default noncurrent object expiration rule
2225
+ # if one isn't already set
2226
+ rule = {
2227
+ "id": "expire_noncurrent_versions",
2228
+ "enabled": True,
2229
+ "noncurrent_version_expiration": {"days": 30},
2230
+ "expiration": {"expired_object_delete_marker": True},
2231
+ "abort_incomplete_multipart_upload_days": 3,
2232
+ }
2233
+ lifecycle_rules.append(rule)
2234
+
2235
+ if storage_class := common_values.get("storage_class"):
2236
+ sc = storage_class.upper()
2237
+ days = "1"
2238
+ if sc.endswith("_IA"):
2239
+ # Infrequent Access storage class has minimum 30 days
2240
+ # before transition
2241
+ days = "30"
2242
+ rule = {
2243
+ "id": sc + "_storage_class",
2244
+ "enabled": True,
2245
+ "transition": {"days": days, "storage_class": sc},
2246
+ "noncurrent_version_transition": {"days": days, "storage_class": sc},
2247
+ }
2248
+ lifecycle_rules.append(rule)
2249
+
2250
+ return lifecycle_rules
2251
+
2213
2252
  def populate_tf_resource_s3(self, spec: ExternalResourceSpec) -> aws_s3_bucket:
2214
2253
  account = spec.provisioner_name
2215
2254
  identifier = spec.identifier
@@ -2249,47 +2288,11 @@ class TerrascriptClient:
2249
2288
  request_payer = common_values.get("request_payer")
2250
2289
  if request_payer:
2251
2290
  values["request_payer"] = request_payer
2252
- lifecycle_rules = common_values.get("lifecycle_rules")
2253
- if lifecycle_rules:
2254
- # common_values['lifecycle_rules'] is a list of lifecycle_rules
2291
+ if lifecycle_rules := self._build_tf_resource_s3_lifecycle_rules(
2292
+ versioning=versioning,
2293
+ common_values=common_values,
2294
+ ):
2255
2295
  values["lifecycle_rule"] = lifecycle_rules
2256
- if versioning:
2257
- lrs = values.get("lifecycle_rule", [])
2258
- expiration_rule = False
2259
- for lr in lrs:
2260
- if "noncurrent_version_expiration" in lr:
2261
- expiration_rule = True
2262
- break
2263
- if not expiration_rule:
2264
- # Add a default noncurrent object expiration rule if
2265
- # if one isn't already set
2266
- rule = {
2267
- "id": "expire_noncurrent_versions",
2268
- "enabled": "true",
2269
- "noncurrent_version_expiration": {"days": 30},
2270
- }
2271
- if len(lrs) > 0:
2272
- lrs.append(rule)
2273
- else:
2274
- lrs = rule
2275
- sc = common_values.get("storage_class")
2276
- if sc:
2277
- sc = sc.upper()
2278
- days = "1"
2279
- if sc.endswith("_IA"):
2280
- # Infrequent Access storage class has minimum 30 days
2281
- # before transition
2282
- days = "30"
2283
- rule = {
2284
- "id": sc + "_storage_class",
2285
- "enabled": "true",
2286
- "transition": {"days": days, "storage_class": sc},
2287
- "noncurrent_version_transition": {"days": days, "storage_class": sc},
2288
- }
2289
- if values.get("lifecycle_rule"):
2290
- values["lifecycle_rule"].append(rule)
2291
- else:
2292
- values["lifecycle_rule"] = rule
2293
2296
  cors_rules = common_values.get("cors_rules")
2294
2297
  if cors_rules:
2295
2298
  # common_values['cors_rules'] is a list of cors_rules
@@ -5907,7 +5910,8 @@ class TerrascriptClient:
5907
5910
  return commit.sha
5908
5911
  case "gitlab":
5909
5912
  gitlab = self.init_gitlab()
5910
- project = gitlab.get_project(url)
5913
+ if not (project := gitlab.get_project(url)):
5914
+ raise ValueError(f"could not find gitlab project for url {url}")
5911
5915
  commits = project.commits.list(ref_name=ref, per_page=1, page=1)
5912
5916
  return commits[0].id
5913
5917
  case _:
@@ -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
@@ -6,7 +6,7 @@ import threading
6
6
  import time
7
7
  from collections.abc import Mapping
8
8
  from functools import lru_cache
9
- from typing import Any, Self, TypedDict
9
+ from typing import Any, Self
10
10
 
11
11
  import hvac
12
12
  import requests
@@ -48,13 +48,6 @@ class VaultConnectionError(Exception):
48
48
  pass
49
49
 
50
50
 
51
- class Secret(TypedDict):
52
- path: str
53
- field: str
54
- format: str | None
55
- version: str | None
56
-
57
-
58
51
  SECRET_VERSION_LATEST = "LATEST"
59
52
 
60
53
 
@@ -197,7 +190,7 @@ class VaultClient:
197
190
  self._client.auth_approle(self.role_id, self.secret_id)
198
191
 
199
192
  @retry()
200
- 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]:
201
194
  """Returns a dictionary of keys and values in a Vault secret and the
202
195
  version of the secret, for V1 secrets, version will be None.
203
196
 
@@ -250,7 +243,7 @@ class VaultClient:
250
243
 
251
244
  def __read_all_v2(
252
245
  self, path: str, version: str | None
253
- ) -> tuple[dict[str, Any], str | None]:
246
+ ) -> tuple[dict[str, Any], int]:
254
247
  path_split = path.split("/")
255
248
  mount_point = path_split[0]
256
249
  read_path = "/".join(path_split[1:])
@@ -294,7 +287,7 @@ class VaultClient:
294
287
  return secret["data"]
295
288
 
296
289
  @retry()
297
- def read(self, secret: Secret) -> Any:
290
+ def read(self, secret: Mapping[str, Any]) -> Any:
298
291
  """Returns a value of a key in a Vault secret.
299
292
 
300
293
  The input secret is a dictionary which contains the following fields:
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}")
@@ -217,7 +217,7 @@ def copy_vault_secret(
217
217
  return
218
218
 
219
219
  # If we reach here, we successfully read the destination secret
220
- if dest_version is None and version is None:
220
+ if dest_version is None or version is None:
221
221
  # v1 secrets don't have version
222
222
  if source_data == dest_data:
223
223
  # If the secret is the same in both vaults, we don't need
@@ -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,