qontract-reconcile 0.10.2.dev310__py3-none-any.whl → 0.10.2.dev439__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.

Potentially problematic release.


This version of qontract-reconcile might be problematic. Click here for more details.

Files changed (400) hide show
  1. {qontract_reconcile-0.10.2.dev310.dist-info → qontract_reconcile-0.10.2.dev439.dist-info}/METADATA +13 -12
  2. {qontract_reconcile-0.10.2.dev310.dist-info → qontract_reconcile-0.10.2.dev439.dist-info}/RECORD +396 -391
  3. reconcile/acs_rbac.py +2 -2
  4. reconcile/aus/advanced_upgrade_service.py +18 -12
  5. reconcile/aus/base.py +134 -32
  6. reconcile/aus/cluster_version_data.py +15 -5
  7. reconcile/aus/models.py +3 -1
  8. reconcile/aus/ocm_addons_upgrade_scheduler_org.py +1 -0
  9. reconcile/aus/ocm_upgrade_scheduler.py +8 -1
  10. reconcile/aus/ocm_upgrade_scheduler_org.py +20 -5
  11. reconcile/aus/version_gates/sts_version_gate_handler.py +54 -1
  12. reconcile/automated_actions/config/integration.py +16 -4
  13. reconcile/aws_account_manager/integration.py +8 -8
  14. reconcile/aws_account_manager/reconciler.py +3 -3
  15. reconcile/aws_ami_cleanup/integration.py +8 -12
  16. reconcile/aws_ami_share.py +69 -62
  17. reconcile/aws_cloudwatch_log_retention/integration.py +155 -126
  18. reconcile/aws_ecr_image_pull_secrets.py +5 -5
  19. reconcile/aws_iam_keys.py +1 -0
  20. reconcile/aws_saml_idp/integration.py +12 -4
  21. reconcile/aws_saml_roles/integration.py +32 -25
  22. reconcile/aws_version_sync/integration.py +125 -84
  23. reconcile/change_owners/bundle.py +3 -3
  24. reconcile/change_owners/change_log_tracking.py +3 -2
  25. reconcile/change_owners/change_owners.py +1 -1
  26. reconcile/change_owners/diff.py +2 -4
  27. reconcile/checkpoint.py +12 -4
  28. reconcile/cli.py +111 -18
  29. reconcile/cluster_deployment_mapper.py +2 -3
  30. reconcile/dashdotdb_dora.py +5 -12
  31. reconcile/dashdotdb_slo.py +1 -1
  32. reconcile/database_access_manager.py +125 -121
  33. reconcile/deadmanssnitch.py +1 -5
  34. reconcile/dynatrace_token_provider/integration.py +1 -1
  35. reconcile/endpoints_discovery/integration.py +4 -1
  36. reconcile/endpoints_discovery/merge_request.py +1 -1
  37. reconcile/endpoints_discovery/merge_request_manager.py +9 -11
  38. reconcile/external_resources/factories.py +5 -12
  39. reconcile/external_resources/integration.py +1 -1
  40. reconcile/external_resources/manager.py +8 -5
  41. reconcile/external_resources/meta.py +0 -1
  42. reconcile/external_resources/metrics.py +1 -1
  43. reconcile/external_resources/model.py +20 -20
  44. reconcile/external_resources/reconciler.py +7 -4
  45. reconcile/external_resources/secrets_sync.py +10 -14
  46. reconcile/external_resources/state.py +26 -16
  47. reconcile/fleet_labeler/integration.py +1 -1
  48. reconcile/gabi_authorized_users.py +8 -5
  49. reconcile/gcp_image_mirror.py +2 -2
  50. reconcile/github_org.py +1 -1
  51. reconcile/github_owners.py +4 -0
  52. reconcile/gitlab_housekeeping.py +13 -15
  53. reconcile/gitlab_members.py +6 -12
  54. reconcile/gitlab_mr_sqs_consumer.py +2 -2
  55. reconcile/gitlab_owners.py +15 -11
  56. reconcile/gitlab_permissions.py +8 -12
  57. reconcile/glitchtip_project_alerts/integration.py +3 -1
  58. reconcile/gql_definitions/acs/acs_instances.py +10 -10
  59. reconcile/gql_definitions/acs/acs_policies.py +5 -5
  60. reconcile/gql_definitions/acs/acs_rbac.py +6 -6
  61. reconcile/gql_definitions/advanced_upgrade_service/aus_clusters.py +32 -32
  62. reconcile/gql_definitions/advanced_upgrade_service/aus_organization.py +26 -26
  63. reconcile/gql_definitions/app_interface_metrics_exporter/onboarding_status.py +6 -7
  64. reconcile/gql_definitions/app_sre_tekton_access_revalidation/roles.py +5 -5
  65. reconcile/gql_definitions/app_sre_tekton_access_revalidation/users.py +5 -5
  66. reconcile/gql_definitions/automated_actions/instance.py +51 -12
  67. reconcile/gql_definitions/aws_account_manager/aws_accounts.py +11 -11
  68. reconcile/gql_definitions/aws_ami_cleanup/aws_accounts.py +20 -10
  69. reconcile/gql_definitions/aws_cloudwatch_log_retention/aws_accounts.py +28 -68
  70. reconcile/gql_definitions/aws_saml_idp/aws_accounts.py +20 -10
  71. reconcile/gql_definitions/aws_saml_roles/aws_accounts.py +20 -10
  72. reconcile/gql_definitions/aws_saml_roles/roles.py +5 -5
  73. reconcile/gql_definitions/aws_version_sync/clusters.py +10 -10
  74. reconcile/gql_definitions/aws_version_sync/namespaces.py +5 -5
  75. reconcile/gql_definitions/change_owners/queries/change_types.py +5 -5
  76. reconcile/gql_definitions/change_owners/queries/self_service_roles.py +9 -9
  77. reconcile/gql_definitions/cluster_auth_rhidp/clusters.py +18 -18
  78. reconcile/gql_definitions/common/alerting_services_settings.py +9 -9
  79. reconcile/gql_definitions/common/app_code_component_repos.py +5 -5
  80. reconcile/gql_definitions/common/app_interface_custom_messages.py +5 -5
  81. reconcile/gql_definitions/common/app_interface_dms_settings.py +5 -5
  82. reconcile/gql_definitions/common/app_interface_repo_settings.py +5 -5
  83. reconcile/gql_definitions/common/app_interface_roles.py +120 -0
  84. reconcile/gql_definitions/common/app_interface_state_settings.py +10 -10
  85. reconcile/gql_definitions/common/app_interface_vault_settings.py +5 -5
  86. reconcile/gql_definitions/common/app_quay_repos_escalation_policies.py +5 -5
  87. reconcile/gql_definitions/common/apps.py +5 -5
  88. reconcile/gql_definitions/common/aws_vpc_requests.py +22 -9
  89. reconcile/gql_definitions/common/aws_vpcs.py +11 -11
  90. reconcile/gql_definitions/common/clusters.py +37 -35
  91. reconcile/gql_definitions/common/clusters_minimal.py +14 -14
  92. reconcile/gql_definitions/common/clusters_with_dms.py +6 -6
  93. reconcile/gql_definitions/common/clusters_with_peering.py +29 -30
  94. reconcile/gql_definitions/common/github_orgs.py +10 -10
  95. reconcile/gql_definitions/common/jira_settings.py +10 -10
  96. reconcile/gql_definitions/common/jiralert_settings.py +5 -5
  97. reconcile/gql_definitions/common/ldap_settings.py +5 -5
  98. reconcile/gql_definitions/common/namespaces.py +42 -44
  99. reconcile/gql_definitions/common/namespaces_minimal.py +15 -13
  100. reconcile/gql_definitions/common/ocm_env_telemeter.py +12 -12
  101. reconcile/gql_definitions/common/ocm_environments.py +19 -19
  102. reconcile/gql_definitions/common/pagerduty_instances.py +9 -9
  103. reconcile/gql_definitions/common/pgp_reencryption_settings.py +6 -6
  104. reconcile/gql_definitions/common/pipeline_providers.py +29 -29
  105. reconcile/gql_definitions/common/quay_instances.py +5 -5
  106. reconcile/gql_definitions/common/quay_orgs.py +5 -5
  107. reconcile/gql_definitions/common/reserved_networks.py +5 -5
  108. reconcile/gql_definitions/common/rhcs_provider_settings.py +5 -5
  109. reconcile/gql_definitions/common/saas_files.py +44 -44
  110. reconcile/gql_definitions/common/saas_target_namespaces.py +10 -10
  111. reconcile/gql_definitions/common/saasherder_settings.py +5 -5
  112. reconcile/gql_definitions/common/slack_workspaces.py +5 -5
  113. reconcile/gql_definitions/common/smtp_client_settings.py +19 -19
  114. reconcile/gql_definitions/common/state_aws_account.py +7 -8
  115. reconcile/gql_definitions/common/users.py +5 -5
  116. reconcile/gql_definitions/common/users_with_paths.py +5 -5
  117. reconcile/gql_definitions/cost_report/app_names.py +5 -5
  118. reconcile/gql_definitions/cost_report/cost_namespaces.py +5 -5
  119. reconcile/gql_definitions/cost_report/settings.py +9 -9
  120. reconcile/gql_definitions/dashdotdb_slo/slo_documents_query.py +43 -43
  121. reconcile/gql_definitions/dynatrace_token_provider/dynatrace_bootstrap_tokens.py +10 -10
  122. reconcile/gql_definitions/dynatrace_token_provider/token_specs.py +5 -5
  123. reconcile/gql_definitions/email_sender/apps.py +5 -5
  124. reconcile/gql_definitions/email_sender/emails.py +8 -8
  125. reconcile/gql_definitions/email_sender/users.py +6 -6
  126. reconcile/gql_definitions/endpoints_discovery/apps.py +10 -10
  127. reconcile/gql_definitions/external_resources/aws_accounts.py +9 -9
  128. reconcile/gql_definitions/external_resources/external_resources_modules.py +23 -23
  129. reconcile/gql_definitions/external_resources/external_resources_namespaces.py +494 -410
  130. reconcile/gql_definitions/external_resources/external_resources_settings.py +28 -26
  131. reconcile/gql_definitions/external_resources/fragments/external_resources_module_overrides.py +5 -5
  132. reconcile/gql_definitions/fleet_labeler/fleet_labels.py +40 -40
  133. reconcile/gql_definitions/fragments/aus_organization.py +5 -5
  134. reconcile/gql_definitions/fragments/aws_account_common.py +7 -5
  135. reconcile/gql_definitions/fragments/aws_account_managed.py +5 -5
  136. reconcile/gql_definitions/fragments/aws_account_sso.py +5 -5
  137. reconcile/gql_definitions/fragments/aws_infra_management_account.py +5 -5
  138. reconcile/gql_definitions/fragments/{aws_vpc_request_subnet.py → aws_organization.py} +12 -8
  139. reconcile/gql_definitions/fragments/aws_vpc.py +5 -5
  140. reconcile/gql_definitions/fragments/aws_vpc_request.py +12 -5
  141. reconcile/gql_definitions/fragments/container_image_mirror.py +5 -5
  142. reconcile/gql_definitions/fragments/deploy_resources.py +5 -5
  143. reconcile/gql_definitions/fragments/disable.py +5 -5
  144. reconcile/gql_definitions/fragments/email_service.py +5 -5
  145. reconcile/gql_definitions/fragments/email_user.py +5 -5
  146. reconcile/gql_definitions/fragments/jumphost_common_fields.py +5 -5
  147. reconcile/gql_definitions/fragments/membership_source.py +5 -5
  148. reconcile/gql_definitions/fragments/minimal_ocm_organization.py +5 -5
  149. reconcile/gql_definitions/fragments/oc_connection_cluster.py +5 -5
  150. reconcile/gql_definitions/fragments/ocm_environment.py +5 -5
  151. reconcile/gql_definitions/fragments/pipeline_provider_retention.py +5 -5
  152. reconcile/gql_definitions/fragments/prometheus_instance.py +5 -5
  153. reconcile/gql_definitions/fragments/resource_limits_requirements.py +5 -5
  154. reconcile/gql_definitions/fragments/resource_requests_requirements.py +5 -5
  155. reconcile/gql_definitions/fragments/resource_values.py +5 -5
  156. reconcile/gql_definitions/fragments/saas_slo_document.py +5 -5
  157. reconcile/gql_definitions/fragments/saas_target_namespace.py +5 -5
  158. reconcile/gql_definitions/fragments/serviceaccount_token.py +5 -5
  159. reconcile/gql_definitions/fragments/terraform_state.py +5 -5
  160. reconcile/gql_definitions/fragments/upgrade_policy.py +5 -5
  161. reconcile/gql_definitions/fragments/user.py +5 -5
  162. reconcile/gql_definitions/fragments/vault_secret.py +5 -5
  163. reconcile/gql_definitions/gcp/gcp_docker_repos.py +9 -9
  164. reconcile/gql_definitions/gcp/gcp_projects.py +9 -9
  165. reconcile/gql_definitions/gitlab_members/gitlab_instances.py +9 -9
  166. reconcile/gql_definitions/gitlab_members/permissions.py +9 -9
  167. reconcile/gql_definitions/glitchtip/glitchtip_instance.py +9 -9
  168. reconcile/gql_definitions/glitchtip/glitchtip_project.py +11 -11
  169. reconcile/gql_definitions/glitchtip_project_alerts/glitchtip_project.py +9 -9
  170. reconcile/gql_definitions/integrations/integrations.py +48 -51
  171. reconcile/gql_definitions/introspection.json +3510 -1865
  172. reconcile/gql_definitions/jenkins_configs/jenkins_configs.py +11 -11
  173. reconcile/gql_definitions/jenkins_configs/jenkins_instances.py +10 -10
  174. reconcile/gql_definitions/jira/jira_servers.py +5 -5
  175. reconcile/gql_definitions/jira_permissions_validator/jira_boards_for_permissions_validator.py +14 -10
  176. reconcile/gql_definitions/jumphosts/jumphosts.py +13 -13
  177. reconcile/gql_definitions/ldap_groups/roles.py +5 -5
  178. reconcile/gql_definitions/ldap_groups/settings.py +9 -9
  179. reconcile/gql_definitions/maintenance/maintenances.py +5 -5
  180. reconcile/gql_definitions/membershipsources/roles.py +5 -5
  181. reconcile/gql_definitions/ocm_labels/clusters.py +18 -19
  182. reconcile/gql_definitions/ocm_labels/organizations.py +5 -5
  183. reconcile/gql_definitions/openshift_cluster_bots/clusters.py +22 -22
  184. reconcile/gql_definitions/openshift_groups/managed_groups.py +5 -5
  185. reconcile/gql_definitions/openshift_groups/managed_roles.py +6 -6
  186. reconcile/gql_definitions/openshift_serviceaccount_tokens/tokens.py +10 -10
  187. reconcile/gql_definitions/quay_membership/quay_membership.py +6 -6
  188. reconcile/gql_definitions/rhcs/certs.py +33 -87
  189. reconcile/gql_definitions/rhcs/openshift_resource_rhcs_cert.py +43 -0
  190. reconcile/gql_definitions/rhidp/organizations.py +18 -18
  191. reconcile/gql_definitions/service_dependencies/jenkins_instance_fragment.py +5 -5
  192. reconcile/gql_definitions/service_dependencies/service_dependencies.py +8 -8
  193. reconcile/gql_definitions/sharding/aws_accounts.py +10 -10
  194. reconcile/gql_definitions/sharding/ocm_organization.py +8 -8
  195. reconcile/gql_definitions/skupper_network/site_controller_template.py +5 -5
  196. reconcile/gql_definitions/skupper_network/skupper_networks.py +10 -10
  197. reconcile/gql_definitions/slack_usergroups/clusters.py +5 -5
  198. reconcile/gql_definitions/slack_usergroups/permissions.py +9 -9
  199. reconcile/gql_definitions/slack_usergroups/users.py +5 -5
  200. reconcile/gql_definitions/slo_documents/slo_documents.py +5 -5
  201. reconcile/gql_definitions/status_board/status_board.py +6 -7
  202. reconcile/gql_definitions/statuspage/statuspages.py +9 -9
  203. reconcile/gql_definitions/templating/template_collection.py +5 -5
  204. reconcile/gql_definitions/templating/templates.py +5 -5
  205. reconcile/gql_definitions/terraform_cloudflare_dns/app_interface_cloudflare_dns_settings.py +6 -6
  206. reconcile/gql_definitions/terraform_cloudflare_dns/terraform_cloudflare_zones.py +11 -11
  207. reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_accounts.py +11 -11
  208. reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_resources.py +20 -25
  209. reconcile/gql_definitions/terraform_cloudflare_users/app_interface_setting_cloudflare_and_vault.py +6 -6
  210. reconcile/gql_definitions/terraform_cloudflare_users/terraform_cloudflare_roles.py +12 -12
  211. reconcile/gql_definitions/terraform_init/aws_accounts.py +23 -9
  212. reconcile/gql_definitions/terraform_repo/terraform_repo.py +9 -9
  213. reconcile/gql_definitions/terraform_resources/database_access_manager.py +5 -5
  214. reconcile/gql_definitions/terraform_resources/terraform_resources_namespaces.py +450 -402
  215. reconcile/gql_definitions/terraform_tgw_attachments/aws_accounts.py +23 -17
  216. reconcile/gql_definitions/unleash_feature_toggles/feature_toggles.py +9 -9
  217. reconcile/gql_definitions/vault_instances/vault_instances.py +61 -61
  218. reconcile/gql_definitions/vault_policies/vault_policies.py +11 -11
  219. reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator.py +8 -8
  220. reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator_peered_cluster_fragment.py +5 -5
  221. reconcile/integrations_manager.py +3 -3
  222. reconcile/jenkins_job_builder.py +1 -1
  223. reconcile/jenkins_worker_fleets.py +80 -11
  224. reconcile/jira_permissions_validator.py +237 -122
  225. reconcile/ldap_groups/integration.py +1 -1
  226. reconcile/ocm/types.py +35 -56
  227. reconcile/ocm_aws_infrastructure_access.py +1 -1
  228. reconcile/ocm_clusters.py +4 -4
  229. reconcile/ocm_labels/integration.py +3 -2
  230. reconcile/ocm_machine_pools.py +33 -27
  231. reconcile/openshift_base.py +122 -10
  232. reconcile/openshift_cluster_bots.py +5 -5
  233. reconcile/openshift_groups.py +5 -0
  234. reconcile/openshift_limitranges.py +1 -1
  235. reconcile/openshift_namespace_labels.py +1 -1
  236. reconcile/openshift_namespaces.py +97 -101
  237. reconcile/openshift_resources_base.py +10 -5
  238. reconcile/openshift_rhcs_certs.py +77 -40
  239. reconcile/openshift_rolebindings.py +230 -130
  240. reconcile/openshift_saas_deploy.py +6 -7
  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 +8 -7
  244. reconcile/openshift_tekton_resources.py +1 -1
  245. reconcile/openshift_upgrade_watcher.py +4 -4
  246. reconcile/openshift_users.py +5 -3
  247. reconcile/oum/labelset.py +5 -3
  248. reconcile/oum/models.py +1 -4
  249. reconcile/oum/providers.py +1 -1
  250. reconcile/prometheus_rules_tester/integration.py +4 -4
  251. reconcile/quay_mirror.py +1 -1
  252. reconcile/queries.py +131 -0
  253. reconcile/requests_sender.py +8 -3
  254. reconcile/resource_scraper.py +1 -5
  255. reconcile/rhidp/common.py +3 -5
  256. reconcile/rhidp/sso_client/base.py +19 -10
  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/sendgrid_teammates.py +20 -9
  260. reconcile/skupper_network/integration.py +2 -2
  261. reconcile/slack_usergroups.py +35 -14
  262. reconcile/sql_query.py +1 -0
  263. reconcile/status.py +2 -2
  264. reconcile/status_board.py +6 -6
  265. reconcile/statuspage/atlassian.py +7 -7
  266. reconcile/statuspage/integrations/maintenances.py +4 -3
  267. reconcile/statuspage/page.py +4 -9
  268. reconcile/statuspage/status.py +5 -8
  269. reconcile/templates/rosa-classic-cluster-creation.sh.j2 +5 -1
  270. reconcile/templates/rosa-hcp-cluster-creation.sh.j2 +4 -1
  271. reconcile/templating/lib/merge_request_manager.py +2 -2
  272. reconcile/templating/lib/rendering.py +3 -3
  273. reconcile/templating/renderer.py +12 -13
  274. reconcile/terraform_aws_route53.py +18 -8
  275. reconcile/terraform_cloudflare_dns.py +3 -3
  276. reconcile/terraform_cloudflare_resources.py +12 -13
  277. reconcile/terraform_cloudflare_users.py +3 -2
  278. reconcile/terraform_init/integration.py +187 -23
  279. reconcile/terraform_repo.py +16 -12
  280. reconcile/terraform_resources.py +18 -10
  281. reconcile/terraform_tgw_attachments.py +28 -20
  282. reconcile/terraform_users.py +27 -22
  283. reconcile/terraform_vpc_peerings.py +15 -3
  284. reconcile/terraform_vpc_resources/integration.py +23 -8
  285. reconcile/typed_queries/app_interface_roles.py +10 -0
  286. reconcile/typed_queries/aws_account_tags.py +41 -0
  287. reconcile/typed_queries/cost_report/app_names.py +1 -1
  288. reconcile/typed_queries/cost_report/cost_namespaces.py +2 -2
  289. reconcile/typed_queries/saas_files.py +13 -13
  290. reconcile/typed_queries/status_board.py +2 -2
  291. reconcile/unleash_feature_toggles/integration.py +4 -2
  292. reconcile/utils/acs/base.py +6 -3
  293. reconcile/utils/acs/policies.py +2 -2
  294. reconcile/utils/aggregated_list.py +4 -3
  295. reconcile/utils/aws_api.py +51 -20
  296. reconcile/utils/aws_api_typed/api.py +38 -9
  297. reconcile/utils/aws_api_typed/cloudformation.py +149 -0
  298. reconcile/utils/aws_api_typed/logs.py +73 -0
  299. reconcile/utils/aws_api_typed/organization.py +4 -2
  300. reconcile/utils/binary.py +7 -12
  301. reconcile/utils/datetime_util.py +67 -0
  302. reconcile/utils/deadmanssnitch_api.py +1 -1
  303. reconcile/utils/differ.py +2 -3
  304. reconcile/utils/early_exit_cache.py +11 -12
  305. reconcile/utils/expiration.py +7 -3
  306. reconcile/utils/external_resource_spec.py +24 -1
  307. reconcile/utils/filtering.py +1 -1
  308. reconcile/utils/gitlab_api.py +7 -5
  309. reconcile/utils/glitchtip/client.py +6 -2
  310. reconcile/utils/glitchtip/models.py +25 -28
  311. reconcile/utils/gpg.py +5 -3
  312. reconcile/utils/gql.py +4 -7
  313. reconcile/utils/helm.py +2 -1
  314. reconcile/utils/helpers.py +1 -1
  315. reconcile/utils/imap_client.py +1 -1
  316. reconcile/utils/instrumented_wrappers.py +1 -1
  317. reconcile/utils/internal_groups/client.py +2 -2
  318. reconcile/utils/internal_groups/models.py +8 -17
  319. reconcile/utils/jenkins_api.py +24 -1
  320. reconcile/utils/jinja2/utils.py +6 -8
  321. reconcile/utils/jira_client.py +82 -63
  322. reconcile/utils/jjb_client.py +78 -46
  323. reconcile/utils/jobcontroller/controller.py +2 -2
  324. reconcile/utils/jobcontroller/models.py +17 -1
  325. reconcile/utils/json.py +74 -0
  326. reconcile/utils/ldap_client.py +4 -3
  327. reconcile/utils/lean_terraform_client.py +3 -1
  328. reconcile/utils/membershipsources/app_interface_resolver.py +4 -2
  329. reconcile/utils/membershipsources/models.py +16 -23
  330. reconcile/utils/membershipsources/resolver.py +4 -2
  331. reconcile/utils/merge_request_manager/merge_request_manager.py +4 -4
  332. reconcile/utils/merge_request_manager/parser.py +6 -6
  333. reconcile/utils/metrics.py +5 -5
  334. reconcile/utils/models.py +304 -82
  335. reconcile/utils/mr/__init__.py +3 -1
  336. reconcile/utils/mr/app_interface_reporter.py +6 -3
  337. reconcile/utils/mr/aws_access.py +1 -1
  338. reconcile/utils/mr/base.py +7 -13
  339. reconcile/utils/mr/clusters_updates.py +4 -2
  340. reconcile/utils/mr/notificator.py +3 -3
  341. reconcile/utils/mr/ocm_upgrade_scheduler_org_updates.py +4 -1
  342. reconcile/utils/mr/promote_qontract.py +28 -12
  343. reconcile/utils/mr/update_access_report_base.py +3 -4
  344. reconcile/utils/mr/user_maintenance.py +7 -6
  345. reconcile/utils/oc.py +445 -336
  346. reconcile/utils/oc_filters.py +3 -3
  347. reconcile/utils/ocm/addons.py +0 -1
  348. reconcile/utils/ocm/base.py +18 -21
  349. reconcile/utils/ocm/cluster_groups.py +1 -1
  350. reconcile/utils/ocm/identity_providers.py +2 -2
  351. reconcile/utils/ocm/labels.py +1 -1
  352. reconcile/utils/ocm/ocm.py +81 -71
  353. reconcile/utils/ocm/products.py +9 -3
  354. reconcile/utils/ocm/search_filters.py +3 -6
  355. reconcile/utils/ocm/service_log.py +4 -6
  356. reconcile/utils/ocm/sre_capability_labels.py +20 -13
  357. reconcile/utils/ocm_base_client.py +4 -4
  358. reconcile/utils/openshift_resource.py +83 -52
  359. reconcile/utils/openssl.py +2 -2
  360. reconcile/utils/output.py +3 -2
  361. reconcile/utils/pagerduty_api.py +10 -7
  362. reconcile/utils/promotion_state.py +6 -11
  363. reconcile/utils/raw_github_api.py +11 -8
  364. reconcile/utils/repo_owners.py +21 -29
  365. reconcile/utils/rhcsv2_certs.py +138 -35
  366. reconcile/utils/rosa/session.py +16 -0
  367. reconcile/utils/runtime/integration.py +2 -3
  368. reconcile/utils/runtime/meta.py +2 -1
  369. reconcile/utils/runtime/runner.py +2 -2
  370. reconcile/utils/saasherder/interfaces.py +13 -20
  371. reconcile/utils/saasherder/models.py +25 -21
  372. reconcile/utils/saasherder/saasherder.py +60 -32
  373. reconcile/utils/secret_reader.py +6 -6
  374. reconcile/utils/sharding.py +1 -1
  375. reconcile/utils/slack_api.py +26 -4
  376. reconcile/utils/sloth.py +224 -0
  377. reconcile/utils/sqs_gateway.py +16 -11
  378. reconcile/utils/state.py +2 -1
  379. reconcile/utils/structs.py +1 -1
  380. reconcile/utils/terraform_client.py +29 -26
  381. reconcile/utils/terrascript_aws_client.py +200 -116
  382. reconcile/utils/three_way_diff_strategy.py +1 -1
  383. reconcile/utils/unleash/server.py +2 -8
  384. reconcile/utils/vault.py +44 -41
  385. reconcile/utils/vcs.py +8 -8
  386. reconcile/vault_replication.py +119 -58
  387. tools/app_interface_reporter.py +4 -4
  388. tools/cli_commands/cost_report/cost_management_api.py +3 -3
  389. tools/cli_commands/cost_report/view.py +7 -6
  390. tools/cli_commands/erv2.py +1 -1
  391. tools/cli_commands/gpg_encrypt.py +4 -1
  392. tools/cli_commands/systems_and_tools.py +5 -1
  393. tools/qontract_cli.py +36 -21
  394. tools/template_validation.py +3 -1
  395. reconcile/gql_definitions/ocm_oidc_idp/__init__.py +0 -0
  396. reconcile/gql_definitions/ocm_subscription_labels/__init__.py +0 -0
  397. reconcile/jenkins/__init__.py +0 -0
  398. reconcile/jenkins/types.py +0 -77
  399. {qontract_reconcile-0.10.2.dev310.dist-info → qontract_reconcile-0.10.2.dev439.dist-info}/WHEEL +0 -0
  400. {qontract_reconcile-0.10.2.dev310.dist-info → qontract_reconcile-0.10.2.dev439.dist-info}/entry_points.txt +0 -0
