qontract-reconcile 0.10.2.dev361__py3-none-any.whl → 0.10.2.dev474__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 (371) hide show
  1. {qontract_reconcile-0.10.2.dev361.dist-info → qontract_reconcile-0.10.2.dev474.dist-info}/METADATA +14 -13
  2. {qontract_reconcile-0.10.2.dev361.dist-info → qontract_reconcile-0.10.2.dev474.dist-info}/RECORD +371 -364
  3. {qontract_reconcile-0.10.2.dev361.dist-info → qontract_reconcile-0.10.2.dev474.dist-info}/WHEEL +1 -1
  4. reconcile/acs_rbac.py +2 -2
  5. reconcile/aus/advanced_upgrade_service.py +18 -12
  6. reconcile/aus/aus_sts_gate_handler.py +59 -0
  7. reconcile/aus/base.py +137 -34
  8. reconcile/aus/cluster_version_data.py +15 -5
  9. reconcile/aus/models.py +3 -1
  10. reconcile/aus/ocm_addons_upgrade_scheduler_org.py +1 -0
  11. reconcile/aus/ocm_upgrade_scheduler.py +8 -1
  12. reconcile/aus/ocm_upgrade_scheduler_org.py +20 -5
  13. reconcile/aus/version_gate_approver.py +1 -16
  14. reconcile/aus/version_gates/sts_version_gate_handler.py +5 -72
  15. reconcile/automated_actions/config/integration.py +16 -4
  16. reconcile/aws_account_manager/integration.py +21 -9
  17. reconcile/aws_account_manager/reconciler.py +3 -3
  18. reconcile/aws_account_manager/utils.py +1 -1
  19. reconcile/aws_ami_cleanup/integration.py +8 -12
  20. reconcile/aws_ami_share.py +69 -62
  21. reconcile/aws_cloudwatch_log_retention/integration.py +155 -126
  22. reconcile/aws_ecr_image_pull_secrets.py +1 -1
  23. reconcile/aws_iam_keys.py +1 -0
  24. reconcile/aws_saml_idp/integration.py +12 -4
  25. reconcile/aws_saml_roles/integration.py +30 -23
  26. reconcile/aws_version_sync/integration.py +6 -12
  27. reconcile/change_owners/README.md +1 -1
  28. reconcile/change_owners/bundle.py +3 -3
  29. reconcile/change_owners/change_log_tracking.py +3 -2
  30. reconcile/change_owners/change_owners.py +108 -42
  31. reconcile/change_owners/decision.py +1 -1
  32. reconcile/change_owners/diff.py +0 -2
  33. reconcile/checkpoint.py +11 -3
  34. reconcile/cli.py +94 -11
  35. reconcile/dashdotdb_dora.py +5 -12
  36. reconcile/dashdotdb_slo.py +1 -1
  37. reconcile/database_access_manager.py +123 -117
  38. reconcile/dynatrace_token_provider/integration.py +1 -1
  39. reconcile/endpoints_discovery/integration.py +4 -1
  40. reconcile/endpoints_discovery/merge_request.py +1 -1
  41. reconcile/endpoints_discovery/merge_request_manager.py +8 -8
  42. reconcile/external_resources/factories.py +4 -6
  43. reconcile/external_resources/integration.py +1 -1
  44. reconcile/external_resources/manager.py +8 -6
  45. reconcile/external_resources/meta.py +0 -1
  46. reconcile/external_resources/metrics.py +1 -1
  47. reconcile/external_resources/model.py +19 -15
  48. reconcile/external_resources/reconciler.py +7 -4
  49. reconcile/external_resources/secrets_sync.py +6 -10
  50. reconcile/external_resources/state.py +26 -16
  51. reconcile/fleet_labeler/integration.py +1 -1
  52. reconcile/gabi_authorized_users.py +5 -2
  53. reconcile/gcp_image_mirror.py +2 -2
  54. reconcile/github_org.py +1 -1
  55. reconcile/github_owners.py +4 -0
  56. reconcile/gitlab_housekeeping.py +13 -15
  57. reconcile/gitlab_members.py +6 -12
  58. reconcile/gitlab_owners.py +15 -11
  59. reconcile/gitlab_permissions.py +8 -12
  60. reconcile/glitchtip_project_alerts/integration.py +3 -1
  61. reconcile/gql_definitions/acs/acs_instances.py +5 -5
  62. reconcile/gql_definitions/acs/acs_policies.py +5 -5
  63. reconcile/gql_definitions/acs/acs_rbac.py +5 -5
  64. reconcile/gql_definitions/advanced_upgrade_service/aus_clusters.py +5 -5
  65. reconcile/gql_definitions/advanced_upgrade_service/aus_organization.py +5 -5
  66. reconcile/gql_definitions/app_interface_metrics_exporter/onboarding_status.py +5 -5
  67. reconcile/gql_definitions/app_sre_tekton_access_revalidation/roles.py +5 -5
  68. reconcile/gql_definitions/app_sre_tekton_access_revalidation/users.py +5 -5
  69. reconcile/gql_definitions/automated_actions/instance.py +46 -7
  70. reconcile/gql_definitions/aws_account_manager/aws_accounts.py +14 -5
  71. reconcile/gql_definitions/aws_ami_cleanup/aws_accounts.py +15 -5
  72. reconcile/gql_definitions/aws_cloudwatch_log_retention/aws_accounts.py +27 -66
  73. reconcile/gql_definitions/aws_saml_idp/aws_accounts.py +15 -5
  74. reconcile/gql_definitions/aws_saml_roles/aws_accounts.py +15 -5
  75. reconcile/gql_definitions/aws_saml_roles/roles.py +5 -5
  76. reconcile/gql_definitions/aws_version_sync/clusters.py +5 -5
  77. reconcile/gql_definitions/aws_version_sync/namespaces.py +5 -5
  78. reconcile/gql_definitions/change_owners/queries/change_types.py +5 -5
  79. reconcile/gql_definitions/change_owners/queries/self_service_roles.py +5 -5
  80. reconcile/gql_definitions/cluster_auth_rhidp/clusters.py +5 -5
  81. reconcile/gql_definitions/common/alerting_services_settings.py +5 -5
  82. reconcile/gql_definitions/common/app_code_component_repos.py +5 -5
  83. reconcile/gql_definitions/common/app_interface_custom_messages.py +5 -5
  84. reconcile/gql_definitions/common/app_interface_dms_settings.py +5 -5
  85. reconcile/gql_definitions/common/app_interface_repo_settings.py +5 -5
  86. reconcile/gql_definitions/common/app_interface_roles.py +5 -5
  87. reconcile/gql_definitions/common/app_interface_state_settings.py +5 -5
  88. reconcile/gql_definitions/common/app_interface_vault_settings.py +5 -5
  89. reconcile/gql_definitions/common/app_quay_repos_escalation_policies.py +5 -5
  90. reconcile/gql_definitions/common/apps.py +5 -5
  91. reconcile/gql_definitions/common/aws_vpc_requests.py +18 -5
  92. reconcile/gql_definitions/common/aws_vpcs.py +5 -5
  93. reconcile/gql_definitions/common/clusters.py +7 -5
  94. reconcile/gql_definitions/common/clusters_minimal.py +5 -5
  95. reconcile/gql_definitions/common/clusters_with_dms.py +5 -5
  96. reconcile/gql_definitions/common/clusters_with_peering.py +5 -5
  97. reconcile/gql_definitions/common/github_orgs.py +5 -5
  98. reconcile/gql_definitions/common/jira_settings.py +5 -5
  99. reconcile/gql_definitions/common/jiralert_settings.py +5 -5
  100. reconcile/gql_definitions/common/ldap_settings.py +5 -5
  101. reconcile/gql_definitions/common/namespaces.py +5 -5
  102. reconcile/gql_definitions/common/namespaces_minimal.py +7 -5
  103. reconcile/gql_definitions/common/ocm_env_telemeter.py +5 -5
  104. reconcile/gql_definitions/common/ocm_environments.py +5 -5
  105. reconcile/gql_definitions/common/pagerduty_instances.py +5 -5
  106. reconcile/gql_definitions/common/pgp_reencryption_settings.py +5 -5
  107. reconcile/gql_definitions/common/pipeline_providers.py +5 -5
  108. reconcile/gql_definitions/common/quay_instances.py +5 -5
  109. reconcile/gql_definitions/common/quay_orgs.py +5 -5
  110. reconcile/gql_definitions/common/reserved_networks.py +5 -5
  111. reconcile/gql_definitions/common/rhcs_provider_settings.py +5 -5
  112. reconcile/gql_definitions/common/saas_files.py +5 -5
  113. reconcile/gql_definitions/common/saas_target_namespaces.py +5 -5
  114. reconcile/gql_definitions/common/saasherder_settings.py +5 -5
  115. reconcile/gql_definitions/common/slack_workspaces.py +5 -5
  116. reconcile/gql_definitions/common/smtp_client_settings.py +5 -5
  117. reconcile/gql_definitions/common/state_aws_account.py +5 -5
  118. reconcile/gql_definitions/common/users.py +5 -5
  119. reconcile/gql_definitions/common/users_with_paths.py +5 -5
  120. reconcile/gql_definitions/cost_report/app_names.py +5 -5
  121. reconcile/gql_definitions/cost_report/cost_namespaces.py +5 -5
  122. reconcile/gql_definitions/cost_report/settings.py +5 -5
  123. reconcile/gql_definitions/dashdotdb_slo/slo_documents_query.py +5 -5
  124. reconcile/gql_definitions/dynatrace_token_provider/dynatrace_bootstrap_tokens.py +5 -5
  125. reconcile/gql_definitions/dynatrace_token_provider/token_specs.py +5 -5
  126. reconcile/gql_definitions/email_sender/apps.py +5 -5
  127. reconcile/gql_definitions/email_sender/emails.py +5 -5
  128. reconcile/gql_definitions/email_sender/users.py +5 -5
  129. reconcile/gql_definitions/endpoints_discovery/apps.py +5 -5
  130. reconcile/gql_definitions/external_resources/aws_accounts.py +5 -5
  131. reconcile/gql_definitions/external_resources/external_resources_modules.py +5 -5
  132. reconcile/gql_definitions/external_resources/external_resources_namespaces.py +38 -7
  133. reconcile/gql_definitions/external_resources/external_resources_settings.py +5 -5
  134. reconcile/gql_definitions/external_resources/fragments/external_resources_module_overrides.py +5 -5
  135. reconcile/gql_definitions/fleet_labeler/fleet_labels.py +5 -5
  136. reconcile/gql_definitions/fragments/aus_organization.py +5 -5
  137. reconcile/gql_definitions/fragments/aws_account_common.py +7 -5
  138. reconcile/gql_definitions/fragments/aws_account_managed.py +5 -5
  139. reconcile/gql_definitions/fragments/aws_account_sso.py +5 -5
  140. reconcile/gql_definitions/fragments/aws_infra_management_account.py +5 -5
  141. reconcile/gql_definitions/fragments/aws_organization.py +33 -0
  142. reconcile/gql_definitions/fragments/aws_vpc.py +5 -5
  143. reconcile/gql_definitions/fragments/aws_vpc_request.py +12 -5
  144. reconcile/gql_definitions/fragments/container_image_mirror.py +5 -5
  145. reconcile/gql_definitions/fragments/deploy_resources.py +5 -5
  146. reconcile/gql_definitions/fragments/disable.py +5 -5
  147. reconcile/gql_definitions/fragments/email_service.py +5 -5
  148. reconcile/gql_definitions/fragments/email_user.py +5 -5
  149. reconcile/gql_definitions/fragments/jumphost_common_fields.py +5 -5
  150. reconcile/gql_definitions/fragments/membership_source.py +5 -5
  151. reconcile/gql_definitions/fragments/minimal_ocm_organization.py +5 -5
  152. reconcile/gql_definitions/fragments/oc_connection_cluster.py +5 -5
  153. reconcile/gql_definitions/fragments/ocm_environment.py +5 -5
  154. reconcile/gql_definitions/fragments/pipeline_provider_retention.py +5 -5
  155. reconcile/gql_definitions/fragments/prometheus_instance.py +5 -5
  156. reconcile/gql_definitions/fragments/resource_limits_requirements.py +5 -5
  157. reconcile/gql_definitions/fragments/resource_requests_requirements.py +5 -5
  158. reconcile/gql_definitions/fragments/resource_values.py +5 -5
  159. reconcile/gql_definitions/fragments/saas_slo_document.py +5 -5
  160. reconcile/gql_definitions/fragments/saas_target_namespace.py +5 -5
  161. reconcile/gql_definitions/fragments/serviceaccount_token.py +5 -5
  162. reconcile/gql_definitions/fragments/terraform_state.py +5 -5
  163. reconcile/gql_definitions/fragments/upgrade_policy.py +5 -5
  164. reconcile/gql_definitions/fragments/user.py +5 -5
  165. reconcile/gql_definitions/fragments/vault_secret.py +5 -5
  166. reconcile/gql_definitions/gcp/gcp_docker_repos.py +5 -5
  167. reconcile/gql_definitions/gcp/gcp_projects.py +5 -5
  168. reconcile/gql_definitions/gitlab_members/gitlab_instances.py +5 -5
  169. reconcile/gql_definitions/gitlab_members/permissions.py +5 -5
  170. reconcile/gql_definitions/glitchtip/glitchtip_instance.py +5 -5
  171. reconcile/gql_definitions/glitchtip/glitchtip_project.py +5 -5
  172. reconcile/gql_definitions/glitchtip_project_alerts/glitchtip_project.py +5 -5
  173. reconcile/gql_definitions/integrations/integrations.py +5 -5
  174. reconcile/gql_definitions/introspection.json +775 -136
  175. reconcile/gql_definitions/jenkins_configs/jenkins_configs.py +5 -5
  176. reconcile/gql_definitions/jenkins_configs/jenkins_instances.py +5 -5
  177. reconcile/gql_definitions/jira/jira_servers.py +5 -5
  178. reconcile/gql_definitions/jira_permissions_validator/jira_boards_for_permissions_validator.py +9 -5
  179. reconcile/gql_definitions/jumphosts/jumphosts.py +5 -5
  180. reconcile/gql_definitions/ldap_groups/roles.py +5 -5
  181. reconcile/gql_definitions/ldap_groups/settings.py +5 -5
  182. reconcile/gql_definitions/maintenance/maintenances.py +5 -5
  183. reconcile/gql_definitions/membershipsources/roles.py +5 -5
  184. reconcile/gql_definitions/ocm_labels/clusters.py +5 -5
  185. reconcile/gql_definitions/ocm_labels/organizations.py +5 -5
  186. reconcile/gql_definitions/openshift_cluster_bots/clusters.py +5 -5
  187. reconcile/gql_definitions/openshift_groups/managed_groups.py +5 -5
  188. reconcile/gql_definitions/openshift_groups/managed_roles.py +5 -5
  189. reconcile/gql_definitions/openshift_serviceaccount_tokens/tokens.py +5 -5
  190. reconcile/gql_definitions/quay_membership/quay_membership.py +5 -5
  191. reconcile/gql_definitions/rhcs/certs.py +25 -79
  192. reconcile/gql_definitions/rhcs/openshift_resource_rhcs_cert.py +43 -0
  193. reconcile/gql_definitions/rhidp/organizations.py +5 -5
  194. reconcile/gql_definitions/service_dependencies/jenkins_instance_fragment.py +5 -5
  195. reconcile/gql_definitions/service_dependencies/service_dependencies.py +5 -5
  196. reconcile/gql_definitions/sharding/aws_accounts.py +5 -5
  197. reconcile/gql_definitions/sharding/ocm_organization.py +5 -5
  198. reconcile/gql_definitions/skupper_network/site_controller_template.py +5 -5
  199. reconcile/gql_definitions/skupper_network/skupper_networks.py +5 -5
  200. reconcile/gql_definitions/slack_usergroups/clusters.py +5 -5
  201. reconcile/gql_definitions/slack_usergroups/permissions.py +5 -5
  202. reconcile/gql_definitions/slack_usergroups/users.py +5 -5
  203. reconcile/gql_definitions/slo_documents/slo_documents.py +5 -5
  204. reconcile/gql_definitions/status_board/status_board.py +5 -5
  205. reconcile/gql_definitions/statuspage/statuspages.py +5 -5
  206. reconcile/gql_definitions/templating/template_collection.py +5 -5
  207. reconcile/gql_definitions/templating/templates.py +5 -5
  208. reconcile/gql_definitions/terraform_cloudflare_dns/app_interface_cloudflare_dns_settings.py +5 -5
  209. reconcile/gql_definitions/terraform_cloudflare_dns/terraform_cloudflare_zones.py +5 -5
  210. reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_accounts.py +5 -5
  211. reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_resources.py +5 -5
  212. reconcile/gql_definitions/terraform_cloudflare_users/app_interface_setting_cloudflare_and_vault.py +5 -5
  213. reconcile/gql_definitions/terraform_cloudflare_users/terraform_cloudflare_roles.py +5 -5
  214. reconcile/gql_definitions/terraform_init/aws_accounts.py +19 -5
  215. reconcile/gql_definitions/terraform_repo/terraform_repo.py +5 -5
  216. reconcile/gql_definitions/terraform_resources/database_access_manager.py +5 -5
  217. reconcile/gql_definitions/terraform_resources/terraform_resources_namespaces.py +37 -7
  218. reconcile/gql_definitions/terraform_tgw_attachments/aws_accounts.py +15 -5
  219. reconcile/gql_definitions/unleash_feature_toggles/feature_toggles.py +5 -5
  220. reconcile/gql_definitions/vault_instances/vault_instances.py +5 -5
  221. reconcile/gql_definitions/vault_policies/vault_policies.py +5 -5
  222. reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator.py +8 -5
  223. reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator_peered_cluster_fragment.py +6 -5
  224. reconcile/integrations_manager.py +3 -3
  225. reconcile/jenkins_worker_fleets.py +10 -8
  226. reconcile/jira_permissions_validator.py +237 -122
  227. reconcile/ldap_groups/integration.py +1 -1
  228. reconcile/ocm/types.py +35 -57
  229. reconcile/ocm_aws_infrastructure_access.py +1 -1
  230. reconcile/ocm_clusters.py +4 -4
  231. reconcile/ocm_labels/integration.py +3 -2
  232. reconcile/ocm_machine_pools.py +33 -27
  233. reconcile/openshift_base.py +113 -4
  234. reconcile/openshift_cluster_bots.py +1 -1
  235. reconcile/openshift_namespace_labels.py +1 -1
  236. reconcile/openshift_namespaces.py +96 -101
  237. reconcile/openshift_resources_base.py +6 -2
  238. reconcile/openshift_rhcs_certs.py +74 -37
  239. reconcile/openshift_rolebindings.py +7 -11
  240. reconcile/openshift_saas_deploy.py +4 -5
  241. reconcile/openshift_saas_deploy_change_tester.py +9 -7
  242. reconcile/openshift_saas_deploy_trigger_cleaner.py +3 -5
  243. reconcile/openshift_serviceaccount_tokens.py +2 -2
  244. reconcile/openshift_upgrade_watcher.py +4 -4
  245. reconcile/oum/labelset.py +5 -3
  246. reconcile/oum/models.py +1 -4
  247. reconcile/prometheus_rules_tester/integration.py +3 -3
  248. reconcile/quay_base.py +25 -6
  249. reconcile/quay_membership.py +55 -29
  250. reconcile/quay_mirror.py +1 -1
  251. reconcile/quay_mirror_org.py +6 -4
  252. reconcile/quay_permissions.py +81 -75
  253. reconcile/quay_repos.py +35 -37
  254. reconcile/queries.py +132 -1
  255. reconcile/rhidp/common.py +3 -5
  256. reconcile/rhidp/sso_client/base.py +16 -5
  257. reconcile/saas_auto_promotions_manager/merge_request_manager/renderer.py +1 -1
  258. reconcile/saas_auto_promotions_manager/subscriber.py +4 -3
  259. reconcile/skupper_network/integration.py +2 -2
  260. reconcile/slack_usergroups.py +35 -14
  261. reconcile/sql_query.py +1 -0
  262. reconcile/status_board.py +6 -6
  263. reconcile/statuspage/atlassian.py +7 -7
  264. reconcile/statuspage/integrations/maintenances.py +4 -3
  265. reconcile/statuspage/page.py +4 -9
  266. reconcile/statuspage/status.py +5 -8
  267. reconcile/templates/rosa-classic-cluster-creation.sh.j2 +1 -1
  268. reconcile/templates/rosa-hcp-cluster-creation.sh.j2 +1 -1
  269. reconcile/templating/lib/rendering.py +3 -3
  270. reconcile/templating/renderer.py +2 -2
  271. reconcile/templating/validator.py +4 -4
  272. reconcile/terraform_aws_route53.py +7 -1
  273. reconcile/terraform_cloudflare_dns.py +3 -3
  274. reconcile/terraform_cloudflare_resources.py +5 -5
  275. reconcile/terraform_cloudflare_users.py +3 -2
  276. reconcile/terraform_init/integration.py +187 -23
  277. reconcile/terraform_repo.py +16 -12
  278. reconcile/terraform_resources.py +6 -6
  279. reconcile/terraform_tgw_attachments.py +27 -19
  280. reconcile/terraform_users.py +7 -0
  281. reconcile/terraform_vpc_peerings.py +14 -3
  282. reconcile/terraform_vpc_resources/integration.py +20 -8
  283. reconcile/terraform_vpc_resources/merge_request.py +12 -2
  284. reconcile/terraform_vpc_resources/merge_request_manager.py +43 -19
  285. reconcile/typed_queries/aws_account_tags.py +41 -0
  286. reconcile/typed_queries/cost_report/app_names.py +1 -1
  287. reconcile/typed_queries/cost_report/cost_namespaces.py +2 -2
  288. reconcile/typed_queries/saas_files.py +20 -15
  289. reconcile/typed_queries/status_board.py +2 -2
  290. reconcile/unleash_feature_toggles/integration.py +4 -2
  291. reconcile/utils/acs/base.py +6 -3
  292. reconcile/utils/acs/policies.py +2 -2
  293. reconcile/utils/aws_api.py +51 -20
  294. reconcile/utils/aws_api_typed/api.py +38 -9
  295. reconcile/utils/aws_api_typed/cloudformation.py +149 -0
  296. reconcile/utils/aws_api_typed/logs.py +73 -0
  297. reconcile/utils/aws_api_typed/organization.py +4 -2
  298. reconcile/utils/binary.py +7 -12
  299. reconcile/utils/datetime_util.py +67 -0
  300. reconcile/utils/deadmanssnitch_api.py +1 -1
  301. reconcile/utils/differ.py +2 -3
  302. reconcile/utils/early_exit_cache.py +11 -12
  303. reconcile/utils/environ.py +5 -0
  304. reconcile/utils/expiration.py +7 -3
  305. reconcile/utils/external_resource_spec.py +2 -0
  306. reconcile/utils/filtering.py +1 -1
  307. reconcile/utils/gitlab_api.py +19 -5
  308. reconcile/utils/glitchtip/client.py +6 -2
  309. reconcile/utils/glitchtip/models.py +25 -28
  310. reconcile/utils/gql.py +4 -7
  311. reconcile/utils/helpers.py +1 -1
  312. reconcile/utils/instrumented_wrappers.py +1 -1
  313. reconcile/utils/internal_groups/client.py +2 -2
  314. reconcile/utils/internal_groups/models.py +8 -17
  315. reconcile/utils/jinja2/utils.py +6 -101
  316. reconcile/utils/jira_client.py +82 -63
  317. reconcile/utils/jjb_client.py +26 -13
  318. reconcile/utils/jobcontroller/controller.py +2 -2
  319. reconcile/utils/jobcontroller/models.py +17 -1
  320. reconcile/utils/json.py +43 -1
  321. reconcile/utils/membershipsources/app_interface_resolver.py +4 -2
  322. reconcile/utils/membershipsources/models.py +16 -23
  323. reconcile/utils/membershipsources/resolver.py +4 -2
  324. reconcile/utils/merge_request_manager/merge_request_manager.py +4 -4
  325. reconcile/utils/merge_request_manager/parser.py +6 -6
  326. reconcile/utils/metrics.py +5 -5
  327. reconcile/utils/models.py +304 -82
  328. reconcile/utils/mr/app_interface_reporter.py +2 -2
  329. reconcile/utils/mr/notificator.py +3 -3
  330. reconcile/utils/mr/update_access_report_base.py +3 -4
  331. reconcile/utils/mr/user_maintenance.py +3 -2
  332. reconcile/utils/oc.py +252 -201
  333. reconcile/utils/oc_filters.py +3 -3
  334. reconcile/utils/ocm/addons.py +0 -1
  335. reconcile/utils/ocm/base.py +17 -20
  336. reconcile/utils/ocm/cluster_groups.py +1 -1
  337. reconcile/utils/ocm/identity_providers.py +2 -2
  338. reconcile/utils/ocm/labels.py +1 -1
  339. reconcile/utils/ocm/products.py +8 -8
  340. reconcile/utils/ocm/search_filters.py +3 -6
  341. reconcile/utils/ocm/service_log.py +4 -6
  342. reconcile/utils/ocm/sre_capability_labels.py +20 -13
  343. reconcile/utils/openshift_resource.py +8 -3
  344. reconcile/utils/pagerduty_api.py +10 -7
  345. reconcile/utils/promotion_state.py +6 -11
  346. reconcile/utils/quay_api.py +74 -87
  347. reconcile/utils/raw_github_api.py +1 -1
  348. reconcile/utils/rhcsv2_certs.py +86 -23
  349. reconcile/utils/rosa/session.py +16 -0
  350. reconcile/utils/runtime/integration.py +2 -3
  351. reconcile/utils/runtime/runner.py +2 -2
  352. reconcile/utils/saasherder/interfaces.py +13 -20
  353. reconcile/utils/saasherder/models.py +23 -20
  354. reconcile/utils/saasherder/saasherder.py +50 -27
  355. reconcile/utils/slack_api.py +2 -2
  356. reconcile/utils/sloth.py +171 -2
  357. reconcile/utils/structs.py +1 -1
  358. reconcile/utils/terraform_client.py +5 -4
  359. reconcile/utils/terrascript_aws_client.py +274 -124
  360. reconcile/utils/unleash/server.py +2 -8
  361. reconcile/utils/vault.py +5 -12
  362. reconcile/utils/vcs.py +8 -8
  363. reconcile/vault_replication.py +107 -42
  364. reconcile/vpc_peerings_validator.py +13 -0
  365. tools/app_interface_reporter.py +4 -4
  366. tools/cli_commands/cost_report/cost_management_api.py +3 -3
  367. tools/cli_commands/cost_report/view.py +7 -6
  368. tools/cli_commands/erv2.py +1 -1
  369. tools/qontract_cli.py +28 -17
  370. tools/template_validation.py +3 -1
  371. {qontract_reconcile-0.10.2.dev361.dist-info → qontract_reconcile-0.10.2.dev474.dist-info}/entry_points.txt +0 -0
