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
@@ -7,12 +7,7 @@ from enum import StrEnum
7
7
  from typing import Any
8
8
 
9
9
  import semver
10
- from pydantic import (
11
- BaseModel,
12
- ValidationError,
13
- root_validator,
14
- validator,
15
- )
10
+ from pydantic import BaseModel, ValidationError, field_validator, model_validator
16
11
 
17
12
  from reconcile.aws_version_sync.merge_request_manager.merge_request import (
18
13
  Renderer,
@@ -81,7 +76,7 @@ class SupportedResourceProvider(StrEnum):
81
76
  ELASTICACHE = "elasticache"
82
77
 
83
78
 
84
- class ExternalResource(BaseModel):
79
+ class ExternalResource(BaseModel, arbitrary_types_allowed=True):
85
80
  namespace_file: str | None = None
86
81
  provider: str = "aws"
87
82
  provisioner: ExternalResourceProvisioner
@@ -94,9 +89,6 @@ class ExternalResource(BaseModel):
94
89
  # used to map AWS cache name to resource_identifier
95
90
  redis_replication_group_id: str | None = None
96
91
 
97
- class Config:
98
- arbitrary_types_allowed = True
99
-
100
92
  @property
101
93
  def key(self) -> tuple:
102
94
  return (
@@ -106,7 +98,8 @@ class ExternalResource(BaseModel):
106
98
  self.resource_identifier,
107
99
  )
108
100
 
109
- @validator("resource_engine_version", pre=True)
101
+ @field_validator("resource_engine_version", mode="before")
102
+ @classmethod
110
103
  def parse_resource_engine_version(
111
104
  cls, v: str | semver.VersionInfo
112
105
  ) -> semver.VersionInfo:
@@ -114,7 +107,8 @@ class ExternalResource(BaseModel):
114
107
  return v
115
108
  return parse_semver(str(v), optional_minor_and_patch=True)
116
109
 
117
- @root_validator(pre=True)
110
+ @model_validator(mode="before")
111
+ @classmethod
118
112
  def set_resource_engine_version_format(cls, values: dict) -> dict:
119
113
  resource_engine_version, resource_engine_version_format = (
120
114
  str(values.get("resource_engine_version")),
@@ -62,8 +62,8 @@ class QontractServerDatafileDiff(BaseModel):
62
62
 
63
63
  datafilepath: str
64
64
  datafileschema: str
65
- old: dict[str, Any] | None
66
- new: dict[str, Any] | None
65
+ old: dict[str, Any] | None = None
66
+ new: dict[str, Any] | None = None
67
67
 
68
68
  @property
69
69
  def old_datafilepath(self) -> str | None:
@@ -119,7 +119,7 @@ class QontractServerResourcefileDiffState(BaseModel):
119
119
  content: str
120
120
  resourcefileschema: str | None = Field(..., alias="$schema")
121
121
  sha256sum: str
122
- backrefs: list[QontractServerResourcefileBackref] | None
122
+ backrefs: list[QontractServerResourcefileBackref] | None = None
123
123
 
124
124
 
125
125
  class QontractServerResourcefileDiff(BaseModel):
@@ -155,7 +155,8 @@ class ChangeLogIntegration(QontractReconcileIntegration[ChangeLogIntegrationPara
155
155
  changes = aggregate_resource_changes(
156
156
  bundle_changes=aggregate_file_moves(parse_bundle_changes(diff)),
157
157
  content_store={
158
- c.path: c.dict(by_alias=True) for c in namespaces + jenkins_configs
158
+ c.path: c.model_dump(by_alias=True)
159
+ for c in namespaces + jenkins_configs
159
160
  },
160
161
  supported_schemas={
161
162
  "/openshift/namespace-1.yml",
@@ -239,4 +240,4 @@ class ChangeLogIntegration(QontractReconcileIntegration[ChangeLogIntegrationPara
239
240
  change_log.items, key=lambda i: i.merged_at, reverse=True
240
241
  )
241
242
  if not dry_run:
242
- integration_state.add(BUNDLE_DIFFS_OBJ, change_log.dict(), force=True)
243
+ integration_state.add(BUNDLE_DIFFS_OBJ, change_log.model_dump(), force=True)
@@ -140,7 +140,7 @@ def write_coverage_report_to_mr(
140
140
  approver_reachability = set()
141
141
  for d in change_decisions:
142
142
  approvers = [
143
- f"{cr.context} - {' '.join([f'@{a.org_username}' if a.tag_on_merge_requests else a.org_username for a in cr.approvers])}"
143
+ f"{cr.context} - {' '.join([f'@{a.org_username}' if (a.tag_on_merge_requests or len(cr.approvers) == 1) else a.org_username for a in cr.approvers])}"
144
144
  for cr in d.change_responsibles
145
145
  ]
146
146
  if d.coverable_by_fragment_decisions:
@@ -466,7 +466,7 @@ class DashdotdbDORA(DashdotdbBase):
466
466
  ]
467
467
 
468
468
  def _github_compare_commits(self, rc: RepoChanges, repo: str) -> list[Commit]:
469
- if not rc.repo_url:
469
+ if not rc.repo_url or not rc.ref_from or not rc.ref_to:
470
470
  return []
471
471
 
472
472
  return [
@@ -119,4 +119,4 @@ def run(
119
119
 
120
120
 
121
121
  def early_exit_desired_state(*args: Any, **kwargs: Any) -> dict[str, Any]:
122
- return {doc.name: doc.dict() for doc in get_slo_documents()}
122
+ return {doc.name: doc.model_dump() for doc in get_slo_documents()}
@@ -61,20 +61,19 @@ def get_database_access_namespaces(
61
61
  return query(query_func).namespaces_v1 or []
62
62
 
63
63
 
64
- class DatabaseConnectionParameters(BaseModel):
64
+ class DatabaseConnectionParameters(
65
+ BaseModel, validate_by_name=True, validate_by_alias=True
66
+ ):
65
67
  host: str = Field(..., alias="db.host")
66
68
  port: str = Field(..., alias="db.port")
67
69
  user: str = Field(..., alias="db.user")
68
70
  password: str = Field(..., alias="db.password")
69
71
  database: str = Field(..., alias="db.name")
70
72
 
71
- class Config:
72
- allow_population_by_field_name = True
73
-
74
73
 
75
74
  class PSQLScriptGenerator(BaseModel):
76
75
  db_access: DatabaseAccessV1
77
- current_db_access: DatabaseAccessV1 | None
76
+ current_db_access: DatabaseAccessV1 | None = None
78
77
  connection_parameter: DatabaseConnectionParameters
79
78
  admin_connection_parameter: DatabaseConnectionParameters
80
79
  engine: str
@@ -597,7 +596,7 @@ def _process_db_access(
597
596
  current_db_access: DatabaseAccessV1 | None = None
598
597
  if state.exists(state_key):
599
598
  current_state = state.get(state_key)
600
- if current_state == db_access.dict(by_alias=True):
599
+ if current_state == db_access.model_dump(by_alias=True):
601
600
  return
602
601
  current_db_access = DatabaseAccessV1(**current_state)
603
602
  if current_db_access.database != db_access.database:
@@ -610,7 +609,7 @@ def _process_db_access(
610
609
 
611
610
  resource_prefix = f"dbam-{state_key.replace('/', '-')}"
612
611
  with OC_Map(
613
- clusters=[namespace.cluster.dict(by_alias=True)],
612
+ clusters=[namespace.cluster.model_dump(by_alias=True)],
614
613
  integration=QONTRACT_INTEGRATION,
615
614
  settings=settings,
616
615
  ) as oc_map:
@@ -677,7 +676,7 @@ def _process_db_access(
677
676
  if not dry_run and not db_access.delete:
678
677
  secret = {
679
678
  "path": f"{vault_output_path}/{QONTRACT_INTEGRATION}/{state_key}",
680
- "data": connections["user"].dict(by_alias=True),
679
+ "data": connections["user"].model_dump(by_alias=True),
681
680
  }
682
681
  vault_client.write(secret, decode_base64=False)
683
682
  logging.debug("job completed, cleaning up")
@@ -693,7 +692,7 @@ def _process_db_access(
693
692
  )
694
693
  state.add(
695
694
  state_key,
696
- value=db_access.dict(by_alias=True),
695
+ value=db_access.model_dump(by_alias=True),
697
696
  force=True,
698
697
  )
699
698
  else:
@@ -82,7 +82,7 @@ class DynatraceTokenProviderIntegration(QontractReconcileIntegration[NoParams]):
82
82
  return {
83
83
  "version": QONTRACT_INTEGRATION_VERSION,
84
84
  "specs": {
85
- spec.name: spec.dict()
85
+ spec.name: spec.model_dump()
86
86
  for spec in get_dynatrace_token_provider_token_specs()
87
87
  },
88
88
  }
@@ -150,7 +150,10 @@ class EndpointsDiscoveryIntegration(
150
150
  return []
151
151
 
152
152
  routes = defaultdict(list)
153
- for item in oc.get_items(kind="Route", namespace=namespace.name):
153
+ for item in oc.get_items(
154
+ kind="Route.route.openshift.io",
155
+ namespace=namespace.name,
156
+ ):
154
157
  tls = bool(item["spec"].get("tls"))
155
158
  host = item["spec"]["host"]
156
159
  # group all routes with the same hostname/tls
@@ -89,7 +89,7 @@ class Renderer:
89
89
 
90
90
  def render_description(self, hash: str) -> str:
91
91
  """Render the description for a merge request."""
92
- return DESC.safe_substitute(EPDInfo(hash=hash).dict())
92
+ return DESC.safe_substitute(EPDInfo(hash=hash).model_dump())
93
93
 
94
94
  def render_title(self) -> str:
95
95
  """Render the title for a merge request."""
@@ -64,7 +64,7 @@ class Endpoint(BaseModel):
64
64
 
65
65
  @property
66
66
  def hash(self) -> str:
67
- return hashlib.sha256(json_dumps(self.dict()).encode()).hexdigest()
67
+ return hashlib.sha256(json_dumps(self.model_dump()).encode()).hexdigest()
68
68
 
69
69
 
70
70
  EndpointsToAdd = list[Endpoint]
@@ -45,7 +45,7 @@ from reconcile.utils.secret_reader import SecretReaderBase, create_secret_reader
45
45
  def fetch_current_state(
46
46
  ri: ResourceInventory, oc: OCCli, cluster: str, namespace: str
47
47
  ) -> None:
48
- for item in oc.get_items("Job", namespace=namespace):
48
+ for item in oc.get_items("Job.batch", namespace=namespace):
49
49
  r = OpenshiftResource(item, QONTRACT_INTEGRATION, QONTRACT_INTEGRATION_VERSION)
50
50
  ri.add_current(cluster, namespace, "Job", r.name, r)
51
51
 
@@ -45,6 +45,7 @@ from reconcile.utils.datetime_util import utc_now
45
45
  from reconcile.utils.external_resource_spec import (
46
46
  ExternalResourceSpec,
47
47
  )
48
+ from reconcile.utils.json import json_dumps
48
49
  from reconcile.utils.secret_reader import SecretReaderBase
49
50
 
50
51
 
@@ -244,7 +245,7 @@ class ExternalResourcesManager:
244
245
  reconciliation = Reconciliation(
245
246
  key=key,
246
247
  resource_hash=resource.hash(),
247
- input=resource.json(),
248
+ input=json_dumps(resource),
248
249
  action=Action.APPLY,
249
250
  module_configuration=module_conf,
250
251
  linked_resources=self._find_linked_resources(spec),
@@ -252,11 +253,15 @@ class ExternalResourcesManager:
252
253
  r.add(reconciliation)
253
254
  return r
254
255
 
255
- def _get_deleted_objects_reconciliations(self) -> set[Reconciliation]:
256
+ def _get_deleted_objects_reconciliations(
257
+ self, enable_migration: bool = False
258
+ ) -> set[Reconciliation]:
256
259
  to_reconcile: set[Reconciliation] = set()
257
260
  deleted_keys = (k for k, v in self.er_inventory.items() if v.marked_to_delete)
258
261
  for key in deleted_keys:
259
- state = self.state_mgr.get_external_resource_state(key)
262
+ state = self.state_mgr.get_external_resource_state(
263
+ key, enable_migration=enable_migration
264
+ )
260
265
  if state.resource_status == ResourceStatus.NOT_EXISTS:
261
266
  logging.debug("Resource has already been removed. key: %s", key)
262
267
  continue
@@ -349,7 +354,9 @@ class ExternalResourcesManager:
349
354
 
350
355
  if r.linked_resources:
351
356
  for lr in r.linked_resources:
352
- lrs = self.state_mgr.get_external_resource_state(lr)
357
+ lrs = self.state_mgr.get_external_resource_state(
358
+ lr, enable_migration=True
359
+ )
353
360
  if not lrs.resource_status.is_in_progress:
354
361
  lrs.resource_status = ResourceStatus.RECONCILIATION_REQUESTED
355
362
  self.state_mgr.set_external_resource_state(lrs)
@@ -416,10 +423,12 @@ class ExternalResourcesManager:
416
423
 
417
424
  def handle_resources(self) -> None:
418
425
  desired_r = self._get_desired_objects_reconciliations()
419
- deleted_r = self._get_deleted_objects_reconciliations()
426
+ deleted_r = self._get_deleted_objects_reconciliations(enable_migration=True)
420
427
  to_sync_keys: set[ExternalResourceKey] = set()
421
428
  for r in desired_r.union(deleted_r):
422
- state = self.state_mgr.get_external_resource_state(r.key)
429
+ state = self.state_mgr.get_external_resource_state(
430
+ r.key, enable_migration=True
431
+ )
423
432
  reconciliation_status = self._get_reconciliation_status(r, state)
424
433
  self._update_resource_state(r, state, reconciliation_status)
425
434
 
@@ -450,7 +459,10 @@ class ExternalResourcesManager:
450
459
  r
451
460
  for r in desired_r.union(deleted_r)
452
461
  if self._reconciliation_needs_dry_run_run(
453
- r, self.state_mgr.get_external_resource_state(key=r.key)
462
+ r,
463
+ self.state_mgr.get_external_resource_state(
464
+ key=r.key, enable_migration=False
465
+ ),
454
466
  )
455
467
  }
456
468
 
@@ -13,7 +13,7 @@ from reconcile.utils.metrics import (
13
13
 
14
14
 
15
15
  class ExternalResourcesBaseMetric(BaseModel):
16
- integration = normalize_integration_name(QONTRACT_INTEGRATION)
16
+ integration: str = normalize_integration_name(QONTRACT_INTEGRATION)
17
17
  app: str
18
18
  environment: str
19
19
  provision_provider: str
@@ -89,7 +89,7 @@ class ExternalResourceKey(BaseModel, frozen=True):
89
89
  )
90
90
 
91
91
  def hash(self) -> str:
92
- return hashlib.md5(json_dumps(self.dict()).encode("utf-8")).hexdigest()
92
+ return hashlib.md5(json_dumps(self.model_dump()).encode("utf-8")).hexdigest()
93
93
 
94
94
  @property
95
95
  def state_path(self) -> str:
@@ -138,15 +138,15 @@ class ExternalResourcesInventory(MutableMapping):
138
138
  ) -> ExternalResourceSpec:
139
139
  spec = ExternalResourceSpec(
140
140
  provision_provider=provider.provider,
141
- provisioner=provider.provisioner.dict(),
142
- resource=resource.dict(
141
+ provisioner=provider.provisioner.model_dump(),
142
+ resource=resource.model_dump(
143
143
  exclude={
144
144
  FLAG_RESOURCE_MANAGED_BY_ERV2,
145
145
  FLAG_DELETE_RESOURCE,
146
146
  MODULE_OVERRIDES,
147
147
  }
148
148
  ),
149
- namespace=namespace.dict(by_alias=True),
149
+ namespace=namespace.model_dump(by_alias=True),
150
150
  )
151
151
  spec.metadata[FLAG_DELETE_RESOURCE] = resource.delete or namespace.delete
152
152
  spec.metadata[MODULE_OVERRIDES] = resource.module_overrides
@@ -387,7 +387,7 @@ class Reconciliation(BaseModel, frozen=True):
387
387
  )
388
388
  # linked_resources store dependants resources. They will get reconciled
389
389
  # every time the parent resource reconciliation finishes.
390
- linked_resources: frozenset[ExternalResourceKey] | None
390
+ linked_resources: frozenset[ExternalResourceKey] | None = None
391
391
 
392
392
 
393
393
  class ReconcileAction(StrEnum):
@@ -440,4 +440,4 @@ class ExternalResource(BaseModel):
440
440
  provision: ExternalResourceProvision
441
441
 
442
442
  def hash(self) -> str:
443
- return hashlib.md5(json_dumps(self.data).encode("utf-8")).hexdigest()
443
+ return hashlib.sha256(json_dumps(self.data).encode("utf-8")).hexdigest()
@@ -70,10 +70,13 @@ class ReconciliationK8sJob(K8sJob, BaseModel, frozen=True):
70
70
  dry_run_suffix: str = ""
71
71
 
72
72
  def name_prefix(self) -> str:
73
+ identifier = (
74
+ f"{self.reconciliation.key.provider}-{self.reconciliation.key.identifier}"
75
+ )
73
76
  if self.is_dry_run:
74
- return f"er-dry-run-mr-{self.dry_run_suffix}"
77
+ return f"er-dry-run-mr-{self.dry_run_suffix}-{identifier}"
75
78
  else:
76
- return "er"
79
+ return f"er-{identifier}"
77
80
 
78
81
  def unit_of_work_identity(self) -> Any:
79
82
  return self.reconciliation.key
@@ -96,10 +99,10 @@ class ReconciliationK8sJob(K8sJob, BaseModel, frozen=True):
96
99
  image=self.reconciliation.module_configuration.image_version,
97
100
  image_pull_policy="Always",
98
101
  resources=V1ResourceRequirements(
99
- requests=self.reconciliation.module_configuration.resources.requests.dict(
102
+ requests=self.reconciliation.module_configuration.resources.requests.model_dump(
100
103
  exclude_none=True
101
104
  ),
102
- limits=self.reconciliation.module_configuration.resources.limits.dict(
105
+ limits=self.reconciliation.module_configuration.resources.limits.model_dump(
103
106
  exclude_none=True
104
107
  ),
105
108
  ),
@@ -46,8 +46,8 @@ class VaultSecret(BaseModel):
46
46
 
47
47
  path: str
48
48
  field: str
49
- version: int | None
50
- q_format: str | None
49
+ version: int | None = None
50
+ q_format: str | None = None
51
51
 
52
52
 
53
53
  class SecretHelper:
@@ -1,7 +1,9 @@
1
+ import json
1
2
  import logging
2
3
  from collections.abc import Mapping
3
4
  from datetime import datetime
4
5
  from enum import StrEnum
6
+ from hashlib import sha256
5
7
  from typing import Any
6
8
 
7
9
  from pydantic import BaseModel
@@ -17,6 +19,7 @@ from reconcile.external_resources.model import (
17
19
  )
18
20
  from reconcile.utils.aws_api_typed.api import AWSApi
19
21
  from reconcile.utils.datetime_util import to_utc_microseconds_iso_format, utc_now
22
+ from reconcile.utils.json import json_dumps
20
23
 
21
24
  DATE_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
22
25
 
@@ -170,7 +173,7 @@ class DynamoDBStateAdapter:
170
173
 
171
174
  def serialize(self, state: ExternalResourceState) -> dict[str, Any]:
172
175
  return {
173
- self.ER_KEY_HASH: {"S": state.key.hash()},
176
+ self.ER_KEY_HASH: {"S": state.key.state_path},
174
177
  self.TIMESTAMP: {"S": to_utc_microseconds_iso_format(state.ts)},
175
178
  self.RESOURCE_STATUS: {"S": state.resource_status.value},
176
179
  self.ER_KEY: {
@@ -259,24 +262,63 @@ class ExternalResourcesStateDynamoDB:
259
262
  self._table = table_name
260
263
  self.partial_resources = self._get_partial_resources()
261
264
 
265
+ def _new_sha256_hash(self, item: dict) -> str:
266
+ resource_json = item[self.adapter.RECONC]["M"][self.adapter.RECONC_INPUT]["S"]
267
+ resource_dict = json.loads(resource_json)
268
+ data = resource_dict["data"]
269
+ return sha256(json_dumps(data).encode("utf-8")).hexdigest()
270
+
262
271
  def get_external_resource_state(
263
- self, key: ExternalResourceKey
272
+ self,
273
+ key: ExternalResourceKey,
274
+ enable_migration: bool = False,
264
275
  ) -> ExternalResourceState:
265
276
  data = self.aws_api.dynamodb.boto3_client.get_item(
266
277
  TableName=self._table,
267
278
  ConsistentRead=True,
268
- Key={self.adapter.ER_KEY_HASH: {"S": key.hash()}},
279
+ Key={self.adapter.ER_KEY_HASH: {"S": key.state_path}},
269
280
  )
270
- if "Item" in data:
281
+ item = data.get("Item")
282
+ if item:
271
283
  return self.adapter.deserialize(data["Item"])
272
- else:
273
- return ExternalResourceState(
274
- key=key,
275
- ts=utc_now(),
276
- resource_status=ResourceStatus.NOT_EXISTS,
277
- reconciliation=Reconciliation(key=key),
278
- reconciliation_errors=0,
279
- )
284
+
285
+ old_data = self.aws_api.dynamodb.boto3_client.get_item(
286
+ TableName=self._table,
287
+ ConsistentRead=True,
288
+ Key={self.adapter.ER_KEY_HASH: {"S": key.hash()}},
289
+ )
290
+ old_item = old_data.get("Item")
291
+ if old_item:
292
+ old_item[self.adapter.ER_KEY_HASH]["S"] = key.state_path
293
+ old_item[self.adapter.RECONC]["M"][self.adapter.RECONC_RESOURCE_HASH][
294
+ "S"
295
+ ] = self._new_sha256_hash(old_item)
296
+ if enable_migration:
297
+ self.aws_api.dynamodb.boto3_client.transact_write_items(
298
+ TransactItems=[
299
+ {
300
+ "Put": {
301
+ "TableName": self._table,
302
+ "Item": old_item,
303
+ }
304
+ },
305
+ {
306
+ "Delete": {
307
+ "TableName": self._table,
308
+ "Key": {self.adapter.ER_KEY_HASH: {"S": key.hash()}},
309
+ }
310
+ },
311
+ ]
312
+ )
313
+ return self.adapter.deserialize(old_item)
314
+
315
+ return ExternalResourceState(
316
+ key=key,
317
+ ts=utc_now(),
318
+ resource_status=ResourceStatus.NOT_EXISTS,
319
+ reconciliation=Reconciliation(key=key),
320
+ reconciliation_errors=0,
321
+ )
280
322
 
281
323
  def set_external_resource_state(
282
324
  self,
@@ -289,7 +331,7 @@ class ExternalResourcesStateDynamoDB:
289
331
  def del_external_resource_state(self, key: ExternalResourceKey) -> None:
290
332
  self.aws_api.dynamodb.boto3_client.delete_item(
291
333
  TableName=self._table,
292
- Key={self.adapter.ER_KEY_HASH: {"S": key.hash()}},
334
+ Key={self.adapter.ER_KEY_HASH: {"S": key.state_path}},
293
335
  )
294
336
 
295
337
  def _get_partial_resources(
@@ -331,7 +373,7 @@ class ExternalResourcesStateDynamoDB:
331
373
  ) -> None:
332
374
  self.aws_api.dynamodb.boto3_client.update_item(
333
375
  TableName=self._table,
334
- Key={self.adapter.ER_KEY_HASH: {"S": key.hash()}},
376
+ Key={self.adapter.ER_KEY_HASH: {"S": key.state_path}},
335
377
  UpdateExpression="set resource_status=:new_value",
336
378
  ExpressionAttributeValues={":new_value": {"S": status.value}},
337
379
  ReturnValues="UPDATED_NEW",
@@ -58,7 +58,7 @@ class FleetLabelerIntegration(QontractReconcileIntegration[NoParams]):
58
58
  """Return the desired state for early exit."""
59
59
  return {
60
60
  "version": QONTRACT_INTEGRATION_VERSION,
61
- "specs": {spec.name: spec.dict() for spec in get_fleet_label_specs()},
61
+ "specs": {spec.name: spec.model_dump() for spec in get_fleet_label_specs()},
62
62
  }
63
63
 
64
64
  def run(self, dry_run: bool) -> None:
@@ -132,7 +132,7 @@ class QuayMirror:
132
132
  mirror_creds = None
133
133
  pull_credentials = item.mirror.pull_credentials
134
134
  if pull_credentials:
135
- raw_data = self.secret_reader.read_all(pull_credentials.dict())
135
+ raw_data = self.secret_reader.read_all(pull_credentials.model_dump())
136
136
  username = raw_data["user"]
137
137
  password = raw_data["token"]
138
138
  mirror_creds = f"{username}:{password}"
@@ -226,7 +226,7 @@ class QuayMirror:
226
226
  return False
227
227
 
228
228
  def _decode_push_secret(self, secret: VaultSecret) -> str:
229
- raw_data = self.secret_reader.read_all(secret.dict())
229
+ raw_data = self.secret_reader.read_all(secret.model_dump())
230
230
  token = base64.b64decode(raw_data["token"]).decode()
231
231
  return f"{raw_data['user']}:{token}"
232
232
 
reconcile/github_org.py CHANGED
@@ -144,7 +144,7 @@ def get_org_and_teams(
144
144
 
145
145
 
146
146
  @retry()
147
- def get_members(unit: Organization) -> list[str]:
147
+ def get_members(unit: Organization | Team) -> list[str]:
148
148
  return [member.login for member in unit.get_members()]
149
149
 
150
150
 
@@ -2,6 +2,7 @@ import logging
2
2
  import os
3
3
 
4
4
  import github
5
+ import github.NamedUser
5
6
  from github import Github
6
7
  from sretoolbox.utils import retry
7
8
 
@@ -100,4 +101,7 @@ def run(dry_run: bool) -> None:
100
101
 
101
102
  if not dry_run:
102
103
  gh_user = gh.get_user(github_username)
104
+ assert isinstance(
105
+ gh_user, github.NamedUser.NamedUser
106
+ ) # make mypy happy
103
107
  gh_org.add_to_members(gh_user, "admin")
@@ -44,19 +44,13 @@ class GitlabUser(BaseModel):
44
44
  access_level: int
45
45
 
46
46
 
47
- class CurrentStateSpec(BaseModel):
47
+ class CurrentStateSpec(BaseModel, arbitrary_types_allowed=True):
48
48
  members: dict[str, GroupMember]
49
49
 
50
- class Config:
51
- arbitrary_types_allowed = True
52
50
 
53
-
54
- class DesiredStateSpec(BaseModel):
51
+ class DesiredStateSpec(BaseModel, arbitrary_types_allowed=True):
55
52
  members: dict[str, GitlabUser]
56
53
 
57
- class Config:
58
- arbitrary_types_allowed = True
59
-
60
54
 
61
55
  CurrentState = dict[str, CurrentStateSpec]
62
56
  DesiredState = dict[str, DesiredStateSpec]
@@ -122,8 +116,8 @@ def build_desired_state_spec(
122
116
  pagerduty_map,
123
117
  get_username_method=lambda u: u.org_username,
124
118
  )
125
- for u in usernames_from_pagerduty:
126
- gu = GitlabUser(user=u, access_level=p_access_level)
119
+ for pu in usernames_from_pagerduty:
120
+ gu = GitlabUser(user=pu, access_level=p_access_level)
127
121
  add_or_update_user(desired_state_spec, gu)
128
122
  return desired_state_spec
129
123
 
@@ -239,6 +233,6 @@ def reconcile_gitlab_members(
239
233
  def early_exit_desired_state(*args: Any, **kwargs: Any) -> dict[str, Any]:
240
234
  gqlapi = gql.get_api()
241
235
  return {
242
- "instance": get_gitlab_instance(gqlapi.query).dict(),
243
- "permissions": [p.dict() for p in get_permissions(gqlapi.query)],
236
+ "instance": get_gitlab_instance(gqlapi.query).model_dump(),
237
+ "permissions": [p.model_dump() for p in get_permissions(gqlapi.query)],
244
238
  }
@@ -94,14 +94,6 @@ class GroupPermissionHandler:
94
94
  desired_state: dict[str, GroupSpec],
95
95
  current_state: dict[str, GroupSpec],
96
96
  ) -> None:
97
- # gather list of app-interface managed repos
98
- instance = queries.get_gitlab_instance()
99
- managed_repos = {
100
- f"{instance['url']}/{project_request['group']}/{r}"
101
- for project_request in instance.get("projectRequests", [])
102
- for r in project_request.get("projects", [])
103
- }
104
-
105
97
  # get the diff data
106
98
  diff_data = diff_mappings(
107
99
  current=current_state,
@@ -112,11 +104,10 @@ class GroupPermissionHandler:
112
104
  errors: list[Exception] = []
113
105
  for repo in diff_data.add:
114
106
  project = self.gl.get_project(repo)
115
- if not project and repo in managed_repos:
116
- logging.info(
117
- f"New app-interface managed repository {repo} hasn't been created yet - skipping"
118
- )
107
+ if not project:
108
+ logging.info(f"{repo} hasn't been created yet - skipping")
119
109
  continue
110
+
120
111
  if not self.can_share_project(project):
121
112
  errors.append(
122
113
  GroupAccessLevelError(
@@ -136,8 +127,13 @@ class GroupPermissionHandler:
136
127
  group_id=self.group.id,
137
128
  access_level=self.access_level,
138
129
  )
130
+
139
131
  for repo in diff_data.change:
140
132
  project = self.gl.get_project(repo)
133
+ if not project:
134
+ logging.info(f"{repo} hasn't been created yet - skipping")
135
+ continue
136
+
141
137
  if not self.can_share_project(project):
142
138
  errors.append(
143
139
  GroupAccessLevelError(