@@ -1,9 +1,13 @@
1
1
  import logging
2
2
  import os
3
3
  import pathlib
4
+ from collections.abc import Iterable, Mapping
4
5
 
5
6
  from ruamel import yaml
6
7
 
8
+ from reconcile.utils.github_api import GithubRepositoryApi
9
+ from reconcile.utils.gitlab_api import GitLabApi
10
+
7
11
  _LOG = logging.getLogger(__name__)
8
12
 
9
13
 
@@ -12,38 +16,24 @@ class RepoOwners:
12
16
  Abstracts the owners of a repository with per-path granularity.
13
17
  """
14
18
 
15
- def __init__(self, git_cli, ref="master", recursive=True):
19
+ def __init__(
20
+ self,
21
+ git_cli: GitLabApi | GithubRepositoryApi,
22
+ ref: str = "master",
23
+ recursive: bool = True,
24
+ ) -> None:
16
25
  self._git_cli = git_cli
17
26
  self._ref = ref
18
- self._owners_map = None
27
+ self._owners_map: dict[str, dict[str, set[str]]] | None = None
19
28
  self._recursive = recursive
20
29
 
21
30
  @property
22
- def owners_map(self):
31
+ def owners_map(self) -> dict[str, dict[str, set[str]]]:
23
32
  if self._owners_map is None:
24
33
  self._owners_map = self._get_owners_map()
25
34
  return self._owners_map
26
35
 
27
- def get_owners(self):
28
- """
29
- Gets all the owners of the repository.
30
-
31
- :return: the repository owners
32
- :rtype: dict
33
- """
34
- repo_owners = {"approvers": set(), "reviewers": set()}
35
-
36
- if "." in self.owners_map:
37
- repo_owners["approvers"].update(self.owners_map["."]["approvers"])
38
- repo_owners["reviewers"].update(self.owners_map["."]["reviewers"])
39
-
40
- for owners in self.owners_map.values():
41
- repo_owners["approvers"].update(owners["approvers"])
42
- repo_owners["reviewers"].update(owners["reviewers"])
43
-
44
- return repo_owners
45
-
46
- def get_root_owners(self):
36
+ def get_root_owners(self) -> dict[str, list[str]]:
47
37
  """