@@ -17,10 +17,8 @@ from jira.resources import CustomFieldOption as JiraCustomFieldOption
17
17
  from jira.resources import Resource
18
18
  from pydantic import BaseModel
19
19
 
20
- from reconcile.utils.secret_reader import SecretReader
21
-
22
20
  if TYPE_CHECKING:
23
- from collections.abc import Iterable, Mapping
21
+ from collections.abc import Iterable
24
22
 
25
23
 
26
24
  class JiraWatcherSettings(Protocol):
@@ -87,36 +85,18 @@ class IssueField(BaseModel):
87
85
  options: list[FieldOption | CustomFieldOption]
88
86
 
89
87
 
88
+ CREATE_ISSUES = "CREATE_ISSUES"
89
+ TRANSITION_ISSUES = "TRANSITION_ISSUES"
90
+ PERMISSIONS = [CREATE_ISSUES, TRANSITION_ISSUES]
91
+
92
+
90
93
  class JiraClient:
91
94
  """Wrapper around Jira client."""
92
95
 
93
96
  DEFAULT_CONNECT_TIMEOUT = 60
94
97
  DEFAULT_READ_TIMEOUT = 60
95
98
 
96
- def __init__(
97
- self,
98
- jira_board: Mapping[str, Any] | None = None,
99
- settings: Mapping | None = None,
100
- jira_api: JIRA | None = None,
101
- project: str | None = None,
102
- server: str | None = None,
103
- ):
104
- """
105
- Note: jira_board and settings is to be deprecated. Use JiraClient.create() instead.
106
- """
107
- if jira_api and jira_board:
108
- raise RuntimeError(
109
- "jira_board parameter is deprecated. Use JiraClient.create() instead."
110
- )
111
- if not (jira_api and project):
112
- # kept for backwards-compatibility
113
- if not jira_board:
114
- raise RuntimeError(
115
- "JiraClient needs jira_api and project or jira_board."
116
- )
117
- self._deprecated_init(jira_board=jira_board, settings=settings)
118
- return
119
-
99
+ def __init__(self, jira_api: JIRA, project: str, server: str):
120
100
  self.server = server
