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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (356) hide show
  1. {qontract_reconcile-0.10.2.dev349.dist-info → qontract_reconcile-0.10.2.dev414.dist-info}/METADATA +12 -11
  2. {qontract_reconcile-0.10.2.dev349.dist-info → qontract_reconcile-0.10.2.dev414.dist-info}/RECORD +356 -350
  3. reconcile/acs_rbac.py +2 -2
  4. reconcile/aus/advanced_upgrade_service.py +15 -12
  5. reconcile/aus/base.py +26 -27
  6. reconcile/aus/cluster_version_data.py +15 -5
  7. reconcile/aus/models.py +1 -1
  8. reconcile/automated_actions/config/integration.py +15 -3
  9. reconcile/aws_account_manager/integration.py +8 -8
  10. reconcile/aws_account_manager/reconciler.py +3 -3
  11. reconcile/aws_ami_cleanup/integration.py +8 -12
  12. reconcile/aws_ami_share.py +69 -62
  13. reconcile/aws_cloudwatch_log_retention/integration.py +155 -126
  14. reconcile/aws_ecr_image_pull_secrets.py +2 -2
  15. reconcile/aws_iam_keys.py +7 -41
  16. reconcile/aws_saml_idp/integration.py +12 -4
  17. reconcile/aws_saml_roles/integration.py +32 -25
  18. reconcile/aws_version_sync/integration.py +6 -12
  19. reconcile/change_owners/bundle.py +3 -3
  20. reconcile/change_owners/change_log_tracking.py +3 -2
  21. reconcile/change_owners/change_owners.py +1 -1
  22. reconcile/change_owners/diff.py +2 -4
  23. reconcile/checkpoint.py +11 -3
  24. reconcile/cli.py +33 -8
  25. reconcile/dashdotdb_dora.py +5 -12
  26. reconcile/dashdotdb_slo.py +1 -1
  27. reconcile/database_access_manager.py +123 -117
  28. reconcile/dynatrace_token_provider/integration.py +1 -1
  29. reconcile/endpoints_discovery/integration.py +4 -1
  30. reconcile/endpoints_discovery/merge_request.py +1 -1
  31. reconcile/endpoints_discovery/merge_request_manager.py +9 -11
  32. reconcile/external_resources/factories.py +5 -12
  33. reconcile/external_resources/integration.py +1 -1
  34. reconcile/external_resources/manager.py +24 -10
  35. reconcile/external_resources/meta.py +0 -1
  36. reconcile/external_resources/metrics.py +1 -1
  37. reconcile/external_resources/model.py +13 -13
  38. reconcile/external_resources/reconciler.py +7 -4
  39. reconcile/external_resources/secrets_sync.py +6 -8
  40. reconcile/external_resources/state.py +60 -17
  41. reconcile/fleet_labeler/integration.py +1 -1
  42. reconcile/gabi_authorized_users.py +8 -5
  43. reconcile/gcp_image_mirror.py +2 -2
  44. reconcile/github_org.py +1 -1
  45. reconcile/github_owners.py +4 -0
  46. reconcile/gitlab_housekeeping.py +13 -15
  47. reconcile/gitlab_members.py +6 -12
  48. reconcile/gitlab_mr_sqs_consumer.py +2 -2
  49. reconcile/gitlab_owners.py +15 -11
  50. reconcile/gitlab_permissions.py +8 -12
  51. reconcile/glitchtip_project_alerts/integration.py +3 -1
  52. reconcile/gql_definitions/acs/acs_instances.py +5 -5
  53. reconcile/gql_definitions/acs/acs_policies.py +5 -5
  54. reconcile/gql_definitions/acs/acs_rbac.py +5 -5
  55. reconcile/gql_definitions/advanced_upgrade_service/aus_clusters.py +5 -5
  56. reconcile/gql_definitions/advanced_upgrade_service/aus_organization.py +5 -5
  57. reconcile/gql_definitions/app_interface_metrics_exporter/onboarding_status.py +5 -5
  58. reconcile/gql_definitions/app_sre_tekton_access_revalidation/roles.py +5 -5
  59. reconcile/gql_definitions/app_sre_tekton_access_revalidation/users.py +5 -5
  60. reconcile/gql_definitions/automated_actions/instance.py +46 -7
  61. reconcile/gql_definitions/aws_account_manager/aws_accounts.py +5 -5
  62. reconcile/gql_definitions/aws_ami_cleanup/aws_accounts.py +15 -5
  63. reconcile/gql_definitions/aws_cloudwatch_log_retention/aws_accounts.py +27 -66
  64. reconcile/gql_definitions/aws_saml_idp/aws_accounts.py +15 -5
  65. reconcile/gql_definitions/aws_saml_roles/aws_accounts.py +15 -5
  66. reconcile/gql_definitions/aws_saml_roles/roles.py +5 -5
  67. reconcile/gql_definitions/aws_version_sync/clusters.py +5 -5
  68. reconcile/gql_definitions/aws_version_sync/namespaces.py +5 -5
  69. reconcile/gql_definitions/change_owners/queries/change_types.py +5 -5
  70. reconcile/gql_definitions/change_owners/queries/self_service_roles.py +5 -5
  71. reconcile/gql_definitions/cluster_auth_rhidp/clusters.py +5 -5
  72. reconcile/gql_definitions/common/alerting_services_settings.py +5 -5
  73. reconcile/gql_definitions/common/app_code_component_repos.py +5 -5
  74. reconcile/gql_definitions/common/app_interface_custom_messages.py +5 -5
  75. reconcile/gql_definitions/common/app_interface_dms_settings.py +5 -5
  76. reconcile/gql_definitions/common/app_interface_repo_settings.py +5 -5
  77. reconcile/gql_definitions/common/app_interface_roles.py +5 -5
  78. reconcile/gql_definitions/common/app_interface_state_settings.py +5 -5
  79. reconcile/gql_definitions/common/app_interface_vault_settings.py +5 -5
  80. reconcile/gql_definitions/common/app_quay_repos_escalation_policies.py +5 -5
  81. reconcile/gql_definitions/common/apps.py +5 -5
  82. reconcile/gql_definitions/common/aws_vpc_requests.py +15 -5
  83. reconcile/gql_definitions/common/aws_vpcs.py +5 -5
  84. reconcile/gql_definitions/common/clusters.py +7 -5
  85. reconcile/gql_definitions/common/clusters_minimal.py +5 -5
  86. reconcile/gql_definitions/common/clusters_with_dms.py +5 -5
  87. reconcile/gql_definitions/common/clusters_with_peering.py +5 -5
  88. reconcile/gql_definitions/common/github_orgs.py +5 -5
  89. reconcile/gql_definitions/common/jira_settings.py +5 -5
  90. reconcile/gql_definitions/common/jiralert_settings.py +5 -5
  91. reconcile/gql_definitions/common/ldap_settings.py +5 -5
  92. reconcile/gql_definitions/common/namespaces.py +5 -5
  93. reconcile/gql_definitions/common/namespaces_minimal.py +7 -5
  94. reconcile/gql_definitions/common/ocm_env_telemeter.py +5 -5
  95. reconcile/gql_definitions/common/ocm_environments.py +5 -5
  96. reconcile/gql_definitions/common/pagerduty_instances.py +5 -5
  97. reconcile/gql_definitions/common/pgp_reencryption_settings.py +5 -5
  98. reconcile/gql_definitions/common/pipeline_providers.py +5 -5
  99. reconcile/gql_definitions/common/quay_instances.py +5 -5
  100. reconcile/gql_definitions/common/quay_orgs.py +5 -5
  101. reconcile/gql_definitions/common/reserved_networks.py +5 -5
  102. reconcile/gql_definitions/common/rhcs_provider_settings.py +5 -5
  103. reconcile/gql_definitions/common/saas_files.py +5 -5
  104. reconcile/gql_definitions/common/saas_target_namespaces.py +5 -5
  105. reconcile/gql_definitions/common/saasherder_settings.py +5 -5
  106. reconcile/gql_definitions/common/slack_workspaces.py +5 -5
  107. reconcile/gql_definitions/common/smtp_client_settings.py +5 -5
  108. reconcile/gql_definitions/common/state_aws_account.py +5 -5
  109. reconcile/gql_definitions/common/users.py +5 -5
  110. reconcile/gql_definitions/common/users_with_paths.py +5 -5
  111. reconcile/gql_definitions/cost_report/app_names.py +5 -5
  112. reconcile/gql_definitions/cost_report/cost_namespaces.py +5 -5
  113. reconcile/gql_definitions/cost_report/settings.py +5 -5
  114. reconcile/gql_definitions/dashdotdb_slo/slo_documents_query.py +5 -5
  115. reconcile/gql_definitions/dynatrace_token_provider/dynatrace_bootstrap_tokens.py +5 -5
  116. reconcile/gql_definitions/dynatrace_token_provider/token_specs.py +5 -5
  117. reconcile/gql_definitions/email_sender/apps.py +5 -5
  118. reconcile/gql_definitions/email_sender/emails.py +5 -5
  119. reconcile/gql_definitions/email_sender/users.py +5 -5
  120. reconcile/gql_definitions/endpoints_discovery/apps.py +5 -5
  121. reconcile/gql_definitions/external_resources/aws_accounts.py +5 -5
  122. reconcile/gql_definitions/external_resources/external_resources_modules.py +5 -5
  123. reconcile/gql_definitions/external_resources/external_resources_namespaces.py +89 -6
  124. reconcile/gql_definitions/external_resources/external_resources_settings.py +7 -5
  125. reconcile/gql_definitions/external_resources/fragments/external_resources_module_overrides.py +5 -5
  126. reconcile/gql_definitions/fleet_labeler/fleet_labels.py +5 -5
  127. reconcile/gql_definitions/fragments/aus_organization.py +5 -5
  128. reconcile/gql_definitions/fragments/aws_account_common.py +7 -5
  129. reconcile/gql_definitions/fragments/aws_account_managed.py +5 -5
  130. reconcile/gql_definitions/fragments/aws_account_sso.py +5 -5
  131. reconcile/gql_definitions/fragments/aws_infra_management_account.py +5 -5
  132. reconcile/gql_definitions/fragments/aws_organization.py +33 -0
  133. reconcile/gql_definitions/fragments/aws_vpc.py +5 -5
  134. reconcile/gql_definitions/fragments/aws_vpc_request.py +7 -5
  135. reconcile/gql_definitions/fragments/container_image_mirror.py +5 -5
  136. reconcile/gql_definitions/fragments/deploy_resources.py +5 -5
  137. reconcile/gql_definitions/fragments/disable.py +5 -5
  138. reconcile/gql_definitions/fragments/email_service.py +5 -5
  139. reconcile/gql_definitions/fragments/email_user.py +5 -5
  140. reconcile/gql_definitions/fragments/jumphost_common_fields.py +5 -5
  141. reconcile/gql_definitions/fragments/membership_source.py +5 -5
  142. reconcile/gql_definitions/fragments/minimal_ocm_organization.py +5 -5
  143. reconcile/gql_definitions/fragments/oc_connection_cluster.py +5 -5
  144. reconcile/gql_definitions/fragments/ocm_environment.py +5 -5
  145. reconcile/gql_definitions/fragments/pipeline_provider_retention.py +5 -5
  146. reconcile/gql_definitions/fragments/prometheus_instance.py +5 -5
  147. reconcile/gql_definitions/fragments/resource_limits_requirements.py +5 -5
  148. reconcile/gql_definitions/fragments/resource_requests_requirements.py +5 -5
  149. reconcile/gql_definitions/fragments/resource_values.py +5 -5
  150. reconcile/gql_definitions/fragments/saas_slo_document.py +5 -5
  151. reconcile/gql_definitions/fragments/saas_target_namespace.py +5 -5
  152. reconcile/gql_definitions/fragments/serviceaccount_token.py +5 -5
  153. reconcile/gql_definitions/fragments/terraform_state.py +5 -5
  154. reconcile/gql_definitions/fragments/upgrade_policy.py +5 -5
  155. reconcile/gql_definitions/fragments/user.py +5 -5
  156. reconcile/gql_definitions/fragments/vault_secret.py +5 -5
  157. reconcile/gql_definitions/gcp/gcp_docker_repos.py +5 -5
  158. reconcile/gql_definitions/gcp/gcp_projects.py +5 -5
  159. reconcile/gql_definitions/gitlab_members/gitlab_instances.py +5 -5
  160. reconcile/gql_definitions/gitlab_members/permissions.py +5 -5
  161. reconcile/gql_definitions/glitchtip/glitchtip_instance.py +5 -5
  162. reconcile/gql_definitions/glitchtip/glitchtip_project.py +5 -5
  163. reconcile/gql_definitions/glitchtip_project_alerts/glitchtip_project.py +5 -5
  164. reconcile/gql_definitions/integrations/integrations.py +5 -5
  165. reconcile/gql_definitions/introspection.json +2137 -1053
  166. reconcile/gql_definitions/jenkins_configs/jenkins_configs.py +5 -5
  167. reconcile/gql_definitions/jenkins_configs/jenkins_instances.py +5 -5
  168. reconcile/gql_definitions/jira/jira_servers.py +5 -5
  169. reconcile/gql_definitions/jira_permissions_validator/jira_boards_for_permissions_validator.py +9 -5
  170. reconcile/gql_definitions/jumphosts/jumphosts.py +5 -5
  171. reconcile/gql_definitions/ldap_groups/roles.py +5 -5
  172. reconcile/gql_definitions/ldap_groups/settings.py +5 -5
  173. reconcile/gql_definitions/maintenance/maintenances.py +5 -5
  174. reconcile/gql_definitions/membershipsources/roles.py +5 -5
  175. reconcile/gql_definitions/ocm_labels/clusters.py +5 -5
  176. reconcile/gql_definitions/ocm_labels/organizations.py +5 -5
  177. reconcile/gql_definitions/openshift_cluster_bots/clusters.py +5 -5
  178. reconcile/gql_definitions/openshift_groups/managed_groups.py +5 -5
  179. reconcile/gql_definitions/openshift_groups/managed_roles.py +5 -5
  180. reconcile/gql_definitions/openshift_serviceaccount_tokens/tokens.py +5 -5
  181. reconcile/gql_definitions/quay_membership/quay_membership.py +5 -5
  182. reconcile/gql_definitions/rhcs/certs.py +5 -5
  183. reconcile/gql_definitions/rhidp/organizations.py +5 -5
  184. reconcile/gql_definitions/service_dependencies/jenkins_instance_fragment.py +5 -5
  185. reconcile/gql_definitions/service_dependencies/service_dependencies.py +5 -5
  186. reconcile/gql_definitions/sharding/aws_accounts.py +5 -5
  187. reconcile/gql_definitions/sharding/ocm_organization.py +5 -5
  188. reconcile/gql_definitions/skupper_network/site_controller_template.py +5 -5
  189. reconcile/gql_definitions/skupper_network/skupper_networks.py +5 -5
  190. reconcile/gql_definitions/slack_usergroups/clusters.py +5 -5
  191. reconcile/gql_definitions/slack_usergroups/permissions.py +5 -5
  192. reconcile/gql_definitions/slack_usergroups/users.py +5 -5
  193. reconcile/gql_definitions/slo_documents/slo_documents.py +5 -5
  194. reconcile/gql_definitions/status_board/status_board.py +5 -5
  195. reconcile/gql_definitions/statuspage/statuspages.py +5 -5
  196. reconcile/gql_definitions/templating/template_collection.py +5 -5
  197. reconcile/gql_definitions/templating/templates.py +5 -5
  198. reconcile/gql_definitions/terraform_cloudflare_dns/app_interface_cloudflare_dns_settings.py +5 -5
  199. reconcile/gql_definitions/terraform_cloudflare_dns/terraform_cloudflare_zones.py +5 -5
  200. reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_accounts.py +5 -5
  201. reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_resources.py +5 -5
  202. reconcile/gql_definitions/terraform_cloudflare_users/app_interface_setting_cloudflare_and_vault.py +5 -5
  203. reconcile/gql_definitions/terraform_cloudflare_users/terraform_cloudflare_roles.py +5 -5
  204. reconcile/gql_definitions/terraform_init/aws_accounts.py +19 -5
  205. reconcile/gql_definitions/terraform_repo/terraform_repo.py +5 -5
  206. reconcile/gql_definitions/terraform_resources/database_access_manager.py +5 -5
  207. reconcile/gql_definitions/terraform_resources/terraform_resources_namespaces.py +38 -6
  208. reconcile/gql_definitions/terraform_tgw_attachments/aws_accounts.py +15 -5
  209. reconcile/gql_definitions/unleash_feature_toggles/feature_toggles.py +5 -5
  210. reconcile/gql_definitions/vault_instances/vault_instances.py +5 -5
  211. reconcile/gql_definitions/vault_policies/vault_policies.py +5 -5
  212. reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator.py +5 -5
  213. reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator_peered_cluster_fragment.py +5 -5
  214. reconcile/integrations_manager.py +3 -3
  215. reconcile/jenkins_worker_fleets.py +10 -8
  216. reconcile/jira_permissions_validator.py +237 -122
  217. reconcile/ldap_groups/integration.py +1 -1
  218. reconcile/ocm/types.py +35 -56
  219. reconcile/ocm_aws_infrastructure_access.py +1 -1
  220. reconcile/ocm_clusters.py +4 -4
  221. reconcile/ocm_labels/integration.py +3 -2
  222. reconcile/ocm_machine_pools.py +23 -23
  223. reconcile/openshift_base.py +53 -2
  224. reconcile/openshift_cluster_bots.py +3 -2
  225. reconcile/openshift_namespace_labels.py +1 -1
  226. reconcile/openshift_namespaces.py +97 -101
  227. reconcile/openshift_resources_base.py +6 -2
  228. reconcile/openshift_rhcs_certs.py +5 -5
  229. reconcile/openshift_rolebindings.py +7 -11
  230. reconcile/openshift_saas_deploy.py +6 -7
  231. reconcile/openshift_saas_deploy_change_tester.py +9 -7
  232. reconcile/openshift_saas_deploy_trigger_cleaner.py +3 -5
  233. reconcile/openshift_serviceaccount_tokens.py +2 -2
  234. reconcile/openshift_upgrade_watcher.py +4 -4
  235. reconcile/oum/labelset.py +5 -3
  236. reconcile/oum/models.py +1 -4
  237. reconcile/prometheus_rules_tester/integration.py +3 -3
  238. reconcile/quay_mirror.py +1 -1
  239. reconcile/queries.py +131 -1
  240. reconcile/rhidp/common.py +3 -5
  241. reconcile/rhidp/sso_client/base.py +1 -1
  242. reconcile/saas_auto_promotions_manager/merge_request_manager/renderer.py +1 -1
  243. reconcile/saas_auto_promotions_manager/subscriber.py +4 -3
  244. reconcile/skupper_network/integration.py +2 -2
  245. reconcile/slack_usergroups.py +35 -14
  246. reconcile/sql_query.py +1 -0
  247. reconcile/status_board.py +6 -6
  248. reconcile/statuspage/atlassian.py +7 -7
  249. reconcile/statuspage/integrations/maintenances.py +4 -3
  250. reconcile/statuspage/page.py +4 -9
  251. reconcile/statuspage/status.py +5 -8
  252. reconcile/templates/rosa-classic-cluster-creation.sh.j2 +4 -0
  253. reconcile/templates/rosa-hcp-cluster-creation.sh.j2 +3 -0
  254. reconcile/templating/lib/rendering.py +3 -3
  255. reconcile/templating/renderer.py +4 -3
  256. reconcile/terraform_aws_route53.py +7 -1
  257. reconcile/terraform_cloudflare_dns.py +3 -3
  258. reconcile/terraform_cloudflare_resources.py +5 -5
  259. reconcile/terraform_cloudflare_users.py +3 -2
  260. reconcile/terraform_init/integration.py +187 -23
  261. reconcile/terraform_repo.py +16 -12
  262. reconcile/terraform_resources.py +17 -7
  263. reconcile/terraform_tgw_attachments.py +27 -19
  264. reconcile/terraform_users.py +7 -0
  265. reconcile/terraform_vpc_peerings.py +14 -3
  266. reconcile/terraform_vpc_resources/integration.py +10 -1
  267. reconcile/typed_queries/aws_account_tags.py +41 -0
  268. reconcile/typed_queries/cost_report/app_names.py +1 -1
  269. reconcile/typed_queries/cost_report/cost_namespaces.py +2 -2
  270. reconcile/typed_queries/saas_files.py +13 -13
  271. reconcile/typed_queries/status_board.py +2 -2
  272. reconcile/unleash_feature_toggles/integration.py +4 -2
  273. reconcile/utils/acs/base.py +6 -3
  274. reconcile/utils/acs/policies.py +2 -2
  275. reconcile/utils/aggregated_list.py +4 -3
  276. reconcile/utils/aws_api.py +51 -54
  277. reconcile/utils/aws_api_typed/api.py +38 -9
  278. reconcile/utils/aws_api_typed/cloudformation.py +149 -0
  279. reconcile/utils/aws_api_typed/logs.py +73 -0
  280. reconcile/utils/aws_api_typed/organization.py +4 -2
  281. reconcile/utils/datetime_util.py +67 -0
  282. reconcile/utils/deadmanssnitch_api.py +1 -1
  283. reconcile/utils/differ.py +2 -3
  284. reconcile/utils/early_exit_cache.py +11 -12
  285. reconcile/utils/expiration.py +7 -3
  286. reconcile/utils/external_resource_spec.py +24 -1
  287. reconcile/utils/filtering.py +1 -1
  288. reconcile/utils/gitlab_api.py +7 -5
  289. reconcile/utils/glitchtip/client.py +6 -2
  290. reconcile/utils/glitchtip/models.py +25 -28
  291. reconcile/utils/gql.py +4 -7
  292. reconcile/utils/helm.py +2 -1
  293. reconcile/utils/helpers.py +1 -1
  294. reconcile/utils/instrumented_wrappers.py +1 -1
  295. reconcile/utils/internal_groups/client.py +2 -2
  296. reconcile/utils/internal_groups/models.py +8 -17
  297. reconcile/utils/jinja2/utils.py +6 -101
  298. reconcile/utils/jira_client.py +82 -63
  299. reconcile/utils/jjb_client.py +9 -12
  300. reconcile/utils/jobcontroller/controller.py +1 -1
  301. reconcile/utils/jobcontroller/models.py +17 -1
  302. reconcile/utils/json.py +70 -0
  303. reconcile/utils/membershipsources/app_interface_resolver.py +4 -2
  304. reconcile/utils/membershipsources/models.py +16 -23
  305. reconcile/utils/membershipsources/resolver.py +4 -2
  306. reconcile/utils/merge_request_manager/merge_request_manager.py +4 -4
  307. reconcile/utils/merge_request_manager/parser.py +6 -6
  308. reconcile/utils/metrics.py +5 -5
  309. reconcile/utils/models.py +304 -82
  310. reconcile/utils/mr/app_interface_reporter.py +2 -2
  311. reconcile/utils/mr/base.py +2 -2
  312. reconcile/utils/mr/notificator.py +3 -3
  313. reconcile/utils/mr/update_access_report_base.py +3 -4
  314. reconcile/utils/mr/user_maintenance.py +3 -2
  315. reconcile/utils/oc.py +118 -97
  316. reconcile/utils/oc_filters.py +3 -3
  317. reconcile/utils/ocm/addons.py +0 -1
  318. reconcile/utils/ocm/base.py +17 -20
  319. reconcile/utils/ocm/cluster_groups.py +1 -1
  320. reconcile/utils/ocm/identity_providers.py +2 -2
  321. reconcile/utils/ocm/labels.py +1 -1
  322. reconcile/utils/ocm/products.py +9 -3
  323. reconcile/utils/ocm/search_filters.py +3 -6
  324. reconcile/utils/ocm/service_log.py +4 -6
  325. reconcile/utils/ocm/sre_capability_labels.py +20 -13
  326. reconcile/utils/openshift_resource.py +10 -5
  327. reconcile/utils/output.py +3 -2
  328. reconcile/utils/pagerduty_api.py +10 -7
  329. reconcile/utils/promotion_state.py +6 -11
  330. reconcile/utils/raw_github_api.py +1 -1
  331. reconcile/utils/rhcsv2_certs.py +1 -4
  332. reconcile/utils/runtime/integration.py +2 -3
  333. reconcile/utils/runtime/runner.py +2 -2
  334. reconcile/utils/saasherder/interfaces.py +13 -20
  335. reconcile/utils/saasherder/models.py +25 -21
  336. reconcile/utils/saasherder/saasherder.py +35 -24
  337. reconcile/utils/slack_api.py +26 -4
  338. reconcile/utils/sloth.py +171 -2
  339. reconcile/utils/sqs_gateway.py +2 -1
  340. reconcile/utils/state.py +2 -1
  341. reconcile/utils/structs.py +1 -1
  342. reconcile/utils/terraform_client.py +5 -4
  343. reconcile/utils/terrascript_aws_client.py +171 -114
  344. reconcile/utils/unleash/server.py +2 -8
  345. reconcile/utils/vault.py +5 -12
  346. reconcile/utils/vcs.py +8 -8
  347. reconcile/vault_replication.py +107 -42
  348. tools/app_interface_reporter.py +4 -4
  349. tools/cli_commands/cost_report/cost_management_api.py +3 -3
  350. tools/cli_commands/cost_report/view.py +7 -6
  351. tools/cli_commands/erv2.py +3 -1
  352. tools/cli_commands/systems_and_tools.py +5 -1
  353. tools/qontract_cli.py +31 -18
  354. tools/template_validation.py +3 -1
  355. {qontract_reconcile-0.10.2.dev349.dist-info → qontract_reconcile-0.10.2.dev414.dist-info}/WHEEL +0 -0
  356. {qontract_reconcile-0.10.2.dev349.dist-info → qontract_reconcile-0.10.2.dev414.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,149 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from reconcile.utils.json import json_dumps
6
+
7
+ if TYPE_CHECKING:
8
+ from mypy_boto3_cloudformation import CloudFormationClient
9
+ from mypy_boto3_cloudformation.type_defs import (
10
+ ParameterTypeDef,
11
+ StackTypeDef,
12
+ TagTypeDef,
13
+ )
14
+
15
+
16
+ class AWSApiCloudFormation:
17
+ def __init__(self, client: CloudFormationClient) -> None:
18
+ self.client = client
19
+
20
+ def create_stack(
21
+ self,
22
+ stack_name: str,
23
+ change_set_name: str,
24
+ template_body: str,
25
+ parameters: dict[str, str] | None = None,
26
+ tags: dict[str, str] | None = None,
27
+ ) -> str:
28
+ """
29
+ Create a CloudFormation stack using a change set with import existing resources.
30
+ This method creates a change set of type "CREATE" with the provided template and parameters,
31
+ waits for the change set to be created, executes it, and then waits for the stack creation to complete.
32
+ It returns the StackId of the created stack.
33
+
34
+ Args:
35
+ stack_name (str): The name of the stack to create.
36
+ change_set_name (str): The name of the change set to create.
37
+ template_body (str): The CloudFormation template body as a string.
38
+ parameters (dict[str, str] | None): A dictionary of parameter key-value pairs for
39
+ the stack. Defaults to None.
40
+ tags (dict[str, str] | None): A dictionary of tag key-value pairs to
41
+ associate with the stack. Defaults to None.
42
+
43
+ Returns:
44
+ str: The StackId of the created stack.
45
+ """
46
+ response = self.client.create_change_set(
47
+ StackName=stack_name,
48
+ ChangeSetName=change_set_name,
49
+ TemplateBody=template_body,
50
+ ChangeSetType="CREATE",
51
+ Parameters=self._build_parameters(parameters or {}),
52
+ Tags=self._build_tags(tags or {}),
53
+ ImportExistingResources=True,
54
+ )
55
+ change_set_arn = response["Id"]
56
+ self.client.get_waiter("change_set_create_complete").wait(
57
+ ChangeSetName=change_set_arn
58
+ )
59
+ self.client.execute_change_set(ChangeSetName=change_set_arn)
60
+ self.client.get_waiter("stack_create_complete").wait(StackName=stack_name)
61
+ return response["StackId"]
62
+
63
+ def update_stack(
64
+ self,
65
+ stack_name: str,
66
+ template_body: str,
67
+ parameters: dict[str, str] | None = None,
68
+ tags: dict[str, str] | None = None,
69
+ ) -> str:
70
+ """
71
+ Update a CloudFormation stack with the provided template and parameters.
72
+ This method updates the specified stack, waits for the update to complete,
73
+ and returns the StackId of the updated stack.
74
+
75
+ Args:
76
+ stack_name (str): The name of the stack to update.
77
+ template_body (str): The CloudFormation template body as a string.
78
+ parameters (dict[str, str] | None): A dictionary of parameter key-value pairs for
79
+ the stack. Defaults to None.
80
+ tags (dict[str, str] | None): A dictionary of tag key-value pairs to
81
+ associate with the stack. Defaults to None.
82
+
83
+ Returns:
84
+ str: The StackId of the updated stack.
85
+ """
86
+ response = self.client.update_stack(
87
+ StackName=stack_name,
88
+ TemplateBody=template_body,
89
+ Parameters=self._build_parameters(parameters or {}),
90
+ Tags=self._build_tags(tags or {}),
91
+ )
92
+ self.client.get_waiter("stack_update_complete").wait(StackName=stack_name)
93
+ return response["StackId"]
94
+
95
+ def get_stack(self, stack_name: str) -> StackTypeDef | None:
96
+ """
97
+ Retrieve information about a CloudFormation stack by its name.
98
+ If the stack exists, it returns the stack details as a dictionary.
99
+ If the stack does not exist, it returns None.
100
+
101
+ Args:
102
+ stack_name (str): The name of the stack to retrieve.
103
+
104
+ Returns:
105
+ StackTypeDef | None: The stack details if found, otherwise None.
106
+ """
107
+ try:
108
+ response = self.client.describe_stacks(StackName=stack_name)
109
+ return response["Stacks"][0]
110
+ except self.client.exceptions.ClientError as e:
111
+ if e.response["Error"]["Code"] == "ValidationError":
112
+ return None
113
+ raise
114
+
115
+ def get_template_body(self, stack_name: str) -> str:
116
+ """
117
+ Retrieve the CloudFormation template body for a specified stack.
118
+
119
+ Args:
120
+ stack_name (str): The name of the stack whose template is to be retrieved.
121
+
122
+ Returns:
123
+ str: The CloudFormation template body as a string.
124
+ """
125
+ response = self.client.get_template(StackName=stack_name)
126
+ # TemplateBody is str when using yaml
127
+ if isinstance(response["TemplateBody"], str):
128
+ return response["TemplateBody"]
129
+ return json_dumps(response["TemplateBody"])
130
+
131
+ @staticmethod
132
+ def _build_parameters(parameters: dict[str, str]) -> list[ParameterTypeDef]:
133
+ return [
134
+ {
135
+ "ParameterKey": key,
136
+ "ParameterValue": value,
137
+ }
138
+ for key, value in sorted(parameters.items())
139
+ ]
140
+
141
+ @staticmethod
142
+ def _build_tags(tags: dict[str, str]) -> list[TagTypeDef]:
143
+ return [
144
+ {
145
+ "Key": key,
146
+ "Value": value,
147
+ }
148
+ for key, value in sorted(tags.items())
149
+ ]
@@ -0,0 +1,73 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ if TYPE_CHECKING:
6
+ from collections.abc import Iterable, Iterator
7
+
8
+ from mypy_boto3_logs import CloudWatchLogsClient
9
+ from mypy_boto3_logs.type_defs import LogGroupTypeDef
10
+
11
+
12
+ class AWSApiLogs:
13
+ def __init__(self, client: CloudWatchLogsClient) -> None:
14
+ self.client = client
15
+
16
+ def get_log_groups(self) -> Iterator[LogGroupTypeDef]:
17
+ paginator = self.client.get_paginator("describe_log_groups")
18
+ for page in paginator.paginate():
19
+ yield from page["logGroups"]
20
+
21
+ def delete_log_group(self, log_group_name: str) -> None:
22
+ self.client.delete_log_group(logGroupName=log_group_name)
23
+
24
+ def put_retention_policy(
25
+ self,
26
+ log_group_name: str,
27
+ retention_in_days: int,
28
+ ) -> None:
29
+ self.client.put_retention_policy(
30
+ logGroupName=log_group_name,
31
+ retentionInDays=retention_in_days,
32
+ )
33
+
34
+ def get_tags(self, arn: str) -> dict[str, str]:
35
+ tags = self.client.list_tags_for_resource(
36
+ resourceArn=self._normalize_log_group_arn(arn),
37
+ )
38
+ return tags.get("tags") or {}
39
+
40
+ def set_tags(
41
+ self,
42
+ arn: str,
43
+ tags: dict[str, str],
44
+ ) -> None:
45
+ self.client.tag_resource(
46
+ resourceArn=self._normalize_log_group_arn(arn),
47
+ tags=tags,
48
+ )
49
+
50
+ def delete_tags(
51
+ self,
52
+ arn: str,
53
+ tag_keys: Iterable[str],
54
+ ) -> None:
55
+ self.client.untag_resource(
56
+ resourceArn=self._normalize_log_group_arn(arn),
57
+ tagKeys=list(tag_keys),
58
+ )
59
+
60
+ @staticmethod
61
+ def _normalize_log_group_arn(arn: str) -> str:
62
+ """
63
+ Normalize a log group ARN by removing any trailing ":*".
64
+
65
+ DescribeLogGroups response arn has additional :* at the end.
66
+
67
+ Args:
68
+ arn: The ARN of the log group.
69
+
70
+ Returns:
71
+ The normalized ARN without the trailing ":*".
72
+ """
73
+ return arn.rstrip(":*")
@@ -52,9 +52,11 @@ class AwsOrganizationOU(BaseModel):
52
52
  class AWSAccountStatus(BaseModel):
53
53
  id: str = Field(..., alias="Id")
54
54
  name: str = Field(..., alias="AccountName")
55
- uid: str | None = Field(alias="AccountId")
55
+ uid: str | None = Field(None, alias="AccountId")
56
56
  state: str = Field(..., alias="State")
57
- failure_reason: CreateAccountFailureReasonType | None = Field(alias="FailureReason")
57
+ failure_reason: CreateAccountFailureReasonType | None = Field(
58
+ None, alias="FailureReason"
59
+ )
58
60
 
59
61
 
60
62
  class AWSAccount(BaseModel):
@@ -0,0 +1,67 @@
1
+ from datetime import UTC, datetime
2
+
3
+ ISO8601 = "%Y-%m-%dT%H:%M:%SZ"
4
+ ISO8601_MICRO = "%Y-%m-%dT%H:%M:%S.%fZ"
5
+
6
+
7
+ def utc_now() -> datetime:
8
+ """
9
+ Get the current UTC datetime.
10
+
11
+ Returns:
12
+ A datetime object representing the current time in UTC.
13
+ """
14
+ return datetime.now(tz=UTC)
15
+
16
+
17
+ def ensure_utc(dt: datetime) -> datetime:
18
+ """
19
+ Ensure the provided datetime is in UTC timezone.
20
+
21
+ Args:
22
+ dt: A datetime object.
23
+
24
+ Returns:
25
+ A datetime object in UTC timezone.
26
+ """
27
+ if dt.tzinfo is None:
28
+ return dt.replace(tzinfo=UTC)
29
+ return dt.astimezone(UTC)
30
+
31
+
32
+ def to_utc_seconds_iso_format(dt: datetime) -> str:
33
+ """
34
+ Convert a datetime object to ISO 8601 format YYYY-MM-DDTHH:MM:SSZ.
35
+
36
+ Args:
37
+ dt: A datetime object.
38
+
39
+ Returns:
40
+ A string representing the datetime in ISO 8601 format.
41
+ """
42
+ return ensure_utc(dt).strftime(ISO8601)
43
+
44
+
45
+ def to_utc_microseconds_iso_format(dt: datetime) -> str:
46
+ """
47
+ Convert a datetime object to ISO 8601 format with microseconds YYYY-MM-DDTHH:MM:SS.mmmmmmZ.
48
+
49
+ Args:
50
+ dt: A datetime object.
51
+
52
+ Returns:
53
+ A string representing the datetime in ISO 8601 format with microseconds.
54
+ """
55
+ return ensure_utc(dt).strftime(ISO8601_MICRO)
56
+
57
+
58
+ def from_utc_iso_format(dt_str: str) -> datetime:
59
+ """
60
+ Parse a datetime string in ISO 8601 format to a datetime object.
61
+
62
+ Args:
63
+ dt_str: A string representing the datetime in ISO 8601 format.
64
+ Returns:
65
+ A datetime object in UTC timezone.
66
+ """
67
+ return ensure_utc(datetime.fromisoformat(dt_str))
@@ -26,7 +26,7 @@ class Snitch(BaseModel):
26
26
  interval: str
27
27
  alert_type: str
28
28
  alert_email: list[str]
29
- vault_data: str | None
29
+ vault_data: str | None = None
30
30
 
31
31
  def needs_vault_update(self) -> bool:
32
32
  return self.vault_data is not None and self.check_in_url != self.vault_data
reconcile/utils/differ.py CHANGED
@@ -7,7 +7,6 @@ from collections.abc import (
7
7
  from dataclasses import dataclass
8
8
  from typing import (
9
9
  Any,
10
- Generic,
11
10
  TypeVar,
12
11
  )
13
12
 
@@ -18,13 +17,13 @@ Key = TypeVar("Key")
18
17
 
19
18
 
20
19
  @dataclass(frozen=True, eq=True)
21
- class DiffPair(Generic[Current, Desired]):
20
+ class DiffPair[Current, Desired]:
22
21
  current: Current
23
22
  desired: Desired
24
23
 
25
24
 
26
25
  @dataclass(frozen=True, eq=True)
27
- class DiffResult(Generic[Current, Desired, Key]):
26
+ class DiffResult[Current, Desired, Key]:
28
27
  add: dict[Key, Desired]
29
28
  delete: dict[Key, Current]
30
29
  change: dict[Key, DiffPair[Current, Desired]]
@@ -5,8 +5,9 @@ from functools import cached_property
5
5
  from typing import Any, Self
6
6
 
7
7
  from deepdiff import DeepHash
8
- from pydantic import BaseModel
8
+ from pydantic import BaseModel, ConfigDict
9
9
 
10
+ from reconcile.utils.datetime_util import utc_now
10
11
  from reconcile.utils.secret_reader import SecretReaderBase
11
12
  from reconcile.utils.state import State, init_state
12
13
 
@@ -16,7 +17,7 @@ CACHE_SOURCE_DIGEST_METADATA_KEY = "cache-source-digest"
16
17
  LATEST_CACHE_SOURCE_DIGEST_METADATA_KEY = "latest-cache-source-digest"
17
18
 
18
19
 
19
- class CacheKeyWithDigest(BaseModel):
20
+ class CacheKeyWithDigest(BaseModel, frozen=True):
20
21
  integration: str
21
22
  integration_version: str
22
23
  dry_run: bool
@@ -69,9 +70,6 @@ class CacheKeyWithDigest(BaseModel):
69
70
  args.append(f"--shard {self.shard}")
70
71
  return " ".join(args)
71
72
 
72
- class Config:
73
- frozen = True
74
-
75
73
 
76
74
  class CacheKey(BaseModel):
77
75
  integration: str
@@ -121,9 +119,10 @@ class CacheKey(BaseModel):
121
119
  """
122
120
  return self.cache_key_with_digest.build_cli_delete_args()
123
121
 
124
- class Config:
125
- frozen = True
126
- keep_untouched = (cached_property,)
122
+ model_config = ConfigDict(
123
+ frozen=True,
124
+ ignored_types=(cached_property,),
125
+ )
127
126
 
128
127
 
129
128
  class CacheValue(BaseModel):
@@ -167,7 +166,7 @@ class EarlyExitCache:
167
166
 
168
167
  def get(self, key: CacheKey) -> CacheValue:
169
168
  value = self.state.get(str(key))
170
- return CacheValue.parse_obj(value)
169
+ return CacheValue.model_validate(value)
171
170
 
172
171
  def set(
173
172
  self,
@@ -185,7 +184,7 @@ class EarlyExitCache:
185
184
  :param latest_cache_source_digest: latest cache source digest, used to check stale for dry run cache
186
185
  :return: None
187
186
  """
188
- expire_at = datetime.now(tz=UTC) + timedelta(seconds=ttl_seconds)
187
+ expire_at = utc_now() + timedelta(seconds=ttl_seconds)
189
188
  metadata = {
190
189
  EXPIRE_AT_METADATA_KEY: str(int(expire_at.timestamp())),
191
190
  CACHE_SOURCE_DIGEST_METADATA_KEY: key.cache_source_digest,
@@ -193,7 +192,7 @@ class EarlyExitCache:
193
192
  }
194
193
  self.state.add(
195
194
  str(key),
196
- value.dict(),
195
+ value.model_dump(),
197
196
  metadata=metadata,
198
197
  force=True,
199
198
  )
@@ -233,7 +232,7 @@ class EarlyExitCache:
233
232
  int(metadata[EXPIRE_AT_METADATA_KEY]),
234
233
  tz=UTC,
235
234
  )
236
- now = datetime.now(UTC)
235
+ now = utc_now()
237
236
  return now >= expire_at
238
237
 
239
238
  def _head_dry_run_status(
@@ -6,6 +6,8 @@ from typing import (
6
6
  cast,
7
7
  )
8
8
 
9
+ from reconcile.utils.datetime_util import ensure_utc, utc_now
10
+
9
11
  DATE_FORMAT = "%Y-%m-%d"
10
12
 
11
13
 
@@ -17,12 +19,14 @@ DictsOrRoles = TypeVar("DictsOrRoles", bound=Iterable[FilterableRole] | Iterable
17
19
 
18
20
 
19
21
  def date_expired(date: str) -> bool:
20
- exp_date = datetime.datetime.strptime(date, DATE_FORMAT).date()
21
- current_date = datetime.datetime.utcnow().date()
22
+ exp_date = ensure_utc(datetime.datetime.strptime(date, DATE_FORMAT)).date() # noqa: DTZ007
23
+ current_date = utc_now().date()
22
24
  return current_date >= exp_date
23
25
 
24
26
 
25
- def filter(roles: DictsOrRoles | None) -> DictsOrRoles:
27
+ def filter[DictsOrRoles: Iterable[FilterableRole] | Iterable[dict]](
28
+ roles: DictsOrRoles | None,
29
+ ) -> DictsOrRoles:
26
30
  """Filters roles and returns the ones which are not yet expired."""
27
31
  filtered = []
28
32
  for r in roles or []:
@@ -144,13 +144,36 @@ class ExternalResourceSpec:
144
144
  return {}
145
145
 
146
146
  def tags(self, integration: str) -> dict[str, str]:
147
- return {
147
+ tags = {
148
148
  "managed_by_integration": integration,
149
149
  "cluster": self.cluster_name,
150
150
  "namespace": self.namespace_name,
151
151
  "environment": self.namespace["environment"]["name"],
152
152
  "app": self.namespace["app"]["name"],
153
153
  }
154
+ if app_code := self.namespace["app"].get("appCode"):
155
+ tags["app-code"] = app_code
156
+ if cost_center := self.namespace["app"].get("costCenter"):
157
+ tags["cost-center"] = cost_center
158
+ if service_phase := self.namespace["environment"].get("servicePhase"):
159
+ tags["service-phase"] = service_phase
160
+
161
+ resource_tags_str = self.resource.get("tags")
162
+ if resource_tags_str:
163
+ resource_tags = json.loads(resource_tags_str)
164
+ # normalize camelCase keys to kebab-case
165
+ key_mapping = {
166
+ "appCode": "app-code",
167
+ "costCenter": "cost-center",
168
+ "servicePhase": "service-phase",
169
+ }
170
+ normalized_tags = {}
171
+ for key, value in resource_tags.items():
172
+ normalized_key = key_mapping.get(key, key)
173
+ normalized_tags[normalized_key] = value
174
+ tags.update(normalized_tags)
175
+
176
+ return tags
154
177
 
155
178
  def get_secret_field(self, field: str) -> str | None:
156
179
  return self.secret.get(field)
@@ -6,7 +6,7 @@ KeyType = TypeVar("KeyType")
6
6
  ValueType = TypeVar("ValueType")
7
7
 
8
8
 
9
- def remove_none_values_from_dict(
9
+ def remove_none_values_from_dict[KeyType, ValueType](
10
10
  a_dict: dict[KeyType, ValueType | None],
11
11
  ) -> dict[KeyType, ValueType]:
12
12
  """
@@ -263,13 +263,13 @@ class GitLabApi:
263
263
  # we can determine if a pending MR exists based on the title
264
264
  return any(mr.title == title for mr in mrs)
265
265
 
266
- @retry()
266
+ @retry(no_retry_exceptions=(RuntimeError,))
267
267
  def get_project_maintainers(
268
268
  self, repo_url: str | None = None, query: dict | None = None
269
- ) -> list[str] | None:
269
+ ) -> list[str]:
270
270
  project = self.project if repo_url is None else self.get_project(repo_url)
271
271
  if project is None:
272
- return None
272
+ raise RuntimeError("project not found")
273
273
  members = project.members_all.list(iterator=True, query_parameters=query or {})
274
274
  return [m.username for m in members if m.access_level >= 40]
275
275
 
@@ -826,14 +826,16 @@ class GitLabApi:
826
826
  )
827
827
 
828
828
  def get_commit_sha(self, ref: str, repo_url: str) -> str:
829
- project = self.get_project(repo_url)
829
+ if not (project := self.get_project(repo_url)):
830
+ raise ValueError(f"Project not found for repo_url: {repo_url}")
830
831
  commits = project.commits.list(ref_name=ref, per_page=1, page=1)
831
832
  return commits[0].id
832
833
 
833
834
  def repository_compare(
834
835
  self, repo_url: str, ref_from: str, ref_to: str
835
836
  ) -> list[dict[str, Any]]:
836
- project = self.get_project(repo_url)
837
+ if not (project := self.get_project(repo_url)):
838
+ raise ValueError(f"Project not found for repo_url: {repo_url}")
837
839
  response: Any = project.repository_compare(ref_from, ref_to)
838
840
  return response.get("commits", [])
839
841
 
@@ -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()