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
@@ -164,7 +164,9 @@ class GlitchtipClient(ApiBase):
164
164
  return ProjectAlert(
165
165
  **self._post(
166
166
  f"/api/0/projects/{organization_slug}/{project_slug}/alerts/",
167
- data=alert.dict(by_alias=True, exclude_unset=True, exclude_none=True),
167
+ data=alert.model_dump(
168
+ by_alias=True, exclude_unset=True, exclude_none=True
169
+ ),
168
170
  )
169
171
  )
170
172
 
@@ -183,7 +185,9 @@ class GlitchtipClient(ApiBase):
183
185
  return ProjectAlert(
184
186
  **self._put(
185
187
  f"/api/0/projects/{organization_slug}/{project_slug}/alerts/{alert.pk}/",
186
- data=alert.dict(by_alias=True, exclude_unset=True, exclude_none=True),
188
+ data=alert.model_dump(
189
+ by_alias=True, exclude_unset=True, exclude_none=True
190
+ ),
187
191
  )
188
192
  )
189
193
 
@@ -3,13 +3,13 @@ from __future__ import annotations
3
3
  import re
4
4
  from datetime import datetime
5
5
  from enum import Enum
6
- from typing import TYPE_CHECKING, Any
6
+ from typing import TYPE_CHECKING, Any, Self
7
7
 
8
8
  from pydantic import (
9
9
  BaseModel,
10
10
  Field,
11
- root_validator,
12
- validator,
11
+ field_validator,
12
+ model_validator,
13
13
  )
14
14
 
15
15
  if TYPE_CHECKING:
@@ -49,7 +49,8 @@ class Team(BaseModel):
49
49
  slug: str = ""
50
50
  users: list[User] = []
51
51
 
52
- @root_validator(pre=True)
52
+ @model_validator(mode="before")
53
+ @classmethod
53
54
  def name_xor_slug_must_be_set(
54
55
  cls, values: MutableMapping[str, Any]
55
56
  ) -> MutableMapping[str, Any]:
@@ -58,11 +59,11 @@ class Team(BaseModel):
58
59
  ), "name xor slug must be set!"
59
60
  return values
60
61
 
61
- @root_validator
62
- def slugify(cls, values: MutableMapping[str, Any]) -> MutableMapping[str, Any]:
63
- values["slug"] = values.get("slug") or slugify(values.get("name", ""))
64
- values["name"] = slugify(values.get("name", "")) or values.get("slug")
65
- return values
62
+ @model_validator(mode="after")
63
+ def slugify(self) -> Self:
64
+ self.slug = self.slug or slugify(self.name)
65
+ self.name = slugify(self.name) or self.slug
66
+ return self
66
67
 
67
68
  def __lt__(self, other: Team) -> bool:
68
69
  return self.slug < other.slug
@@ -86,16 +87,15 @@ class RecipientType(Enum):
86
87
  WEBHOOK = "webhook"
87
88
 
88
89
 
89
- class ProjectAlertRecipient(BaseModel):
90
+ class ProjectAlertRecipient(
91
+ BaseModel, validate_by_name=True, validate_by_alias=True, use_enum_values=True
92
+ ):
90
93
  pk: int | None = Field(None, alias="id")
91
94
  recipient_type: RecipientType = Field(..., alias="recipientType")
92
95
  url: str = ""
93
96
 
94
- class Config:
95
- allow_population_by_field_name = True
96
- use_enum_values = True
97
-
98
- @validator("recipient_type")
97
+ @field_validator("recipient_type")
98
+ @classmethod
99
99
  def recipient_type_enforce_enum_type(cls, v: str | RecipientType) -> RecipientType:
100
100
  if isinstance(v, RecipientType):
101
101
  return v
@@ -113,17 +113,15 @@ class ProjectAlertRecipient(BaseModel):
113
113
  return hash((self.recipient_type, self.url))
114
114
 
115
115
 
116
- class ProjectAlert(BaseModel):
116
+ class ProjectAlert(BaseModel, validate_by_name=True, validate_by_alias=True):
117
117
  pk: int | None = Field(None, alias="id")