121
101
  self.project = project
122
102
  self.jira = jira_api
@@ -128,47 +108,31 @@ class JiraClient:
128
108
  self.project_issue_types = functools.cache(self._project_issue_types)
129
109
  self.project_issue_fields = functools.cache(self._project_issue_fields)
130
110
 
131
- def _deprecated_init(
132
- self, jira_board: Mapping[str, Any], settings: Mapping | None
133
- ) -> None:
134
- secret_reader = SecretReader(settings=settings)
135
- self.project = jira_board["name"]
136
- jira_server = jira_board["server"]
137
- self.server = jira_server["serverUrl"]
138
- token = jira_server["token"]
139
- token_auth = secret_reader.read(token)
140
- read_timeout = 60
141
- connect_timeout = 60
142
- if settings and settings["jiraWatcher"]:
143
- read_timeout = settings["jiraWatcher"]["readTimeout"]
144
- connect_timeout = settings["jiraWatcher"]["connectTimeout"]
145
- if not self.server:
146
- raise RuntimeError("JiraClient.server is not set.")
147
-
148
- self.jira = JIRA(
149
- self.server,
150
- token_auth=token_auth,
151
- timeout=(read_timeout, connect_timeout),
152
- logging=False,
153
- )
154
-
155
111
  @staticmethod