48
38
  Gets all the owners defined in the repository root.
49
39
 
@@ -56,7 +46,7 @@ class RepoOwners:
56
46
 
57
47
  return {"approvers": [], "reviewers": []}
58
48
 
59
- def get_path_owners(self, path):
49
+ def get_path_owners(self, path: str) -> dict[str, list[str]]:
60
50
  """
61
51
  Gets all the owners of a given path, no matter in which
62
52
  level of the filesystem tree the owner was specified.
@@ -67,7 +57,7 @@ class RepoOwners:
67
57
  :return: the path owners
68
58
  :rtype: dict
69
59
  """
70
- path_owners = {"approvers": set(), "reviewers": set()}
60
+ path_owners: dict[str, set[str]] = {"approvers": set(), "reviewers": set()}
71
61
 
72
62
  if "." in self.owners_map:
73
63
  path_owners["approvers"].update(self.owners_map["."]["approvers"])
@@ -80,7 +70,7 @@ class RepoOwners:
80
70
 
81
71
  return self._set_to_sorted_list(path_owners)
82
72
 
83
- def get_path_closest_owners(self, path):
73
+ def get_path_closest_owners(self, path: str) -> dict[str, list[str]]:
84
74
  """
85
75
  Gets all closest owners of a given path, no matter in which
86
76
  level of the filesystem tree the owner was specified.
@@ -108,7 +98,7 @@ class RepoOwners:
108
98
 
109
99
  return {"approvers": [], "reviewers": []}
110
100
 
111
- def _get_owners_map(self):
101
+ def _get_owners_map(self) -> dict[str, dict[str, set[str]]]:
112
102
  """