118
118
  name: str
119
119
  timespan_minutes: int = Field(..., alias="timespanMinutes")
120
120
  quantity: int
121
121
  recipients: list[ProjectAlertRecipient] = Field([], alias="alertRecipients")
122
122
 
123
- class Config:
124
- allow_population_by_field_name = True
125
-
126
- @root_validator
123
+ @model_validator(mode="before")
124
+ @classmethod
127
125
  def empty_name(cls, values: MutableMapping[str, Any]) -> MutableMapping[str, Any]:
128
126
  # name is an empty string if the alert was created manually because it can't be set via UI
129
127
  # use the pk instead.
@@ -141,20 +139,18 @@ class ProjectAlert(BaseModel):
141
139
  )
142
140
 
143
141
 
144
- class Project(BaseModel):
142
+ class Project(BaseModel, validate_by_name=True, validate_by_alias=True):
145
143
  pk: int | None = Field(None, alias="id")
146
144
  name: str
147
145
  slug: str = ""
148
- platform: str | None
146
+ platform: str | None = None
149
147
  teams: list[Team] = []
150
148
  alerts: list[ProjectAlert] = []
151
149
  event_throttle_rate: int = Field(0, alias="eventThrottleRate")
152
150
  organization: Organization | None = None
153
151
 
154
- class Config:
155
- allow_population_by_field_name = True
156
-
157
- @root_validator
152
+ @model_validator(mode="before")
153
+ @classmethod
158
154
  def slugify(cls, values: MutableMapping[str, Any]) -> MutableMapping[str, Any]:
159
155
  values["slug"] = values.get("slug") or slugify(values["name"])
160
156
  return values
@@ -195,7 +191,8 @@ class Organization(BaseModel):
195
191
  teams: list[Team] = []
196
192
  users: list[User] = []
197
193
 
198
- @root_validator
194
+ @model_validator(mode="before")
195
+ @classmethod
199
196
  def slugify(cls, values: MutableMapping[str, Any]) -> MutableMapping[str, Any]:
200
197
  values["slug"] = values.get("slug") or slugify(values["name"])
201
198
  return values
@@ -212,4 +209,4 @@ class Organization(BaseModel):
212
209
  return hash(self.name)
213
210
 
214
211
 
215
- Project.update_forward_refs()
212
+ Project.model_rebuild()
reconcile/utils/gql.py CHANGED
@@ -109,7 +109,7 @@ class GqlApi:
109
109
  if int_name:
110
110
  integrations = self.query(INTEGRATIONS_QUERY, skip_validation=True)
111
111
 
112
- for integration in integrations["integrations"]:
112
+ for integration in integrations["integrations"] if integrations else []:
113
113
  if integration["name"] == int_name:
114
114
  self._valid_schemas = integration["schemas"]
115
115
  break
@@ -142,7 +142,7 @@ class GqlApi:
142
142
  query: str,
143
143
  variables: dict[str, Any] | None = None,
144
144
  skip_validation: bool = False,
145
- ) -> dict[str, Any] | None:
145
+ ) -> dict[str, Any]:
146
146
  try:
147
147
  result = self.client.execute(
148
148
  gql(query), variables, get_execution_result=True
@@ -172,11 +172,8 @@ class GqlApi:
172
172
  if forbidden_schemas:
173
173
  raise GqlApiErrorForbiddenSchemaError(forbidden_schemas)
174
174
 
175
- # This is to appease mypy. This exception won't be thrown as this condition
176
- # is already handled above with AssertionError
177
- if result["data"] is None:
178
- raise GqlApiError("`data` not received in GraphQL payload")
179
-
175
+ # make mypy happy
176
+ assert "data" in result and result["data"] is not None
180
177
  return result["data"]
181
178
 
182
179
  def get_template(self, path: str) -> dict[str, str]:
@@ -40,7 +40,7 @@ class InstrumentedImage(Image):
40
40
 
41
41
 
42
42
  class InstrumentedSkopeo(Skopeo):
43
- def copy(self, *args: Any, **kwargs: Any) -> bytes | str:
43
+ def copy(self, *args: Any, **kwargs: Any) -> None:
44
44
  metrics.copy_count.labels(
45
45
  integration=INTEGRATION_NAME, shard=SHARDS, shard_id=SHARD_ID
46
46
  ).inc()
@@ -140,7 +140,7 @@ class InternalGroupsClient:
140
140
  with self._api as api:
141
141
  return Group(
142
142
  **api.create_group(
143
- data=group.dict(by_alias=True),
143
+ data=group.model_dump(by_alias=True),
144
144
  )
145
145
  )
146
146
 
@@ -155,6 +155,6 @@ class InternalGroupsClient:
155
155
  return Group(
156
156
  **api.update_group(
157
157
  name=group.name,
158
- data=group.dict(by_alias=True),
158
+ data=group.model_dump(by_alias=True),
159
159
  )
160
160
  )
@@ -27,7 +27,7 @@ class Entity(BaseModel):
27
27
  return hash(self.id)
28
28
 
29
29
 
30
- class Group(BaseModel):
30
+ class Group(BaseModel, validate_by_name=True, validate_by_alias=True):
31
31
  name: str
32
32
  description: str
33
33
  member_approval_type: str = Field("self-service", alias="memberApprovalType")
@@ -35,16 +35,18 @@ class Group(BaseModel):
35
35
  owners: list[Entity]
36
36
  display_name: str = Field(..., alias="displayName")
37
37
  notes: str | None = None
38
- rover_group_member_query: str | None = Field(None, alias="roverGroupMemberQuery")
38
+ rover_group_member_query: str | None = Field(
39
+ None, alias="roverGroupMemberQuery", exclude=True
40
+ )
39
41
  rover_group_inclusions: list[Entity] | None = Field(
40
- None, alias="roverGroupInclusions"
42
+ None, alias="roverGroupInclusions", exclude=True
41
43
  )
42
44
  rover_group_exclusions: list[Entity] | None = Field(
43
- None, alias="roverGroupExclusions"
45
+ None, alias="roverGroupExclusions", exclude=True
44
46
  )
45
47
  members: list[Entity] = []
46
- member_of: list[str] | None = Field(None, alias="memberOf")
47
- namespace: str | None = None
48
+ member_of: list[str] | None = Field(None, alias="memberOf", exclude=True)
49
+ namespace: str | None = Field(None, exclude=True)
48
50
 
49
51
  def __eq__(self, other: object) -> bool:
50
52
  if not isinstance(other, Group):
@@ -58,14 +60,3 @@ class Group(BaseModel):
58
60
  and self.notes == other.notes
59
61
  and set(self.members) == set(other.members)
60
62
  )
61
-
62
- class Config:
63
- allow_population_by_field_name = True
64
- # exclude read-only fields in the json/dict dumps
65
- fields = {
66
- "rover_group_member_query": {"exclude": True},
67
- "rover_group_inclusions": {"exclude": True},
68
- "rover_group_exclusions": {"exclude": True},
69
- "member_of": {"exclude": True},
70
- "namespace": {"exclude": True},
71
- }
@@ -45,14 +45,11 @@ class Jinja2TemplateError(Exception):
45
45
  super().__init__("error processing jinja2 template: " + str(msg))
46
46
 
47
47
 
48
- class TemplateRenderOptions(BaseModel):
48
+ class TemplateRenderOptions(BaseModel, frozen=True):
49
49
  trim_blocks: bool
50
50
  lstrip_blocks: bool
51
51
  keep_trailing_newline: bool
52
52
 
53
- class Config:
54
- frozen = True
55
-
56
53
  @classmethod