156
112
  def create(
157
113
  project_name: str,
158
114
  token: str,
115
+ email: str | None,
159
116
  server_url: str,
160
117
  jira_watcher_settings: JiraWatcherSettings | None = None,
161
118
  ) -> JiraClient:
119
+ """Create a Jira client for the given project."""
162
120
  read_timeout = JiraClient.DEFAULT_READ_TIMEOUT
163
121
  connect_timeout = JiraClient.DEFAULT_CONNECT_TIMEOUT
164
122
  if jira_watcher_settings:
165
123
  read_timeout = jira_watcher_settings.read_timeout
166
124
  connect_timeout = jira_watcher_settings.connect_timeout
125
+
126
+ # Jira Cloud uses email+API token for basic auth
127
+ # Jira Server/Data Center can use token auth (personal access token)
128
+ auth_params: dict[str, Any] = (
129
+ {"basic_auth": (email, token)} if email else {"token_auth": token}
130
+ )
167
131
  jira_api = JIRA(
168
132
  server=server_url,
169
- token_auth=token,
170
133
  timeout=(read_timeout, connect_timeout),
171
134
  logging=False,
135
+ **auth_params,
172
136
  )
173
137
  return JiraClient(
174
138
  jira_api=jira_api,
@@ -176,7 +140,13 @@ class JiraClient:
176
140
  server=server_url,
177
141
  )