113
103
  Maps all the OWNERS files content to their respective
114
104
  owned directory.
@@ -173,7 +163,7 @@ class RepoOwners:
173
163
  }
174
164
  return owners_map
175
165
 
176
- def _get_aliases(self):
166
+ def _get_aliases(self) -> dict[str, list[str]] | None:
177
167
  """
178
168
  Retrieves the approvers aliases from the OWNERS_ALIASES file.
179
169
 
@@ -191,7 +181,9 @@ class RepoOwners:
191
181
  return aliases["aliases"]
192
182
 
193
183
  @staticmethod
194
- def _set_to_sorted_list(owners):
184
+ def _set_to_sorted_list(
185
+ owners: Mapping[str, Iterable[str]],
186
+ ) -> dict[str, list[str]]:
195
187
  approvers = owners["approvers"]
196
188
  sorted_approvers = sorted(approvers) if approvers else []
197
189
 
@@ -1,25 +1,146 @@
1
+ import base64
1
2
  import re
2
- from datetime import UTC, datetime
3
+ from datetime import UTC
4
+ from enum import StrEnum
3
5
 
4
6
  import requests
5
7
  from cryptography import x509
6
8
  from cryptography.hazmat.primitives import hashes, serialization
7
9
  from cryptography.hazmat.primitives.asymmetric import rsa
10
+ from cryptography.hazmat.primitives.serialization import pkcs12
8
11
  from cryptography.x509.oid import NameOID
9
12
  from pydantic import BaseModel, Field
10
13
 
11
14
 
12
- class RhcsV2Cert(BaseModel):
15
+ class CertificateFormat(StrEnum):
16
+ PEM = "PEM"
17
+ PKCS12 = "PKCS12"
18
+
19
+
20
+ class RhcsV2CertPem(BaseModel, validate_by_name=True, validate_by_alias=True):
13
21
  certificate: str = Field(alias="tls.crt")
14
22
  private_key: str = Field(alias="tls.key")
15
23
  ca_cert: str = Field(alias="ca.crt")
16
24
  expiration_timestamp: int
17
25
 
18
- class Config:
19
- allow_population_by_field_name = True
26
+
27
+ class RhcsV2CertPkcs12(BaseModel, validate_by_name=True, validate_by_alias=True):
28
+ pkcs12_keystore: str = Field(alias="keystore.pkcs12.b64")
29
+ pkcs12_truststore: str = Field(alias="truststore.pkcs12.b64")
30
+ expiration_timestamp: int
31
+
32
+
33
+ def extract_cert(text: str) -> re.Match:
34
+ # The CA webform returns an HTML page with inline JS that builds an array of “outputList”
35
+ # objects. Each object looks roughly like:
36
+ # outputList = new Object;
37
+ # outputList.outputId = "pretty_cert";
38
+ # outputList.outputSyntax = "pretty_print";
39
+ # outputList.outputVal = " Certificate:\n ... Not After: ...";
40
+ # outputListSet[0] = outputList;
41
+ #
42
+ # outputList = new Object;
43
+ # outputList.outputId = "b64_cert";
44
+ # outputList.outputSyntax = "pretty_print";
45
+ # outputList.outputVal = "-----BEGIN CERTIFICATE-----\n...base64...\n-----END CERTIFICATE-----\n";
46
+ # outputListSet[1] = outputList;
47
+ #
48
+ # We must extract the PEM from the *b64_cert* block (the pretty_cert block contains prose
49
+ # and formatting and is not reliable for parsing).
50
+ #
51
+ # The regex below:
52
+ # - Anchors on `outputId = "b64_cert"` so we only consider the base64 block.
53
+ # - Tolerates arbitrary whitespace around `=` and `.` (services sometimes reformat JS).
54
+ # - Jumps non-greedily over whatever properties sit between outputId and outputVal.
55
+ # - Captures only the PEM body (BEGIN…END), excluding any trailing newline/escape junk.
56
+ # - Accepts multiple terminators after the closing quote: literal "\\r\\n", literal "\\n",
57
+ # or a real newline (`\r?\n`), followed by optional whitespace and the semicolon.
58
+ # - Uses DOTALL so `.` spans line breaks inside the JS/HTML blob.
59
+
60
+ cert_pem = re.search(
61
+ r'outputList\s*\.\s*outputId\s*=\s*"b64_cert".*?'
62
+ r'outputList\s*\.\s*outputVal\s*=\s*"'
63
+ r"(-----BEGIN CERTIFICATE-----.*?-----END CERTIFICATE-----)"
64
+ r"(?:\\r\\n|\\n|\r?\n)?\s*",
65
+ text,
66
+ re.DOTALL,
67
+ )
68
+ if not cert_pem:
69
+ raise ValueError("Could not extract certificate PEM from response")
70
+ return cert_pem
71
+
72
+
73
+ def get_cert_expiry_timestamp(js_escaped_pem: str) -> int:
74
+ """Extract certificate expiry timestamp from JavaScript-escaped PEM."""
75
+
76
+ # Convert JavaScript-escaped PEM to proper format: .encode() is needed because
77
+ # unicode_escape decoder only works on bytes, then decode JS escape sequences
78
+ pem_raw = js_escaped_pem.encode().decode("unicode_escape").replace("\\/", "/")
79
+ cert = x509.load_pem_x509_certificate(pem_raw.encode())
80
+ dt_expiry = cert.not_valid_after.replace(tzinfo=UTC)
81
+ return int(dt_expiry.timestamp())
82
+
83
+
84
+ def _format_pem(
85
+ private_key: rsa.RSAPrivateKey,
86
+ cert_pem: str,
87
+ ca_pem: str,
88
+ cert_expiry_timestamp: int,
89
+ ) -> RhcsV2CertPem:
90
+ """Generate RhcsV2Cert with PEM components."""
91
+ private_key_pem = private_key.private_bytes(
92
+ encoding=serialization.Encoding.PEM,
93
+ format=serialization.PrivateFormat.TraditionalOpenSSL,
94
+ encryption_algorithm=serialization.NoEncryption(),
95
+ ).decode()
96
+ return RhcsV2CertPem(
97
+ private_key=private_key_pem,
98
+ certificate=cert_pem.encode().decode("unicode_escape").replace("\\/", "/"),
99
+ ca_cert=ca_pem,
100
+ expiration_timestamp=cert_expiry_timestamp,
101
+ )
102
+
103
+
104
+ def _format_pkcs12(
105
+ private_key: rsa.RSAPrivateKey,
106
+ cert_pem: str,
107
+ ca_pem: str,
108
+ uid: str,
109
+ pwd: str,
110
+ cert_expiry_timestamp: int,
111
+ ) -> RhcsV2CertPkcs12:
112
+ """Generate PKCS#12 keystore and truststore components, returns base64-encoded strings."""
113
+ clean_cert_pem = cert_pem.encode().decode("unicode_escape").replace("\\/", "/")
114
+ cert_obj = x509.load_pem_x509_certificate(clean_cert_pem.encode())
115
+ ca_obj = x509.load_pem_x509_certificate(ca_pem.encode())
116
+ keystore_p12 = pkcs12.serialize_key_and_certificates(
117
+ name=uid.encode("utf-8"),
118
+ key=private_key,
119
+ cert=cert_obj,
120
+ cas=[ca_obj],
121
+ encryption_algorithm=serialization.BestAvailableEncryption(pwd.encode("utf-8")),
122
+ )
123
+ truststore_p12 = pkcs12.serialize_key_and_certificates(
124
+ name=b"ca-trust",
125
+ key=None,
126
+ cert=None,
127
+ cas=[ca_obj],
128
+ encryption_algorithm=serialization.NoEncryption(),
129
+ )
130
+ return RhcsV2CertPkcs12(
131
+ pkcs12_keystore=base64.b64encode(keystore_p12).decode("utf-8"),
132
+ pkcs12_truststore=base64.b64encode(truststore_p12).decode("utf-8"),
133
+ expiration_timestamp=cert_expiry_timestamp,
134
+ )
20
135
 