57
54
  def create(
58
55
  cls,
@@ -75,7 +72,7 @@ def compile_jinja2_template(
75
72
  ) -> Any:
76
73
  if not template_render_options:
77
74
  template_render_options = TemplateRenderOptions.create()
78
- env: dict[str, Any] = template_render_options.dict()
75
+ env: dict[str, Any] = template_render_options.model_dump()
79
76
  if extra_curly:
80
77
  env.update({
81
78
  "block_start_string": "{{%",
@@ -100,7 +100,7 @@ class K8sJobController:
100
100
  """
101
101
  new_cache = {}
102
102
  for item in self.oc.get_items(
103
- kind="Job",
103
+ kind="Job.batch",
104
104
  namespace=self.namespace,
105
105
  ):
106
106
  openshift_resource = OpenshiftResource(
@@ -38,6 +38,8 @@ class JobValidationError(Exception):
38
38
 
39
39
 
40
40
  JOB_GENERATION_ANNOTATION = "qontract-reconcile/job.generation"
41
+ MAX_JOB_NAME_LENGTH = 63
42
+ UNIT_OF_WORK_DIGEST_LENGTH = 10
41
43
 
42
44
 
43
45
  class K8sJob(ABC):
@@ -72,7 +74,21 @@ class K8sJob(ABC):
72
74
  """
73
75
 
74
76
  def name(self) -> str:
75
- return f"{self.name_prefix()}-{self.unit_of_work_digest()}"
77
+ """
78
+ Generate the full job name by combining the name prefix with a digest.
79
+
80
+ The name is constructed from the name_prefix (truncated to ensure total
81
+ length compliance) and the unit_of_work_digest. The total length is
82
+ limited to MAX_JOB_NAME_LENGTH (63 characters) to comply with Kubernetes
83
+ naming constraints.
84
+
85
+ Returns:
86
+ A unique job name in the format: {name_prefix}-{digest}
87
+ """
88
+ prefix = self.name_prefix()[
89
+ : MAX_JOB_NAME_LENGTH - UNIT_OF_WORK_DIGEST_LENGTH - 1
90
+ ]
91
+ return f"{prefix}-{self.unit_of_work_digest(UNIT_OF_WORK_DIGEST_LENGTH)}"
76
92
 
77
93
  @abstractmethod
78
94
  def name_prefix(self) -> str:
reconcile/utils/json.py CHANGED
@@ -1,15 +1,48 @@
1
1
  import json
2
- from typing import Any
2
+ from collections.abc import Callable
3
+ from dataclasses import asdict, is_dataclass
4
+ from datetime import date, datetime
5
+ from decimal import Decimal
6
+ from enum import Enum
7
+ from typing import Any, Literal
8
+
9
+ from pydantic import BaseModel
3
10
 
4
11
  JSON_COMPACT_SEPARATORS = (",", ":")
5
12
 
6
13
 
14
+ def pydantic_encoder(obj: Any) -> Any:
15
+ if isinstance(obj, BaseModel):
16
+ return obj.model_dump()
17
+
18
+ if is_dataclass(obj):
19
+ return asdict(obj) # type: ignore
20
+
21
+ if isinstance(obj, (datetime, date)):
22
+ return obj.isoformat()
23
+
24
+ if isinstance(obj, Enum):
25
+ return obj.value
26
+
27
+ if isinstance(obj, Decimal):
28
+ return float(obj)
29
+
30
+ raise TypeError(
31
+ f"Object of type '{obj.__class__.__name__}' is not JSON serializable"
32
+ )
33
+
34
+
7
35
  def json_dumps(
8
36
  data: Any,
9
37
  *,
10
38
  compact: bool = False,
11
39
  indent: int | None = None,
12
40
  cls: type[json.JSONEncoder] | None = None,
41
+ defaults: Callable | None = None,
42
+ # BaseModel dump parameters
43
+ by_alias: bool = True,
44
+ exclude_none: bool = False,
45
+ mode: Literal["json", "python"] = "json",
13
46
  ) -> str:
14
47
  """
15
48
  Serialize `data` to a consistent JSON formatted `str` with dict keys sorted.
@@ -22,6 +55,10 @@ def json_dumps(
22
55
  Returns:
23
56
  A JSON formatted string.
24
57
  """
58
+ if isinstance(data, BaseModel):
59
+ data = data.model_dump(mode=mode, by_alias=by_alias, exclude_none=exclude_none)
60
+ if mode == "python":
61
+ defaults = pydantic_encoder
25
62
  separators = JSON_COMPACT_SEPARATORS if compact else None
26
63
  return json.dumps(
27
64
  data,
@@ -29,4 +66,5 @@ def json_dumps(
29
66
  separators=separators,
30
67
  sort_keys=True,
31
68
  cls=cls,
69
+ default=defaults,
32
70
  )
@@ -55,6 +55,8 @@ def resolve_app_interface_membership_source(
55
55
 
56
56
  def build_member_list(role: RoleV1) -> list[RoleMember]:
57
57
  members: list[RoleMember] = []
58
- members.extend([RoleUser(**u.dict()) for u in role.users or []])
59
- members.extend([RoleBot(**b.dict()) for b in role.bots or [] if b.org_username])
58
+ members.extend([RoleUser(**u.model_dump()) for u in role.users or []])
59
+ members.extend([
60
+ RoleBot(**b.model_dump()) for b in role.bots or [] if b.org_username
61
+ ])
60
62
  return members
@@ -7,7 +7,6 @@ from typing import (
7
7
 
8
8
  from pydantic import (
9
9
  BaseModel,
10
- Extra,
11
10
  )
12
11
 
13
12
  from reconcile.gql_definitions.fragments.membership_source import (
@@ -23,7 +22,7 @@ class User(Protocol):
23
22
  @property
24
23
  def org_username(self) -> str: ...
25
24
 
26
- def dict(self, *, by_alias: bool = False) -> dict[str, Any]: ...
25
+ def model_dump(self, *, by_alias: bool = False) -> dict[str, Any]: ...
27
26
 
28
27
 
29
28
  class Bot(Protocol):
@@ -33,7 +32,7 @@ class Bot(Protocol):
33
32
  @property
34
33
  def org_username(self) -> str | None: ...
35
34
 
36
- def dict(self, *, by_alias: bool = False) -> dict[str, Any]: ...
35
+ def model_dump(self, *, by_alias: bool = False) -> dict[str, Any]: ...
37
36
 
38
37
 
39
38
  class RoleWithMemberships(Protocol):
@@ -50,33 +49,27 @@ class RoleWithMemberships(Protocol):
50
49
  def member_sources(self) -> Sequence[RoleMembershipSource] | None: ...
51
50
 
52
51
 
53
- class RoleUser(BaseModel):
52
+ class RoleUser(BaseModel, extra="ignore"):
54
53
  name: str
55
54
  org_username: str
56
- github_username: str | None
57
- quay_username: str | None
58
- pagerduty_username: str | None
59
- aws_username: str | None
60
- cloudflare_user: str | None
61
- public_gpg_key: str | None
55
+ github_username: str | None = None
56
+ quay_username: str | None = None
57
+ pagerduty_username: str | None = None
58
+ aws_username: str | None = None
59
+ cloudflare_user: str | None = None
60
+ public_gpg_key: str | None = None
62
61
  tag_on_cluster_updates: bool | None = False
63
62
  tag_on_merge_requests: bool | None = False
64
63
 
65
- class Config:
66
- extra = Extra.ignore
67
64
 
68
-
69
- class RoleBot(BaseModel):
65
+ class RoleBot(BaseModel, extra="ignore"):
70
66
  name: str
71
- description: str | None
72
- org_username: str | None
73
- github_username: str | None
74
- gitlab_username: str | None
75
- openshift_serviceaccount: str | None
76
- quay_username: str | None
77
-
78
- class Config:
79
- extra = Extra.ignore
67
+ description: str | None = None
68
+ org_username: str | None = None
69
+ github_username: str | None = None
70
+ gitlab_username: str | None = None
71
+ openshift_serviceaccount: str | None = None
72
+ quay_username: str | None = None
80
73
 
81
74
 
82
75
  RoleMember = RoleUser | RoleBot
@@ -98,8 +98,10 @@ def resolve_role_members(
98
98
  members: list[RoleMember] = []
99
99
 
100
100
  # bring in the local users and bots ...
101
- members.extend(RoleUser(**u.dict()) for u in r.users or [])
102
- members.extend(RoleBot(**b.dict()) for b in r.bots or [] if b.org_username)
101
+ members.extend(RoleUser(**u.model_dump()) for u in r.users or [])
102
+ members.extend(
103
+ RoleBot(**b.model_dump()) for b in r.bots or [] if b.org_username
104
+ )
103
105
 
104
106
  # ... and enhance with the ones from member sources
105
107
  for ms in r.member_sources or []:
@@ -42,7 +42,7 @@ class MergeRequestManagerBase[T: BaseModel]:
42
42
  expected_data: dict[str, Any],
43
43
  ) -> OpenMergeRequest | None:
44
44
  for mr in self._open_mrs:
45
- mr_info_dict = mr.mr_info.dict()
45
+ mr_info_dict = mr.mr_info.model_dump()
46
46
  if all(mr_info_dict.get(k) == expected_data.get(k) for k in expected_data):
47
47
  return mr
48
48
 
@@ -60,8 +60,8 @@ class Parser[T: BaseModel]:
60
60
 
61
61
  if self.expected_version != self._find_by_name(self.version_ref, parts[1]):
62
62
  raise ParserVersionError("Version is outdated")
63
- return self.klass(
64
- **data_default_none(
65
- self.klass, self._data_from_description(parts[1]), use_defaults=False
66
- )
63
+ data = data_default_none(
64
+ self.klass, self._data_from_description(parts[1]), use_defaults=False
67
65
  )
66
+ assert isinstance(data, dict)
67
+ return self.klass(**data)
@@ -144,7 +144,7 @@ class GaugeMetric(BaseMetric):
144
144
 
145
145
  @classmethod
146
146
  def metric_family(cls) -> GaugeMetricFamily:
147
- labels = [f.alias for f in cls.__fields__.values()]
147
+ labels = [f.alias or name for name, f in cls.model_fields.items()]
148
148
  return GaugeMetricFamily(cls.name(), cls.__doc__ or "", labels=labels)
149
149
 
150
150
  @classmethod
@@ -167,7 +167,7 @@ class CounterMetric(BaseMetric):
167
167
 
168
168
  @classmethod
169
169
  def metric_family(cls) -> CounterMetricFamily:
170
- labels = [f.alias for f in cls.__fields__.values()]
170
+ labels = [f.alias or name for name, f in cls.model_fields.items()]
171
171
  return CounterMetricFamily(cls.name(), cls.__doc__ or "", labels=labels)
172
172
 
173
173
  @classmethod
@@ -198,7 +198,7 @@ class MetricsContainer:
198
198
  """
199
199
  Sets the value of the given gauge metric to the given value.
200
200
  """
201
- label_values = tuple(metric.dict(by_alias=True).values())
201
+ label_values = tuple(metric.model_dump(by_alias=True).values())
202
202
  self._gauges[metric.__class__][label_values] = value
203
203
 
204
204
  def set_info(self, metric: InfoMetric) -> None:
@@ -213,7 +213,7 @@ class MetricsContainer:
213
213
  Increases the value of the given counter by the given amount.
214
214
  """
215
215
  # all label values need to be strings, so lets convert them
216
- label_values = tuple(str(v) for v in counter.dict(by_alias=True).values())
216
+ label_values = tuple(str(v) for v in counter.model_dump(by_alias=True).values())
217
217
  current_value = self._counters[counter.__class__].get(label_values) or 0
218
218
  self._counters[counter.__class__][label_values] = current_value + by
219
219
 
@@ -270,7 +270,7 @@ class MetricsContainer:
270
270
  (
271
271
  metric_class(**{
272
272
  key: labels[i]
273
- for i, key in enumerate(metric_class.__fields__.keys())
273
+ for i, key in enumerate(metric_class.model_fields.keys())
274
274
  }),
275
275
  value,
276
276
  )