178
142
 
143
+ @property
144
+ def is_cloud(self) -> bool:
145
+ """Return whether we are on a Cloud based Jira instance."""
146
+ return self.jira.deploymentType == "Cloud"
147
+
179
148
  def get_issues(self, fields: Iterable | None = None) -> list[Issue]:
149
+ """Return all issues for our project."""
180
150
  block_size = 100
181
151
  block_num = 0
182
152
 
@@ -227,20 +197,34 @@ class JiraClient:
227
197
  return issue
228
198
 
229
199
  def _my_permissions(self, project: str) -> dict[str, Any]:
200
+ """Return my permissions for the given project.
201
+
202
+ Don't use this function directly, use self.my_permissions which is cached."""
203
+ if self.is_cloud:
204
+ return self.jira.my_permissions(
205
+ projectKey=project, permissions=",".join(PERMISSIONS)
206
+ )["permissions"]
230
207
  return self.jira.my_permissions(projectKey=project)["permissions"]
231
208
 
232
209
  def can_i(self, permission: str) -> bool:
210
+ """Return whether I have the given permission in the project."""
233
211
  return bool(
234
212
  self.my_permissions(project=self.project)[permission]["havePermission"]
235
213
  )
236
214
 
237
215
  def can_create_issues(self) -> bool:
238
- return self.can_i("CREATE_ISSUES")
216
+ """Return whether I can create issues in the project."""
217
+ return self.can_i(CREATE_ISSUES)
239
218
 
240
219
  def can_transition_issues(self) -> bool:
241
- return self.can_i("TRANSITION_ISSUES")
220
+ """Return whether I can transition issues in the project."""
221
+ return self.can_i(TRANSITION_ISSUES)
242
222
 
243
223
  def _project_issue_types(self, project: str) -> list[IssueType]:
224
+ """Return all available issue types (e.g. Task, Bug) for the project.
225
+
226
+ Don't use this function directly, use self.project_issue_types which is cached.
227
+ """
244
228
  # Don't use self.project here, because of function.cache usage
245
229
  return [
246
230
  IssueType(id=t.id, name=t.name, statuses=[s.name for s in t.statuses])
@@ -248,6 +232,7 @@ class JiraClient:
248
232
  ]
249
233
 
250
234
  def get_issue_type(self, issue_type: str) -> IssueType | None:
235
+ """Return a issue type (e.g. Task) for the project if it exists."""
251
236
  for _issue_type in self.project_issue_types(self.project):
252
237
  if _issue_type.name == issue_type:
253
238
  return _issue_type
@@ -255,15 +240,23 @@ class JiraClient:
255
240
 