21
136
 
22
- def generate_cert(issuer_url: str, uid: str, pwd: str, ca_url: str) -> RhcsV2Cert:
137
+ def generate_cert(
138
+ issuer_url: str,
139
+ uid: str,
140
+ pwd: str,
141
+ ca_url: str,
142
+ cert_format: CertificateFormat = CertificateFormat.PEM,
143
+ ) -> RhcsV2CertPem | RhcsV2CertPkcs12:
23
144
  private_key = rsa.generate_private_key(65537, 4096)
24
145
  csr = (
25
146
  x509.CertificateSigningRequestBuilder()
@@ -30,6 +151,7 @@ def generate_cert(issuer_url: str, uid: str, pwd: str, ca_url: str) -> RhcsV2Cer
30
151
  )
31
152
  .sign(private_key, hashes.SHA256())
32
153
  )
154
+
33
155
  data = {
34
156
  "uid": uid,
35
157
  "pwd": pwd,
@@ -39,38 +161,19 @@ def generate_cert(issuer_url: str, uid: str, pwd: str, ca_url: str) -> RhcsV2Cer
39
161
  "renewal": "false",
40
162
  "xmlOutput": "false",
41
163
  }
42
- response = requests.post(issuer_url, data=data)
164
+ response = requests.post(issuer_url, data=data, timeout=30)
43
165
  response.raise_for_status()
166
+ cert_pem = extract_cert(response.text).group(1)
167
+ cert_expiry_timestamp = get_cert_expiry_timestamp(cert_pem)
44
168
 
45
- cert_pem = re.search(
46
- r'outputList\.outputVal="(-----BEGIN CERTIFICATE-----.*?-----END CERTIFICATE-----(?:\\n|\r?\n)?)";',
47
- response.text,
48
- re.DOTALL,
49
- )
50
- if not cert_pem:
51
- raise ValueError("Could not extract certificate PEM from response")
52
- cert_expiry = re.search(r"Not\s+After:\s+(.*?UTC)(?:\\n|\r?\n)?", response.text)
53
- if not cert_expiry:
54
- raise ValueError("Could not extract expiration date from response")
55
- # Weekday, Month Day, Year HH:MM:SS PM/AM Timezone
56
- dt_expiry = datetime.strptime(cert_expiry.group(1), "%A, %B %d, %Y %I:%M:%S %p %Z")
57
- dt_expiry = dt_expiry.replace(tzinfo=UTC)
58
- private_key_pem = private_key.private_bytes(
59
- encoding=serialization.Encoding.PEM,
60
- format=serialization.PrivateFormat.TraditionalOpenSSL,
61
- encryption_algorithm=serialization.NoEncryption(),
62
- ).decode()
63
-
64
- response = requests.get(ca_url)
169
+ response = requests.get(ca_url, timeout=30)
65
170
  response.raise_for_status()