256
241
  @staticmethod
257
242
  def _get_allowed_issue_field_options(
258
- allowed_values: list[Resource],
243
+ allowed_values: list[Resource] | list[dict[str, str]],
259
244
  ) -> list[FieldOption | CustomFieldOption]:
260
- """Return a list of allowed values for a field."""
261
- return [
262
- CustomFieldOption(value=v.value)
263
- if isinstance(v, JiraCustomFieldOption)
264
- else FieldOption(name=v.name)
265
- for v in allowed_values
266
- ]
245
+ """Return a list of allowed values for a field. E.g. Minor, Major ... for Priority in a Task."""
246
+ items: list[FieldOption | CustomFieldOption] = []
247
+ for v in allowed_values:
248
+ match v:
249
+ case dict() if "value" in v:
250
+ items.append(CustomFieldOption(value=v["value"]))
251
+ case dict() if "name" in v:
252
+ items.append(FieldOption(name=v["name"]))
253
+ case JiraCustomFieldOption():
254
+ items.append(CustomFieldOption(value=v.value))
255
+ case Resource():
256
+ items.append(FieldOption(name=v.name))
257
+ case _:
258
+ logging.warning(f"Unknown allowed value type: {type(v)}")
259
+ return items
267
260
 
268
261
  def _project_issue_fields(
269
262
  self, project: str, issue_type_id: str
@@ -273,6 +266,27 @@ class JiraClient:
273
266
  This API endpoint needs createIssue project permissions.
274
267
  """
275
268
  # Don't use self.project here, because of function.cache usage
269
+ if self.is_cloud:
270
+ metadata = self.jira.createmeta(
271
+ projectKeys=self.project,
272
+ issuetypeIds=[issue_type_id],
273
+ expand="projects.issuetypes.fields",
274
+ )
275
+ if not metadata["projects"] or not metadata["projects"][0]["issuetypes"]:
276
+ return []
277
+ return [
278
+ IssueField(
279
+ name=field["name"],
280
+ id=field_id,
281
+ options=self._get_allowed_issue_field_options(
282
+ field.get("allowedValues", [])
283
+ ),
284
+ )
285
+ for field_id, field in metadata["projects"][0]["issuetypes"][0][
286
+ "fields"
287
+ ].items()
288
+ ]
289
+
276
290
  return [
277
291
  IssueField(
278
292
  name=field.name,
@@ -304,6 +318,10 @@ class JiraClient:
304
318
 
305
319
  def project_priority_scheme(self) -> list[str]:
306
320
  """Return a list of all priority IDs for the project."""
321
+ if self.is_cloud:
322
+ # Cloud does not have a way to retrieve project specific priority schemes
323
+ return []
324
+
307
325
  scheme = self.jira.project_priority_scheme(self.project)
308
326
  return scheme.optionIds
309
327
 
@@ -329,4 +347,5 @@ class JiraClient:
329
347
 
330
348
  @property
331
349
  def is_archived(self) -> bool:
332
- return self.jira.project(self.project).archived
350
+ """Return whether the project is archived."""
351
+ return getattr(self.jira.project(self.project), "archived", False)
@@ -9,6 +9,7 @@ import tempfile
9
9
  import xml.etree.ElementTree as ET
10
10
  from collections.abc import Iterable, Mapping
11
11
  from os import path
12
+ from pathlib import Path
12
13
  from subprocess import (
13
14
  PIPE,
14
15
  STDOUT,
@@ -17,10 +18,9 @@ from subprocess import (
17
18
  from typing import Any
18
19
 
19
20
  import yaml
20
- from jenkins_jobs.builder import JenkinsManager
21
21
  from jenkins_jobs.errors import JenkinsJobsException
22
- from jenkins_jobs.parser import YamlParser
23
- from jenkins_jobs.registry import ModuleRegistry
22
+ from jenkins_jobs.loader import load_files
23
+ from jenkins_jobs.roots import Roots
24
24
  from sretoolbox.utils import retry
25
25
 
26
26
  from reconcile.utils import throughput
@@ -33,6 +33,10 @@ from reconcile.utils.vcs import GITHUB_BASE_URL
33
33
  JJB_INI = "[jenkins]\nurl = https://JENKINS_URL"
34
34
 
35
35
 
36
+ class MissingJobUrlError(Exception):
37
+ pass
38
+
39
+
36
40
  class JJB:
37
41
  """Wrapper around Jenkins Jobs"""
38
42
 
@@ -292,13 +296,10 @@ class JJB:
292
296
 
293
297
  args = ["--conf", ini_path, "test", config_path]
294
298
  jjb = self.get_jjb(args)
295
- builder = JenkinsManager(jjb.jjb_config)
296
- registry = ModuleRegistry(jjb.jjb_config, builder.plugins_list)
297
- parser = YamlParser(jjb.jjb_config)
298
- parser.load_files(jjb.options.path)
299
- jobs, _ = parser.expandYaml(registry, jjb.options.names)
300
-
301
- return jobs
299
+ roots = Roots(jjb.jjb_config)
300
+ load_files(jjb.jjb_config, roots, [Path(config_path)])
301
+ job_view_data_list = roots.generate_jobs()
302
+ return [job.data for job in job_view_data_list]
302
303
 
303
304
  def get_job_webhooks_data(self) -> dict[str, list[dict[str, Any]]]:
304
305
  job_webhooks_data: dict[str, list[dict[str, Any]]] = {}
@@ -338,7 +339,7 @@ class JJB:
338
339
  job_name = job["name"]
339
340
  try:
340
341
  repos.add(self.get_repo_url(job))
341
- except KeyError:
342
+ except MissingJobUrlError:
342
343
  logging.debug(f"missing github url: {job_name}")
343
344
  return repos
344
345
 
@@ -358,7 +359,19 @@ class JJB:
358
359
 
359
360
  @staticmethod
360
361
  def get_repo_url(job: Mapping[str, Any]) -> str:
361
- repo_url_raw = job["properties"][0]["github"]["url"]
362
+ repo_url_raw = job.get("properties", [{}])[0].get("github", {}).get("url")
363
+
364
+ # we may be in a Github Branch Source type of job
365
+ if not repo_url_raw:
366
+ gh_org = job.get("scm", [{}])[0].get("github", {}).get("repo-owner")
367
+ gh_repo = job.get("scm", [{}])[0].get("github", {}).get("repo")
368
+ if gh_org and gh_repo:
369
+ repo_url_raw = f"https://github.com/{gh_org}/{gh_repo}/"
370
+ else:
371
+ raise MissingJobUrlError(
372
+ f"Cannot find job url for {job['display-name']}"
373
+ )
374
+
362
375
  return repo_url_raw.strip("/").replace(".git", "")
363
376
 
364
377
  @staticmethod
@@ -407,7 +420,7 @@ class JJB:
407
420
  try:
408
421
  if self.get_repo_url(job).lower() == repo_url.rstrip("/").lower():
409
422
  return job
410
- except KeyError:
423
+ except MissingJobUrlError:
411
424
  # something wrong here. ignore this job
412
425
  pass
413
426
  raise ValueError(f"job with {job_type=} and {repo_url=} not found")
@@ -3,7 +3,7 @@ import time
3
3
  from datetime import datetime
4
4
  from typing import Protocol, TextIO
5
5
 
6
- from kubernetes.client import ( # type: ignore[attr-defined]
6
+ from kubernetes.client import (
7
7
  ApiClient,
8
8
  V1Job,
9
9
  V1ObjectMeta,
@@ -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,50 @@
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
10
+ from pydantic.main import IncEx
3
11
 
4
12
  JSON_COMPACT_SEPARATORS = (",", ":")
5
13
 
6
14
 
15
+ def pydantic_encoder(obj: Any) -> Any:
16
+ if isinstance(obj, BaseModel):
17
+ return obj.model_dump()
18
+
19
+ if is_dataclass(obj):
20
+ return asdict(obj) # type: ignore
21
+
22
+ if isinstance(obj, (datetime, date)):
23
+ return obj.isoformat()
24
+
25
+ if isinstance(obj, Enum):
26
+ return obj.value
27
+
28
+ if isinstance(obj, Decimal):
29
+ return float(obj)
30
+
31
+ raise TypeError(
32
+ f"Object of type '{obj.__class__.__name__}' is not JSON serializable"
33
+ )
34
+
35
+
7
36
  def json_dumps(
8
37
  data: Any,
9
38
  *,
10
39
  compact: bool = False,
11
40
  indent: int | None = None,
12
41
  cls: type[json.JSONEncoder] | None = None,
42
+ defaults: Callable | None = None,
43
+ # BaseModel dump parameters
44
+ by_alias: bool = True,
45
+ exclude_none: bool = False,
46
+ exclude: IncEx | None = None,
47
+ mode: Literal["json", "python"] = "json",
13
48
  ) -> str:
14
49
  """
15
50
  Serialize `data` to a consistent JSON formatted `str` with dict keys sorted.
@@ -22,6 +57,12 @@ def json_dumps(
22
57
  Returns:
23
58
  A JSON formatted string.
24
59
  """
60
+ if isinstance(data, BaseModel):
61
+ data = data.model_dump(
62
+ mode=mode, by_alias=by_alias, exclude_none=exclude_none, exclude=exclude
63
+ )
64
+ if mode == "python":
65
+ defaults = pydantic_encoder
25
66
  separators = JSON_COMPACT_SEPARATORS if compact else None
26
67
  return json.dumps(
27
68
  data,
@@ -29,4 +70,5 @@ def json_dumps(
29
70
  separators=separators,
30
71
  sort_keys=True,
31
72
  cls=cls,
73
+ default=defaults,
32
74
  )
@@ -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 []:
@@ -1,7 +1,7 @@
1
1
  import logging
2
2
  from abc import abstractmethod
3
3
  from dataclasses import dataclass
4
- from typing import Any, Generic, TypeVar
4
+ from typing import Any, TypeVar
5
5
 
6
6
  from gitlab.v4.objects import ProjectMergeRequest
7
7
  from pydantic import BaseModel
@@ -17,12 +17,12 @@ T = TypeVar("T", bound=BaseModel)
17
17
 
18
18
 
19
19
  @dataclass
20
- class OpenMergeRequest(Generic[T]):
20
+ class OpenMergeRequest[T: BaseModel]:
21
21
  raw: ProjectMergeRequest
22
22
  mr_info: T
23
23
 
24
24
 
25
- class MergeRequestManagerBase(Generic[T]):
25
+ class MergeRequestManagerBase[T: BaseModel]:
26
26
  """ """
27
27
 
28
28
  def __init__(self, vcs: VCS, parser: Parser, mr_label: str):
@@ -42,7 +42,7 @@ class MergeRequestManagerBase(Generic[T]):
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
 
@@ -1,5 +1,5 @@
1
1
  import re
2
- from typing import Generic, TypeVar
2
+ from typing import TypeVar
3
3
 
4
4
  from pydantic import BaseModel
5
5
 
@@ -17,7 +17,7 @@ class ParserVersionError(Exception):
17
17
  T = TypeVar("T", bound=BaseModel)
18
18
 
19
19
 
20
- class Parser(Generic[T]):
20
+ class Parser[T: BaseModel]:
21
21
  """This class is only concerned with parsing an MR description rendered by the Renderer."""
22
22
 
23
23
  def __init__(
@@ -60,8 +60,8 @@ class Parser(Generic[T]):
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
  )