66
171
  ca_pem = response.text
67
172
 
68
- return RhcsV2Cert(
69
- private_key=private_key_pem,
70
- certificate=cert_pem.group(1)
71
- .encode()
72
- .decode("unicode_escape")
73
- .replace("\\/", "/"),
74
- ca_cert=ca_pem,
75
- expiration_timestamp=int(dt_expiry.timestamp()),
76
- )
173
+ match cert_format:
174
+ case CertificateFormat.PKCS12:
175
+ return _format_pkcs12(
176
+ private_key, cert_pem, ca_pem, uid, pwd, cert_expiry_timestamp
177
+ )
178
+ case CertificateFormat.PEM:
179
+ return _format_pem(private_key, cert_pem, ca_pem, cert_expiry_timestamp)
@@ -178,6 +178,22 @@ class RosaSession:
178
178
  )
179
179
  result.write_logs_to_logger(logging.info)
180
180
 
181
+ def upgrade_rosa_roles(
182
+ self,
183
+ cluster_name: str,
184
+ upgrade_version: str,
185
+ policy_version: str,
186
+ dry_run: bool,
187
+ ) -> None:
188
+ logging.info(
189
+ f"Upgrade roles in AWS account {self.aws_account_id} to {upgrade_version}"
190
+ )
191
+ if not dry_run:
192
+ result = self.cli_execute(
193
+ f"rosa upgrade roles -c {cluster_name} --cluster-version {upgrade_version} --policy-version {policy_version} -y -m=auto"
194
+ )
195
+ result.write_logs_to_logger(logging.info)
196
+
181
197
 
182
198
  def generate_rosa_creation_script(
183
199
  cluster_name: str, cluster: OCMSpec, dry_run: bool
@@ -7,7 +7,6 @@ from dataclasses import dataclass
7
7
  from types import ModuleType
8
8
  from typing import (
9
9
  Any,
10
- Generic,
11
10
  Optional,
12
11
  TypeVar,
13
12
  )
@@ -120,7 +119,7 @@ class PydanticRunParams(RunParams, BaseModel):
120
119
  def copy_and_update(
121
120
  self: PydanticRunParamsSelfTypeVar, update: dict[str, Any]
122
121
  ) -> PydanticRunParamsSelfTypeVar:
123
- return self.copy(update=update)
122
+ return self.model_copy(update=update)
124
123
 
125
124
  def get(self, field: str) -> Any:
126
125
  return getattr(self, field)
@@ -144,7 +143,7 @@ IntegrationClassTypeVar = TypeVar(
144
143
  )
145
144
 
146
145
 
147
- class QontractReconcileIntegration(ABC, Generic[RunParamsTypeVar]):
146
+ class QontractReconcileIntegration[RunParamsTypeVar: RunParams](ABC):
148
147
  """
149
148
  The base class for all integrations. It defines the basic interface to interact
150
149
  with an integration and offers hook methods that allow the integration to opt
@@ -1,5 +1,6 @@
1
1
  import dataclasses
2
2
  from dataclasses import dataclass
3
+ from typing import Any
3
4
 
4
5
 
5
6
  @dataclass
@@ -8,5 +9,5 @@ class IntegrationMeta:
8
9
  args: list[str]
9
10
  short_help: str | None
10
11
 
11
- def to_dict(self):
12
+ def to_dict(self) -> dict[str, Any]:
12
13
  return dataclasses.asdict(self)
@@ -156,7 +156,7 @@ def run_integration_cfg(run_cfg: IntegrationRunConfiguration) -> None:
156
156
  _integration_wet_run(run_cfg.integration)
157
157
 
158
158
 
159
- def _integration_wet_run(
159
+ def _integration_wet_run[RunParamsTypeVar: RunParams](
160
160
  integration: QontractReconcileIntegration[RunParamsTypeVar],
161
161
  ) -> None:
162
162
  """
@@ -165,7 +165,7 @@ def _integration_wet_run(
165
165
  integration.run(False)
166
166
 
167
167
 
168
- def _integration_dry_run(
168
+ def _integration_dry_run[RunParamsTypeVar: RunParams](
169
169
  integration: QontractReconcileIntegration[RunParamsTypeVar],
170
170
  desired_state_diff: DesiredStateDiff | None,
171
171
  ) -> None:
@@ -1,7 +1,7 @@
1
1
  # ruff: noqa: N801
2
2
  from __future__ import annotations
3
3
 
4
- from collections.abc import Mapping, Sequence, Set
4
+ from collections.abc import Sequence
5
5
  from typing import (
6
6
  TYPE_CHECKING,
7
7
  Any,
@@ -12,6 +12,8 @@ from typing import (
12
12
  from reconcile.utils import oc_connection_parameters
13
13
 
14
14
  if TYPE_CHECKING:
15
+ from pydantic.main import IncEx
16
+
15
17
  from reconcile.gql_definitions.fragments.saas_slo_document import SLODocument
16
18
  from reconcile.utils.secret_reader import HasSecret
17
19
 
@@ -27,18 +29,12 @@ class SaasFileSecretParameters(Protocol):
27
29
  @property
28
30
  def secret(self) -> HasSecret: ...
29
31
 
30
- def dict(
31
- self,
32
- *,
33
- by_alias: bool = False,
34
- include: AbstractSetIntStr | MappingIntStrAny | None = None,
32
+ def model_dump(
33
+ self, *, by_alias: bool = False, include: IncEx | None = None
35
34
  ) -> dict[str, Any]: ...
36
35
 
37
36
 
38
37
  SaasSecretParameters = Sequence[SaasFileSecretParameters] | None
39
- # Taken from pydantic.typing
40
- AbstractSetIntStr = Set[int | str]
41
- MappingIntStrAny = Mapping[int | str, Any]
42
38
 
43
39
 
44
40
  @runtime_checkable
@@ -212,11 +208,8 @@ class SaasResourceTemplateTargetNamespace(Protocol):
212
208
  @property
213
209
  def cluster(self) -> oc_connection_parameters.Cluster: ...
214
210
 
215
- def dict(
216
- self,
217
- *,
218
- by_alias: bool = False,
219
- include: AbstractSetIntStr | MappingIntStrAny | None = None,
211
+ def model_dump(
212
+ self, *, by_alias: bool = False, include: IncEx | None = None
220
213
  ) -> dict[str, Any]: ...
221
214
 
222
215
 
@@ -249,7 +242,7 @@ class SaasResourceTemplateTargetPromotion(Protocol):
249
242
  @property
250
243
  def promotion_data(self) -> Sequence[SaasPromotionData] | None: ...
251
244
 
252
- def dict(self, *, by_alias: bool = False) -> dict[str, Any]: ...
245
+ def model_dump(self, *, by_alias: bool = False) -> dict[str, Any]: ...
253
246
 
254
247
 
255
248
  class Channel(Protocol):
@@ -274,7 +267,7 @@ class SaasPromotion(Protocol):
274
267
  @property
275
268
  def subscribe(self) -> list[Channel] | None: ...
276
269
 
277
- def dict(self, *, by_alias: bool = False) -> dict[str, Any]: ...
270
+ def model_dump(self, *, by_alias: bool = False) -> dict[str, Any]: ...
278
271
 
279
272
 
280
273
  class SaasResourceTemplateTarget_SaasSecretParameters(Protocol):
@@ -295,7 +288,7 @@ class SaasResourceTemplateTargetUpstream(Protocol):
295
288
  @property
296
289
  def instance(self) -> SaasJenkinsInstance: ...
297
290
 
298
- def dict(self, *, by_alias: bool = False) -> dict[str, Any]: ...
291
+ def model_dump(self, *, by_alias: bool = False) -> dict[str, Any]: ...
299
292
 
300
293
 
301
294
  class SaasQuayInstance(Protocol):
@@ -315,7 +308,7 @@ class SaasResourceTemplateTargetImage(Protocol):
315
308
  @property
316
309
  def org(self) -> SaasQuayOrg: ...
317
310
 
318
- def dict(self, *, by_alias: bool = False) -> dict[str, Any]: ...
311
+ def model_dump(self, *, by_alias: bool = False) -> dict[str, Any]: ...
319
312
 
320
313
 
321
314
  class SaasResourceTemplateTarget(HasParameters, HasSecretParameters, Protocol):
@@ -344,7 +337,7 @@ class SaasResourceTemplateTarget(HasParameters, HasSecretParameters, Protocol):
344
337
  self, parent_saas_file_name: str, parent_resource_template_name: str
345
338
  ) -> str: ...
346
339
 
347
- def dict(self, *, by_alias: bool = False) -> dict[str, Any]: ...
340
+ def model_dump(self, *, by_alias: bool = False) -> dict[str, Any]: ...
348
341
 
349
342
 
350
343
  class SaasResourceTemplate(HasParameters, HasSecretParameters, Protocol):
@@ -381,7 +374,7 @@ class ManagedResourceName(Protocol):
381
374
  resource: str
382
375
  resource_names: list[str]
383
376
 
384
- def dict(self) -> dict[str, str]: ...
377
+ def model_dump(self) -> dict[str, str]: ...
385
378
 
386
379
 
387
380
  class SaasFile(HasParameters, HasSecretParameters, Protocol):
@@ -7,15 +7,13 @@ from enum import Enum
7
7
  from typing import Any, NotRequired, TypedDict
8
8
 
9
9
  from github import Github
10
- from pydantic import (
11
- BaseModel,
12
- Field,
13
- )
10
+ from pydantic import BaseModel, Field, model_validator
14
11
 
15
12
  from reconcile.gql_definitions.fragments.saas_slo_document import (
16
13
  SLODocument,
17
14
  )
18
15
  from reconcile.utils.jenkins_api import JobBuildState
16
+ from reconcile.utils.json import json_dumps
19
17
  from reconcile.utils.oc_connection_parameters import Cluster
20
18
  from reconcile.utils.saasherder.interfaces import (
21
19
  HasParameters,
@@ -206,7 +204,12 @@ TriggerSpecUnion = (
206
204
  )
207
205
 
208
206
 
209
- class Namespace(BaseModel):
207
+ class Namespace(
208
+ BaseModel,
209
+ validate_by_name=True,
210
+ validate_by_alias=True,
211
+ arbitrary_types_allowed=True,
212
+ ):
210
213
  name: str
211
214
  environment: SaasEnvironment
212
215
  app: SaasApp
@@ -216,29 +219,19 @@ class Namespace(BaseModel):
216
219
  ..., alias="managedResourceNames"
217
220
  )
218
221
 
219
- class Config:
220
- arbitrary_types_allowed = True
221
- allow_population_by_field_name = True
222
-
223
222
 
224
- class PromotionChannelData(BaseModel):
223
+ class PromotionChannelData(BaseModel, validate_by_name=True, validate_by_alias=True):
225
224
  q_type: str = Field(..., alias="type")
226
225
 
227
- class Config:
228
- allow_population_by_field_name = True
229
226
 
230
-
231
- class ParentSaasPromotion(BaseModel):
227
+ class ParentSaasPromotion(BaseModel, validate_by_name=True, validate_by_alias=True):
232
228
  q_type: str = Field(..., alias="type")
233
- parent_saas: str | None
234
- target_config_hash: str | None
235
-
236
- class Config:
237
- allow_population_by_field_name = True
229
+ parent_saas: str | None = None
230
+ target_config_hash: str | None = None
238
231
 
239
232
 
240
233
  class PromotionData(BaseModel):
241
- channel: str | None
234
+ channel: str | None = None
242
235
  data: list[ParentSaasPromotion | PromotionChannelData] | None = None
243
236
 
244
237
 
@@ -263,6 +256,17 @@ class Promotion(BaseModel):
263
256
  saas_file_paths: list[str] | None = None
264
257
  target_paths: list[str] | None = None
265
258
 
259
+ @model_validator(mode="before")
260
+ @classmethod
261
+ def handle_gql_classes(cls, data: Any) -> Any:
262
+ if data.get("promotion_data"):
263
+ data["promotion_data"] = [
264
+ # convert a GQL class to a dict
265
+ item.model_dump() if hasattr(item, "model_dump") else item
266
+ for item in data["promotion_data"]
267
+ ]
268
+ return data
269
+
266
270
 
267
271
  @dataclass
268
272
  class ImageAuth:
@@ -422,7 +426,7 @@ class TargetSpec:
422
426
  elif v is False:
423
427
  parameters[k] = "false"
424
428
  elif any(isinstance(v, t) for t in [dict, list, tuple]):
425
- parameters[k] = json.dumps(v)
429
+ parameters[k] = json_dumps(v)
426
430
  return parameters
427
431
 
428
432
  def _collect_secret_parameters(