qontract-reconcile 0.10.2.dev361__py3-none-any.whl → 0.10.2.dev430__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 (351) hide show
  1. {qontract_reconcile-0.10.2.dev361.dist-info → qontract_reconcile-0.10.2.dev430.dist-info}/METADATA +13 -12
  2. {qontract_reconcile-0.10.2.dev361.dist-info → qontract_reconcile-0.10.2.dev430.dist-info}/RECORD +351 -345
  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_iam_keys.py +1 -0
  19. reconcile/aws_saml_idp/integration.py +12 -4
  20. reconcile/aws_saml_roles/integration.py +30 -23
  21. reconcile/aws_version_sync/integration.py +6 -12
  22. reconcile/change_owners/bundle.py +3 -3
  23. reconcile/change_owners/change_log_tracking.py +3 -2
  24. reconcile/change_owners/change_owners.py +1 -1
  25. reconcile/change_owners/diff.py +0 -2
  26. reconcile/checkpoint.py +11 -3
  27. reconcile/cli.py +93 -10
  28. reconcile/dashdotdb_dora.py +5 -12
  29. reconcile/dashdotdb_slo.py +1 -1
  30. reconcile/database_access_manager.py +123 -117
  31. reconcile/dynatrace_token_provider/integration.py +1 -1
  32. reconcile/endpoints_discovery/integration.py +4 -1
  33. reconcile/endpoints_discovery/merge_request.py +1 -1
  34. reconcile/endpoints_discovery/merge_request_manager.py +8 -8
  35. reconcile/external_resources/factories.py +4 -6
  36. reconcile/external_resources/integration.py +1 -1
  37. reconcile/external_resources/manager.py +8 -6
  38. reconcile/external_resources/meta.py +0 -1
  39. reconcile/external_resources/metrics.py +1 -1
  40. reconcile/external_resources/model.py +19 -15
  41. reconcile/external_resources/reconciler.py +7 -4
  42. reconcile/external_resources/secrets_sync.py +4 -7
  43. reconcile/external_resources/state.py +26 -16
  44. reconcile/fleet_labeler/integration.py +1 -1
  45. reconcile/gabi_authorized_users.py +5 -2
  46. reconcile/gcp_image_mirror.py +2 -2
  47. reconcile/github_org.py +1 -1
  48. reconcile/github_owners.py +4 -0
  49. reconcile/gitlab_housekeeping.py +13 -15
  50. reconcile/gitlab_members.py +6 -12
  51. reconcile/gitlab_owners.py +15 -11
  52. reconcile/gitlab_permissions.py +8 -12
  53. reconcile/glitchtip_project_alerts/integration.py +3 -1
  54. reconcile/gql_definitions/acs/acs_instances.py +5 -5
  55. reconcile/gql_definitions/acs/acs_policies.py +5 -5
  56. reconcile/gql_definitions/acs/acs_rbac.py +5 -5
  57. reconcile/gql_definitions/advanced_upgrade_service/aus_clusters.py +5 -5
  58. reconcile/gql_definitions/advanced_upgrade_service/aus_organization.py +5 -5
  59. reconcile/gql_definitions/app_interface_metrics_exporter/onboarding_status.py +5 -5
  60. reconcile/gql_definitions/app_sre_tekton_access_revalidation/roles.py +5 -5
  61. reconcile/gql_definitions/app_sre_tekton_access_revalidation/users.py +5 -5
  62. reconcile/gql_definitions/automated_actions/instance.py +46 -7
  63. reconcile/gql_definitions/aws_account_manager/aws_accounts.py +5 -5
  64. reconcile/gql_definitions/aws_ami_cleanup/aws_accounts.py +15 -5
  65. reconcile/gql_definitions/aws_cloudwatch_log_retention/aws_accounts.py +27 -66
  66. reconcile/gql_definitions/aws_saml_idp/aws_accounts.py +15 -5
  67. reconcile/gql_definitions/aws_saml_roles/aws_accounts.py +15 -5
  68. reconcile/gql_definitions/aws_saml_roles/roles.py +5 -5
  69. reconcile/gql_definitions/aws_version_sync/clusters.py +5 -5
  70. reconcile/gql_definitions/aws_version_sync/namespaces.py +5 -5
  71. reconcile/gql_definitions/change_owners/queries/change_types.py +5 -5
  72. reconcile/gql_definitions/change_owners/queries/self_service_roles.py +5 -5
  73. reconcile/gql_definitions/cluster_auth_rhidp/clusters.py +5 -5
  74. reconcile/gql_definitions/common/alerting_services_settings.py +5 -5
  75. reconcile/gql_definitions/common/app_code_component_repos.py +5 -5
  76. reconcile/gql_definitions/common/app_interface_custom_messages.py +5 -5
  77. reconcile/gql_definitions/common/app_interface_dms_settings.py +5 -5
  78. reconcile/gql_definitions/common/app_interface_repo_settings.py +5 -5
  79. reconcile/gql_definitions/common/app_interface_roles.py +5 -5
  80. reconcile/gql_definitions/common/app_interface_state_settings.py +5 -5
  81. reconcile/gql_definitions/common/app_interface_vault_settings.py +5 -5
  82. reconcile/gql_definitions/common/app_quay_repos_escalation_policies.py +5 -5
  83. reconcile/gql_definitions/common/apps.py +5 -5
  84. reconcile/gql_definitions/common/aws_vpc_requests.py +15 -5
  85. reconcile/gql_definitions/common/aws_vpcs.py +5 -5
  86. reconcile/gql_definitions/common/clusters.py +5 -5
  87. reconcile/gql_definitions/common/clusters_minimal.py +5 -5
  88. reconcile/gql_definitions/common/clusters_with_dms.py +5 -5
  89. reconcile/gql_definitions/common/clusters_with_peering.py +5 -5
  90. reconcile/gql_definitions/common/github_orgs.py +5 -5
  91. reconcile/gql_definitions/common/jira_settings.py +5 -5
  92. reconcile/gql_definitions/common/jiralert_settings.py +5 -5
  93. reconcile/gql_definitions/common/ldap_settings.py +5 -5
  94. reconcile/gql_definitions/common/namespaces.py +5 -5
  95. reconcile/gql_definitions/common/namespaces_minimal.py +7 -5
  96. reconcile/gql_definitions/common/ocm_env_telemeter.py +5 -5
  97. reconcile/gql_definitions/common/ocm_environments.py +5 -5
  98. reconcile/gql_definitions/common/pagerduty_instances.py +5 -5
  99. reconcile/gql_definitions/common/pgp_reencryption_settings.py +5 -5
  100. reconcile/gql_definitions/common/pipeline_providers.py +5 -5
  101. reconcile/gql_definitions/common/quay_instances.py +5 -5
  102. reconcile/gql_definitions/common/quay_orgs.py +5 -5
  103. reconcile/gql_definitions/common/reserved_networks.py +5 -5
  104. reconcile/gql_definitions/common/rhcs_provider_settings.py +5 -5
  105. reconcile/gql_definitions/common/saas_files.py +5 -5
  106. reconcile/gql_definitions/common/saas_target_namespaces.py +5 -5
  107. reconcile/gql_definitions/common/saasherder_settings.py +5 -5
  108. reconcile/gql_definitions/common/slack_workspaces.py +5 -5
  109. reconcile/gql_definitions/common/smtp_client_settings.py +5 -5
  110. reconcile/gql_definitions/common/state_aws_account.py +5 -5
  111. reconcile/gql_definitions/common/users.py +5 -5
  112. reconcile/gql_definitions/common/users_with_paths.py +5 -5
  113. reconcile/gql_definitions/cost_report/app_names.py +5 -5
  114. reconcile/gql_definitions/cost_report/cost_namespaces.py +5 -5
  115. reconcile/gql_definitions/cost_report/settings.py +5 -5
  116. reconcile/gql_definitions/dashdotdb_slo/slo_documents_query.py +5 -5
  117. reconcile/gql_definitions/dynatrace_token_provider/dynatrace_bootstrap_tokens.py +5 -5
  118. reconcile/gql_definitions/dynatrace_token_provider/token_specs.py +5 -5
  119. reconcile/gql_definitions/email_sender/apps.py +5 -5
  120. reconcile/gql_definitions/email_sender/emails.py +5 -5
  121. reconcile/gql_definitions/email_sender/users.py +5 -5
  122. reconcile/gql_definitions/endpoints_discovery/apps.py +5 -5
  123. reconcile/gql_definitions/external_resources/aws_accounts.py +5 -5
  124. reconcile/gql_definitions/external_resources/external_resources_modules.py +5 -5
  125. reconcile/gql_definitions/external_resources/external_resources_namespaces.py +33 -6
  126. reconcile/gql_definitions/external_resources/external_resources_settings.py +5 -5
  127. reconcile/gql_definitions/external_resources/fragments/external_resources_module_overrides.py +5 -5
  128. reconcile/gql_definitions/fleet_labeler/fleet_labels.py +5 -5
  129. reconcile/gql_definitions/fragments/aus_organization.py +5 -5
  130. reconcile/gql_definitions/fragments/aws_account_common.py +7 -5
  131. reconcile/gql_definitions/fragments/aws_account_managed.py +5 -5
  132. reconcile/gql_definitions/fragments/aws_account_sso.py +5 -5
  133. reconcile/gql_definitions/fragments/aws_infra_management_account.py +5 -5
  134. reconcile/gql_definitions/fragments/aws_organization.py +33 -0
  135. reconcile/gql_definitions/fragments/aws_vpc.py +5 -5
  136. reconcile/gql_definitions/fragments/aws_vpc_request.py +7 -5
  137. reconcile/gql_definitions/fragments/container_image_mirror.py +5 -5
  138. reconcile/gql_definitions/fragments/deploy_resources.py +5 -5
  139. reconcile/gql_definitions/fragments/disable.py +5 -5
  140. reconcile/gql_definitions/fragments/email_service.py +5 -5
  141. reconcile/gql_definitions/fragments/email_user.py +5 -5
  142. reconcile/gql_definitions/fragments/jumphost_common_fields.py +5 -5
  143. reconcile/gql_definitions/fragments/membership_source.py +5 -5
  144. reconcile/gql_definitions/fragments/minimal_ocm_organization.py +5 -5
  145. reconcile/gql_definitions/fragments/oc_connection_cluster.py +5 -5
  146. reconcile/gql_definitions/fragments/ocm_environment.py +5 -5
  147. reconcile/gql_definitions/fragments/pipeline_provider_retention.py +5 -5
  148. reconcile/gql_definitions/fragments/prometheus_instance.py +5 -5
  149. reconcile/gql_definitions/fragments/resource_limits_requirements.py +5 -5
  150. reconcile/gql_definitions/fragments/resource_requests_requirements.py +5 -5
  151. reconcile/gql_definitions/fragments/resource_values.py +5 -5
  152. reconcile/gql_definitions/fragments/saas_slo_document.py +5 -5
  153. reconcile/gql_definitions/fragments/saas_target_namespace.py +5 -5
  154. reconcile/gql_definitions/fragments/serviceaccount_token.py +5 -5
  155. reconcile/gql_definitions/fragments/terraform_state.py +5 -5
  156. reconcile/gql_definitions/fragments/upgrade_policy.py +5 -5
  157. reconcile/gql_definitions/fragments/user.py +5 -5
  158. reconcile/gql_definitions/fragments/vault_secret.py +5 -5
  159. reconcile/gql_definitions/gcp/gcp_docker_repos.py +5 -5
  160. reconcile/gql_definitions/gcp/gcp_projects.py +5 -5
  161. reconcile/gql_definitions/gitlab_members/gitlab_instances.py +5 -5
  162. reconcile/gql_definitions/gitlab_members/permissions.py +5 -5
  163. reconcile/gql_definitions/glitchtip/glitchtip_instance.py +5 -5
  164. reconcile/gql_definitions/glitchtip/glitchtip_project.py +5 -5
  165. reconcile/gql_definitions/glitchtip_project_alerts/glitchtip_project.py +5 -5
  166. reconcile/gql_definitions/integrations/integrations.py +5 -5
  167. reconcile/gql_definitions/introspection.json +724 -129
  168. reconcile/gql_definitions/jenkins_configs/jenkins_configs.py +5 -5
  169. reconcile/gql_definitions/jenkins_configs/jenkins_instances.py +5 -5
  170. reconcile/gql_definitions/jira/jira_servers.py +5 -5
  171. reconcile/gql_definitions/jira_permissions_validator/jira_boards_for_permissions_validator.py +9 -5
  172. reconcile/gql_definitions/jumphosts/jumphosts.py +5 -5
  173. reconcile/gql_definitions/ldap_groups/roles.py +5 -5
  174. reconcile/gql_definitions/ldap_groups/settings.py +5 -5
  175. reconcile/gql_definitions/maintenance/maintenances.py +5 -5
  176. reconcile/gql_definitions/membershipsources/roles.py +5 -5
  177. reconcile/gql_definitions/ocm_labels/clusters.py +5 -5
  178. reconcile/gql_definitions/ocm_labels/organizations.py +5 -5
  179. reconcile/gql_definitions/openshift_cluster_bots/clusters.py +5 -5
  180. reconcile/gql_definitions/openshift_groups/managed_groups.py +5 -5
  181. reconcile/gql_definitions/openshift_groups/managed_roles.py +5 -5
  182. reconcile/gql_definitions/openshift_serviceaccount_tokens/tokens.py +5 -5
  183. reconcile/gql_definitions/quay_membership/quay_membership.py +5 -5
  184. reconcile/gql_definitions/rhcs/certs.py +25 -79
  185. reconcile/gql_definitions/rhcs/openshift_resource_rhcs_cert.py +43 -0
  186. reconcile/gql_definitions/rhidp/organizations.py +5 -5
  187. reconcile/gql_definitions/service_dependencies/jenkins_instance_fragment.py +5 -5
  188. reconcile/gql_definitions/service_dependencies/service_dependencies.py +5 -5
  189. reconcile/gql_definitions/sharding/aws_accounts.py +5 -5
  190. reconcile/gql_definitions/sharding/ocm_organization.py +5 -5
  191. reconcile/gql_definitions/skupper_network/site_controller_template.py +5 -5
  192. reconcile/gql_definitions/skupper_network/skupper_networks.py +5 -5
  193. reconcile/gql_definitions/slack_usergroups/clusters.py +5 -5
  194. reconcile/gql_definitions/slack_usergroups/permissions.py +5 -5
  195. reconcile/gql_definitions/slack_usergroups/users.py +5 -5
  196. reconcile/gql_definitions/slo_documents/slo_documents.py +5 -5
  197. reconcile/gql_definitions/status_board/status_board.py +5 -5
  198. reconcile/gql_definitions/statuspage/statuspages.py +5 -5
  199. reconcile/gql_definitions/templating/template_collection.py +5 -5
  200. reconcile/gql_definitions/templating/templates.py +5 -5
  201. reconcile/gql_definitions/terraform_cloudflare_dns/app_interface_cloudflare_dns_settings.py +5 -5
  202. reconcile/gql_definitions/terraform_cloudflare_dns/terraform_cloudflare_zones.py +5 -5
  203. reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_accounts.py +5 -5
  204. reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_resources.py +5 -5
  205. reconcile/gql_definitions/terraform_cloudflare_users/app_interface_setting_cloudflare_and_vault.py +5 -5
  206. reconcile/gql_definitions/terraform_cloudflare_users/terraform_cloudflare_roles.py +5 -5
  207. reconcile/gql_definitions/terraform_init/aws_accounts.py +19 -5
  208. reconcile/gql_definitions/terraform_repo/terraform_repo.py +5 -5
  209. reconcile/gql_definitions/terraform_resources/database_access_manager.py +5 -5
  210. reconcile/gql_definitions/terraform_resources/terraform_resources_namespaces.py +30 -6
  211. reconcile/gql_definitions/terraform_tgw_attachments/aws_accounts.py +15 -5
  212. reconcile/gql_definitions/unleash_feature_toggles/feature_toggles.py +5 -5
  213. reconcile/gql_definitions/vault_instances/vault_instances.py +5 -5
  214. reconcile/gql_definitions/vault_policies/vault_policies.py +5 -5
  215. reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator.py +5 -5
  216. reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator_peered_cluster_fragment.py +5 -5
  217. reconcile/integrations_manager.py +3 -3
  218. reconcile/jenkins_worker_fleets.py +10 -8
  219. reconcile/jira_permissions_validator.py +237 -122
  220. reconcile/ldap_groups/integration.py +1 -1
  221. reconcile/ocm/types.py +35 -57
  222. reconcile/ocm_aws_infrastructure_access.py +1 -1
  223. reconcile/ocm_clusters.py +4 -4
  224. reconcile/ocm_labels/integration.py +3 -2
  225. reconcile/ocm_machine_pools.py +33 -27
  226. reconcile/openshift_base.py +113 -4
  227. reconcile/openshift_cluster_bots.py +1 -1
  228. reconcile/openshift_namespace_labels.py +1 -1
  229. reconcile/openshift_namespaces.py +97 -101
  230. reconcile/openshift_resources_base.py +6 -2
  231. reconcile/openshift_rhcs_certs.py +74 -37
  232. reconcile/openshift_rolebindings.py +7 -11
  233. reconcile/openshift_saas_deploy.py +4 -5
  234. reconcile/openshift_saas_deploy_change_tester.py +9 -7
  235. reconcile/openshift_saas_deploy_trigger_cleaner.py +3 -5
  236. reconcile/openshift_serviceaccount_tokens.py +2 -2
  237. reconcile/openshift_upgrade_watcher.py +4 -4
  238. reconcile/oum/labelset.py +5 -3
  239. reconcile/oum/models.py +1 -4
  240. reconcile/prometheus_rules_tester/integration.py +3 -3
  241. reconcile/quay_mirror.py +1 -1
  242. reconcile/queries.py +131 -0
  243. reconcile/rhidp/common.py +3 -5
  244. reconcile/rhidp/sso_client/base.py +16 -5
  245. reconcile/saas_auto_promotions_manager/merge_request_manager/renderer.py +1 -1
  246. reconcile/saas_auto_promotions_manager/subscriber.py +4 -3
  247. reconcile/skupper_network/integration.py +2 -2
  248. reconcile/slack_usergroups.py +35 -14
  249. reconcile/sql_query.py +1 -0
  250. reconcile/status_board.py +6 -6
  251. reconcile/statuspage/atlassian.py +7 -7
  252. reconcile/statuspage/integrations/maintenances.py +4 -3
  253. reconcile/statuspage/page.py +4 -9
  254. reconcile/statuspage/status.py +5 -8
  255. reconcile/templating/lib/rendering.py +3 -3
  256. reconcile/templating/renderer.py +2 -2
  257. reconcile/terraform_aws_route53.py +7 -1
  258. reconcile/terraform_cloudflare_dns.py +3 -3
  259. reconcile/terraform_cloudflare_resources.py +5 -5
  260. reconcile/terraform_cloudflare_users.py +3 -2
  261. reconcile/terraform_init/integration.py +187 -23
  262. reconcile/terraform_repo.py +16 -12
  263. reconcile/terraform_resources.py +6 -6
  264. reconcile/terraform_tgw_attachments.py +27 -19
  265. reconcile/terraform_users.py +7 -0
  266. reconcile/terraform_vpc_peerings.py +14 -3
  267. reconcile/terraform_vpc_resources/integration.py +10 -1
  268. reconcile/typed_queries/aws_account_tags.py +41 -0
  269. reconcile/typed_queries/cost_report/app_names.py +1 -1
  270. reconcile/typed_queries/cost_report/cost_namespaces.py +2 -2
  271. reconcile/typed_queries/saas_files.py +11 -11
  272. reconcile/typed_queries/status_board.py +2 -2
  273. reconcile/unleash_feature_toggles/integration.py +4 -2
  274. reconcile/utils/acs/base.py +6 -3
  275. reconcile/utils/acs/policies.py +2 -2
  276. reconcile/utils/aws_api.py +51 -20
  277. reconcile/utils/aws_api_typed/api.py +38 -9
  278. reconcile/utils/aws_api_typed/cloudformation.py +149 -0
  279. reconcile/utils/aws_api_typed/logs.py +73 -0
  280. reconcile/utils/aws_api_typed/organization.py +4 -2
  281. reconcile/utils/binary.py +7 -12
  282. reconcile/utils/datetime_util.py +67 -0
  283. reconcile/utils/deadmanssnitch_api.py +1 -1
  284. reconcile/utils/differ.py +2 -3
  285. reconcile/utils/early_exit_cache.py +11 -12
  286. reconcile/utils/expiration.py +7 -3
  287. reconcile/utils/filtering.py +1 -1
  288. reconcile/utils/gitlab_api.py +7 -5
  289. reconcile/utils/glitchtip/client.py +6 -2
  290. reconcile/utils/glitchtip/models.py +25 -28
  291. reconcile/utils/gql.py +4 -7
  292. reconcile/utils/helpers.py +1 -1
  293. reconcile/utils/instrumented_wrappers.py +1 -1
  294. reconcile/utils/internal_groups/client.py +2 -2
  295. reconcile/utils/internal_groups/models.py +8 -17
  296. reconcile/utils/jinja2/utils.py +6 -101
  297. reconcile/utils/jira_client.py +82 -63
  298. reconcile/utils/jjb_client.py +7 -10
  299. reconcile/utils/jobcontroller/controller.py +2 -2
  300. reconcile/utils/jobcontroller/models.py +17 -1
  301. reconcile/utils/json.py +43 -1
  302. reconcile/utils/membershipsources/app_interface_resolver.py +4 -2
  303. reconcile/utils/membershipsources/models.py +16 -23
  304. reconcile/utils/membershipsources/resolver.py +4 -2
  305. reconcile/utils/merge_request_manager/merge_request_manager.py +4 -4
  306. reconcile/utils/merge_request_manager/parser.py +6 -6
  307. reconcile/utils/metrics.py +5 -5
  308. reconcile/utils/models.py +304 -82
  309. reconcile/utils/mr/app_interface_reporter.py +2 -2
  310. reconcile/utils/mr/notificator.py +3 -3
  311. reconcile/utils/mr/update_access_report_base.py +3 -4
  312. reconcile/utils/mr/user_maintenance.py +3 -2
  313. reconcile/utils/oc.py +246 -201
  314. reconcile/utils/oc_filters.py +3 -3
  315. reconcile/utils/ocm/addons.py +0 -1
  316. reconcile/utils/ocm/base.py +17 -20
  317. reconcile/utils/ocm/cluster_groups.py +1 -1
  318. reconcile/utils/ocm/identity_providers.py +2 -2
  319. reconcile/utils/ocm/labels.py +1 -1
  320. reconcile/utils/ocm/products.py +8 -8
  321. reconcile/utils/ocm/search_filters.py +3 -6
  322. reconcile/utils/ocm/service_log.py +4 -6
  323. reconcile/utils/ocm/sre_capability_labels.py +20 -13
  324. reconcile/utils/openshift_resource.py +8 -3
  325. reconcile/utils/pagerduty_api.py +10 -7
  326. reconcile/utils/promotion_state.py +6 -11
  327. reconcile/utils/raw_github_api.py +1 -1
  328. reconcile/utils/rhcsv2_certs.py +86 -23
  329. reconcile/utils/rosa/session.py +16 -0
  330. reconcile/utils/runtime/integration.py +2 -3
  331. reconcile/utils/runtime/runner.py +2 -2
  332. reconcile/utils/saasherder/interfaces.py +13 -20
  333. reconcile/utils/saasherder/models.py +23 -20
  334. reconcile/utils/saasherder/saasherder.py +50 -27
  335. reconcile/utils/slack_api.py +2 -2
  336. reconcile/utils/sloth.py +171 -2
  337. reconcile/utils/structs.py +1 -1
  338. reconcile/utils/terraform_client.py +5 -4
  339. reconcile/utils/terrascript_aws_client.py +134 -74
  340. reconcile/utils/unleash/server.py +2 -8
  341. reconcile/utils/vault.py +5 -12
  342. reconcile/utils/vcs.py +8 -8
  343. reconcile/vault_replication.py +107 -42
  344. tools/app_interface_reporter.py +4 -4
  345. tools/cli_commands/cost_report/cost_management_api.py +3 -3
  346. tools/cli_commands/cost_report/view.py +7 -6
  347. tools/cli_commands/erv2.py +1 -1
  348. tools/qontract_cli.py +28 -17
  349. tools/template_validation.py +3 -1
  350. {qontract_reconcile-0.10.2.dev361.dist-info → qontract_reconcile-0.10.2.dev430.dist-info}/WHEEL +0 -0
  351. {qontract_reconcile-0.10.2.dev361.dist-info → qontract_reconcile-0.10.2.dev430.dist-info}/entry_points.txt +0 -0
@@ -2,31 +2,41 @@ from __future__ import annotations
2
2
 
3
3
  import logging
4
4
  import re
5
- import typing
6
5
  from collections import defaultdict
7
6
  from datetime import UTC, datetime, timedelta
8
- from enum import Enum
9
7
  from typing import TYPE_CHECKING
10
8
 
11
- from botocore.exceptions import ClientError
12
9
  from pydantic import BaseModel
13
10
 
14
- from reconcile import queries
15
11
  from reconcile.gql_definitions.aws_cloudwatch_log_retention.aws_accounts import (
16
12
  AWSAccountCleanupOptionCloudWatchV1,
17
13
  AWSAccountV1,
18
14
  )
15
+ from reconcile.typed_queries.app_interface_vault_settings import (
16
+ get_app_interface_vault_settings,
17
+ )
18
+ from reconcile.typed_queries.aws_account_tags import get_aws_account_tags
19
19
  from reconcile.typed_queries.aws_cloudwatch_log_retention.aws_accounts import (
20
20
  get_aws_accounts,
21
21
  )
22
+ from reconcile.typed_queries.external_resources import get_settings
22
23
  from reconcile.utils import gql
23
- from reconcile.utils.aws_api import AWSApi
24
+ from reconcile.utils.aws_api_typed.api import AWSApi, AWSStaticCredentials
25
+ from reconcile.utils.datetime_util import utc_now
26
+ from reconcile.utils.differ import diff_mappings
27
+ from reconcile.utils.secret_reader import create_secret_reader
28
+ from reconcile.utils.state import init_state
29
+
30
+ TAGS_KEY = "tags.json"
24
31
 
25
32
  if TYPE_CHECKING:
26
33
  from collections.abc import Iterable
27
34
 
28
35
  from mypy_boto3_logs.type_defs import LogGroupTypeDef
29
36
 
37
+ from reconcile.utils.aws_api_typed.logs import AWSApiLogs
38
+ from reconcile.utils.gql import GqlApi
39
+
30
40
 
31
41
  QONTRACT_INTEGRATION = "aws_cloudwatch_log_retention"
32
42
  MANAGED_BY_INTEGRATION_KEY = "managed_by_integration"
@@ -35,7 +45,7 @@ DEFAULT_RETENTION_IN_DAYS = 90
35
45
 
36
46
 
37
47
  class AWSCloudwatchCleanupOption(BaseModel):
38
- regex: typing.Pattern
48
+ regex: re.Pattern
39
49
  retention_in_days: int
40
50
  delete_empty_log_group: bool
41
51
 
@@ -67,16 +77,6 @@ def get_desired_cleanup_options_by_region(
67
77
  return result
68
78
 
69
79
 
70
- def create_awsapi_client(accounts: list[AWSAccountV1], thread_pool_size: int) -> AWSApi:
71
- settings = queries.get_secret_reader_settings()
72
- return AWSApi(
73
- thread_pool_size,
74
- [account.dict(by_alias=True) for account in accounts],
75
- settings=settings,
76
- init_users=False,
77
- )
78
-
79
-
80
80
  def is_empty(log_group: LogGroupTypeDef) -> bool:
81
81
  return log_group["storedBytes"] == 0
82
82
 
@@ -85,47 +85,32 @@ def is_longer_than_retention(
85
85
  log_group: LogGroupTypeDef,
86
86
  desired_retention_days: int,
87
87
  ) -> bool:
88
- return datetime.fromtimestamp(log_group["creationTime"] / 1000, tz=UTC) + timedelta(
89
- days=desired_retention_days
90
- ) < datetime.now(tz=UTC)
91
-
92
-
93
- class TagStatus(Enum):
94
- NOT_SET = "NOT_SET"
95
- MANAGED_BY_CURRENT_INTEGRATION = "MANAGED_BY_CURRENT_INTEGRATION"
96
- MANAGED_BY_OTHER_INTEGRATION = "MANAGED_BY_OTHER_INTEGRATION"
88
+ return (
89
+ datetime.fromtimestamp(log_group["creationTime"] / 1000, tz=UTC)
90
+ + timedelta(days=desired_retention_days)
91
+ < utc_now()
92
+ )
97
93
 
98
94
 
99
- def get_tag_status(
100
- log_group: LogGroupTypeDef,
101
- account_name: str,
102
- region: str,
103
- aws_api: AWSApi,
104
- ) -> TagStatus:
105
- tags = aws_api.get_cloudwatch_log_group_tags(
106
- account_name,
107
- log_group["arn"],
108
- region,
109
- )
95
+ def _is_managed_by_other_integration(tags: dict[str, str]) -> bool:
110
96
  managed_by_integration = tags.get(MANAGED_BY_INTEGRATION_KEY)
111
- if managed_by_integration is None:
112
- return TagStatus.NOT_SET
113
- if managed_by_integration == QONTRACT_INTEGRATION:
114
- return TagStatus.MANAGED_BY_CURRENT_INTEGRATION
115
- return TagStatus.MANAGED_BY_OTHER_INTEGRATION
97
+ return (
98
+ managed_by_integration is not None
99
+ and managed_by_integration != QONTRACT_INTEGRATION
100
+ )
116
101
 
117
102
 
118
103
  def _reconcile_log_group(
119
104
  dry_run: bool,
120
- aws_log_group: LogGroupTypeDef,
105
+ log_group: LogGroupTypeDef,
121
106
  desired_cleanup_options: Iterable[AWSCloudwatchCleanupOption],
122
- account_name: str,
123
- region: str,
124
- awsapi: AWSApi,
107
+ desired_tags: dict[str, str],
108
+ last_tags: dict[str, str],
109
+ aws_api_logs: AWSApiLogs,
125
110
  ) -> None:
126
- current_retention_in_days = aws_log_group.get("retentionInDays")
127
- log_group_name = aws_log_group["logGroupName"]
128
- log_group_arn = aws_log_group["arn"]
111
+ current_retention_in_days = log_group.get("retentionInDays")
112
+ log_group_name = log_group["logGroupName"]
113
+ log_group_arn = log_group["arn"]
129
114
 
130
115
  desired_cleanup_option = _find_desired_cleanup_option(
131
116
  log_group_name, desired_cleanup_options
@@ -133,54 +118,66 @@ def _reconcile_log_group(
133
118
 
134
119
  if (
135
120
  desired_cleanup_option.delete_empty_log_group
136
- and is_empty(aws_log_group)
121
+ and is_empty(log_group)
137
122
  and is_longer_than_retention(
138
- aws_log_group, desired_cleanup_option.retention_in_days
123
+ log_group, desired_cleanup_option.retention_in_days
139
124
  )
140
125
  ):
141
- if (
142
- get_tag_status(aws_log_group, account_name, region, awsapi)
143
- != TagStatus.MANAGED_BY_OTHER_INTEGRATION
144
- ):
126
+ tags = aws_api_logs.get_tags(log_group_arn)
127
+ if not _is_managed_by_other_integration(tags):
145
128
  logging.info(
146
129
  "Deleting empty log group %s",
147
130
  log_group_arn,
148
131
  )
149
132
  if not dry_run:
150
- awsapi.delete_cloudwatch_log_group(account_name, log_group_name, region)
133
+ aws_api_logs.delete_log_group(log_group_name)
151
134
  return
152
135
 
153
- if current_retention_in_days == desired_cleanup_option.retention_in_days:
136
+ if (
137
+ current_retention_in_days == desired_cleanup_option.retention_in_days
138
+ and last_tags == desired_tags
139
+ ):
154
140
  return
155
141
 
156
- match get_tag_status(aws_log_group, account_name, region, awsapi):
157
- case TagStatus.MANAGED_BY_OTHER_INTEGRATION:
158
- return
159
- case TagStatus.MANAGED_BY_CURRENT_INTEGRATION:
160
- pass
161
- case TagStatus.NOT_SET:
162
- logging.info(
163
- "Setting tag %s for log group %s",
164
- MANAGED_TAG,
142
+ current_tags = aws_api_logs.get_tags(log_group_arn)
143
+ if _is_managed_by_other_integration(current_tags):
144
+ return
145
+
146
+ diff_result = diff_mappings(
147
+ current=current_tags,
148
+ desired=desired_tags,
149
+ )
150
+ if to_delete := diff_result.delete.keys() & last_tags.keys():
151
+ logging.info(
152
+ "Deleting tags %s for log group %s",
153
+ to_delete,
154
+ log_group_arn,
155
+ )
156
+ if not dry_run:
157
+ aws_api_logs.delete_tags(
165
158
  log_group_arn,
159
+ to_delete,
166
160
  )
167
- if not dry_run:
168
- awsapi.create_cloudwatch_tag(
169
- account_name, log_group_arn, MANAGED_TAG, region
170
- )
161
+ if diff_result.add or diff_result.change:
162
+ logging.info(
163
+ "Setting tags %s for log group %s",
164
+ desired_tags,
165
+ log_group_arn,
166
+ )
167
+ if not dry_run:
168
+ aws_api_logs.set_tags(log_group_arn, desired_tags)
171
169
 
172
- logging.info(
173
- "Setting %s retention days to %d",
174
- log_group_arn,
175
- desired_cleanup_option.retention_in_days,
176
- )
177
- if not dry_run:
178
- awsapi.set_cloudwatch_log_retention(
179
- account_name,
180
- log_group_name,
170
+ if current_retention_in_days != desired_cleanup_option.retention_in_days:
171
+ logging.info(
172
+ "Setting %s retention days to %d",
173
+ log_group_arn,
181
174
  desired_cleanup_option.retention_in_days,
182
- region,
183
175
  )
176
+ if not dry_run:
177
+ aws_api_logs.put_retention_policy(
178
+ log_group_name,
179
+ desired_cleanup_option.retention_in_days,
180
+ )
184
181
 
185
182
 
186
183
  def _find_desired_cleanup_option(
@@ -191,60 +188,63 @@ def _find_desired_cleanup_option(
191
188
  Find the first cleanup option that regex matches the log group name.
192
189
  If no match is found, return the default cleanup option.
193
190
 
194
- :param log_group_name: The name of the log group
195
- :param desired_cleanup_options: The desired cleanup options
196
- :return: The desired cleanup option
191
+ Args:
192
+ log_group_name: The name of the log group.
193
+ desired_cleanup_options: A list of desired cleanup options.
194
+ Returns:
195
+ The matching cleanup option or the default one.
197
196
  """
198
- return next(
199
- (o for o in desired_cleanup_options if o.regex.match(log_group_name)),
200
- DEFAULT_AWS_CLOUDWATCH_CLEANUP_OPTION,
201
- )
197
+ for option in desired_cleanup_options:
198
+ if option.regex.match(log_group_name):
199
+ return option
200
+ return DEFAULT_AWS_CLOUDWATCH_CLEANUP_OPTION
202
201
 
203
202
 
204
203
  def _reconcile_log_groups(
205
204
  dry_run: bool,
206
205
  aws_account: AWSAccountV1,
207
- awsapi: AWSApi,
208
- ) -> None:
209
- account_name = aws_account.name
210
- desired_cleanup_options_by_region = get_desired_cleanup_options_by_region(
211
- aws_account
206
+ last_tags: dict[str, str],
207
+ default_tags: dict[str, str],
208
+ automation_token: dict[str, str],
209
+ ) -> dict[str, str]:
210
+ desired_tags = (
211
+ default_tags | get_aws_account_tags(aws_account.organization) | MANAGED_TAG
212
212
  )
213
- try:
214
- for (
215
- region,
216
- desired_cleanup_options,
217
- ) in desired_cleanup_options_by_region.items():
218
- for aws_log_group in awsapi.get_cloudwatch_log_groups(
219
- account_name,
220
- region,
221
- ):
222
- _reconcile_log_group(
223
- dry_run=dry_run,
224
- aws_log_group=aws_log_group,
225
- desired_cleanup_options=desired_cleanup_options,
226
- account_name=account_name,
227
- region=region,
228
- awsapi=awsapi,
213
+ for (
214
+ region,
215
+ desired_cleanup_options,
216
+ ) in get_desired_cleanup_options_by_region(aws_account).items():
217
+ aws_credentials = AWSStaticCredentials(
218
+ access_key_id=automation_token["aws_access_key_id"],
219
+ secret_access_key=automation_token["aws_secret_access_key"],
220
+ region=region,
221
+ )
222
+ with AWSApi(aws_credentials) as aws_api:
223
+ aws_api_logs = aws_api.logs
224
+ try:
225
+ for log_group in aws_api_logs.get_log_groups():
226
+ _reconcile_log_group(
227
+ dry_run=dry_run,
228
+ log_group=log_group,
229
+ desired_cleanup_options=desired_cleanup_options,
230
+ desired_tags=desired_tags,
231
+ last_tags=last_tags,
232
+ aws_api_logs=aws_api_logs,
233
+ )
234
+ except aws_api_logs.client.exceptions.ClientError as e:
235
+ logging.error(
236
+ "Error reconciling log groups for %s: %s",
237
+ aws_account.name,
238
+ e,
229
239
  )
230
- except ClientError as e:
231
- if e.response["Error"]["Code"] == "AccessDeniedException":
232
- logging.info(
233
- "Access denied for aws account %s. Skipping...",
234
- account_name,
235
- )
236
- else:
237
- logging.error(
238
- "Error reconciling log groups for %s: %s",
239
- account_name,
240
- e,
241
- )
240
+ return last_tags
241
+ return desired_tags
242
242
 
243
243
 
244
- def get_active_aws_accounts() -> list[AWSAccountV1]:
244
+ def get_active_aws_accounts(gql_api: GqlApi) -> list[AWSAccountV1]:
245
245
  return [
246
246
  account
247
- for account in get_aws_accounts(gql.get_api())
247
+ for account in get_aws_accounts(gql_api)
248
248
  if not (
249
249
  account.disable
250
250
  and account.disable.integrations
@@ -253,8 +253,37 @@ def get_active_aws_accounts() -> list[AWSAccountV1]:
253
253
  ]
254
254
 
255
255
 
256
- def run(dry_run: bool, thread_pool_size: int) -> None:
257
- aws_accounts = get_active_aws_accounts()
258
- with create_awsapi_client(aws_accounts, thread_pool_size) as awsapi:
259
- for aws_account in aws_accounts:
260
- _reconcile_log_groups(dry_run, aws_account, awsapi)
256
+ def get_default_tags(gql_api: GqlApi) -> dict[str, str]:
257
+ try:
258
+ return get_settings(gql_api.query).default_tags
259
+ except ValueError:
260
+ # no settings found
261
+ return {}
262
+
263
+
264
+ def run(dry_run: bool) -> None:
265
+ gql_api = gql.get_api()
266
+ aws_accounts = get_active_aws_accounts(gql_api)
267
+ vault_settings = get_app_interface_vault_settings(query_func=gql_api.query)
268
+ secret_reader = create_secret_reader(use_vault=vault_settings.vault)
269
+ default_tags = get_default_tags(gql_api)
270
+
271
+ with init_state(
272
+ integration=QONTRACT_INTEGRATION,
273
+ secret_reader=secret_reader,
274
+ ) as state:
275
+ last_tags = state.get(TAGS_KEY, {})
276
+ desired_tags = {
277
+ aws_account.name: _reconcile_log_groups(
278
+ dry_run=dry_run,
279
+ aws_account=aws_account,
280
+ last_tags=last_tags.get(aws_account.name, {}),
281
+ default_tags=default_tags,
282
+ automation_token=secret_reader.read_all_secret(
283
+ aws_account.automation_token
284
+ ),
285
+ )
286
+ for aws_account in aws_accounts
287
+ }
288
+ if not dry_run and desired_tags != last_tags:
289
+ state.add(TAGS_KEY, desired_tags, force=True)
reconcile/aws_iam_keys.py CHANGED
@@ -65,6 +65,7 @@ def init_tf_working_dirs(
65
65
  thread_pool_size,
66
66
  accounts,
67
67
  settings=settings,
68
+ default_tags=None,
68
69
  )
69
70
  return ts.dump()
70
71
 
@@ -19,6 +19,7 @@ from reconcile.gql_definitions.aws_saml_idp.aws_accounts import (
19
19
  query as aws_accounts_query,
20
20
  )
21
21
  from reconcile.status import ExitCodes
22
+ from reconcile.typed_queries.external_resources import get_settings
22
23
  from reconcile.utils import gql
23
24
  from reconcile.utils.aws_api import AWSApi
24
25
  from reconcile.utils.constants import DEFAULT_THREAD_POOL_SIZE
@@ -81,7 +82,7 @@ class AwsSamlIdpIntegration(QontractReconcileIntegration[AwsSamlIdpIntegrationPa
81
82
  if not query_func:
82
83
  query_func = gql.get_api().query
83
84
  return {
84
- "accounts": [c.dict() for c in self.get_aws_accounts(query_func)],
85
+ "accounts": [c.model_dump() for c in self.get_aws_accounts(query_func)],
85
86
  }
86
87
 
87
88
  def get_aws_accounts(
@@ -124,20 +125,27 @@ class AwsSamlIdpIntegration(QontractReconcileIntegration[AwsSamlIdpIntegrationPa
124
125
  aws_accounts = self.get_aws_accounts(
125
126
  gql_api.query, account_name=self.params.account_name
126
127
  )
127
- aws_accounts_dict = [account.dict(by_alias=True) for account in aws_accounts]
128
-
128
+ aws_accounts_dict = [
129
+ account.model_dump(by_alias=True) for account in aws_accounts
130
+ ]
131
+ try:
132
+ default_tags = get_settings().default_tags
133
+ except ValueError:
134
+ # no external resources settings found
135
+ default_tags = None
129
136
  ts = TerrascriptClient(
130
137
  self.name.replace("-", "_"),
131
138
  "",
132
139
  self.params.thread_pool_size,
133
140
  aws_accounts_dict,
134
141
  secret_reader=self.secret_reader,
142
+ default_tags=default_tags,
135
143
  )
136
144
 
137
145
  for saml_idp_config in self.build_saml_idp_config(
138
146
  aws_accounts,
139
147
  saml_idp_name=self.params.saml_idp_name,
140
- saml_metadata=self.get_saml_metadata(self.params.saml_metadata_url),
148
+ saml_metadata=self.get_saml_metadata(str(self.params.saml_metadata_url)),
141
149
  ):
142
150
  ts.populate_saml_idp(
143
151
  account_name=saml_idp_config.account_name,
@@ -6,10 +6,11 @@ from collections.abc import (
6
6
  )
7
7
  from typing import (
8
8
  Any,
9
+ Self,
9
10
  TypedDict,
10
11
  )
11
12
 
12
- from pydantic import BaseModel, root_validator, validator
13
+ from pydantic import BaseModel, field_validator, model_validator
13
14
 
14
15
  from reconcile.gql_definitions.aws_saml_roles.aws_accounts import (
15
16
  AWSAccountV1,
@@ -21,6 +22,7 @@ from reconcile.gql_definitions.aws_saml_roles.roles import (
21
22
  query as roles_query,
22
23
  )
23
24
  from reconcile.status import ExitCodes
25
+ from reconcile.typed_queries.external_resources import get_settings
24
26
  from reconcile.utils import gql
25
27
  from reconcile.utils.aws_api import AWSApi
26
28
  from reconcile.utils.aws_helper import unique_sso_aws_accounts
@@ -58,7 +60,8 @@ class AwsSamlRolesIntegrationParams(PydanticRunParams):
58
60
  extended_early_exit_cache_ttl_seconds: int = 3600
59
61
  log_cached_log_output: bool = False
60
62
 
61
- @validator("max_session_duration_hours")
63
+ @field_validator("max_session_duration_hours")
64
+ @classmethod
62
65
  def max_session_duration_range(cls, v: str | int) -> int:
63
66
  if 1 <= int(v) <= 12:
64
67
  return int(v)
@@ -69,7 +72,8 @@ class CustomPolicy(BaseModel):
69
72
  name: str
70
73
  policy: dict[str, Any]
71
74
 
72
- @validator("name")
75
+ @field_validator("name")
76
+ @classmethod
73
77
  def name_size(cls, v: str) -> str:
74
78
  """Check the policy name size.
75
79
 
@@ -81,7 +85,8 @@ class CustomPolicy(BaseModel):
81
85
  )
82
86
  return v
83
87
 
84
- @validator("policy")
88
+ @field_validator("policy")
89
+ @classmethod
85
90
  def policy_size(cls, v: dict[str, Any]) -> dict[str, Any]:
86
91
  """Check the policy size.
87
92
 
@@ -104,7 +109,8 @@ class AwsRole(BaseModel):
104
109
  custom_policies: list[CustomPolicy]
105
110
  managed_policies: list[ManagedPolicy]
106
111
 
107
- @validator("name")
112
+ @field_validator("name")
113
+ @classmethod
108
114
  def name_size(cls, v: str) -> str:
109
115
  """Check the role name size.
110
116
 
@@ -116,29 +122,23 @@ class AwsRole(BaseModel):
116
122
  )
117
123
  return v
118
124
 
119
- @root_validator
120
- def validate_policies(cls, values: dict[str, Any]) -> dict[str, Any]:
125
+ @model_validator(mode="after")
126
+ def validate_policies(self) -> Self:
121
127
  """Check the policies.
122
128
 
123
129
  See https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_iam-quotas.html
124
130
  """
125
- custom_policies = values.get("custom_policies", [])
126
- managed_policies = values.get("managed_policies", [])
127
- if len(custom_policies) + len(managed_policies) > 20:
131
+ if len(self.custom_policies) + len(self.managed_policies) > 20:
128
132
  raise ValueError(
129
- f"The role '{values['name']}' has too many policies. AWS roles can have at most 20 policies (via quota increase). Please consider consolidating the policies."
133
+ f"The role '{self.name}' has too many policies. AWS roles can have at most 20 policies (via quota increase). Please consider consolidating the policies."
130
134
  )
131
- cp_names = [cp.name for cp in custom_policies]
135
+ cp_names = [cp.name for cp in self.custom_policies]
132
136
  if len(set(cp_names)) != len(cp_names):
133
- raise ValueError(
134
- f"The role '{values['name']}' has duplicate custom policies."
135
- )
136
- mp_names = [mp.name for mp in managed_policies]
137
+ raise ValueError(f"The role '{self.name}' has duplicate custom policies.")
138
+ mp_names = [mp.name for mp in self.managed_policies]
137
139
  if len(set(mp_names)) != len(mp_names):
138
- raise ValueError(
139
- f"The role '{values['name']}' has duplicate managed policies."
140
- )
141
- return values
140
+ raise ValueError(f"The role '{self.name}' has duplicate managed policies.")
141
+ return self
142
142
 
143
143
 
144
144
  class RunnerParams(TypedDict):
@@ -163,7 +163,7 @@ class AwsSamlRolesIntegration(
163
163
  if not query_func:
164
164
  query_func = gql.get_api().query
165
165
  return {
166
- "roles": [c.dict() for c in self.get_roles(query_func)],
166
+ "roles": [c.model_dump() for c in self.get_roles(query_func)],
167
167
  }
168
168
 
169
169
  def get_aws_accounts(
@@ -251,15 +251,22 @@ class AwsSamlRolesIntegration(
251
251
  aws_accounts = self.get_aws_accounts(
252
252
  gql_api.query, account_name=self.params.account_name
253
253
  )
254
- aws_accounts_dict = [account.dict(by_alias=True) for account in aws_accounts]
254
+ aws_accounts_dict = [
255
+ account.model_dump(by_alias=True) for account in aws_accounts
256
+ ]
255
257
  aws_roles = self.get_roles(gql_api.query, account_name=self.params.account_name)
256
-
258
+ try:
259
+ default_tags = get_settings().default_tags
260
+ except ValueError:
261
+ # no external resources settings found
262
+ default_tags = None
257
263
  ts = TerrascriptClient(
258
264
  self.name.replace("-", "_"),
259
265
  "",
260
266
  self.params.thread_pool_size,
261
267
  aws_accounts_dict,
262
268
  secret_reader=self.secret_reader,
269
+ default_tags=default_tags,
263
270
  )
264
271
  self.populate_saml_iam_roles(ts, aws_roles)
265
272
  working_dirs = ts.dump(print_to_file=self.params.print_to_file)
@@ -7,12 +7,7 @@ from enum import StrEnum
7
7
  from typing import Any
8
8
 
9
9
  import semver
10
- from pydantic import (
11
- BaseModel,
12
- ValidationError,
13
- root_validator,
14
- validator,
15
- )
10
+ from pydantic import BaseModel, ValidationError, field_validator, model_validator
16
11
 
17
12
  from reconcile.aws_version_sync.merge_request_manager.merge_request import (
18
13
  Renderer,
@@ -81,7 +76,7 @@ class SupportedResourceProvider(StrEnum):
81
76
  ELASTICACHE = "elasticache"
82
77
 
83
78
 
84
- class ExternalResource(BaseModel):
79
+ class ExternalResource(BaseModel, arbitrary_types_allowed=True):
85
80
  namespace_file: str | None = None
86
81
  provider: str = "aws"
87
82
  provisioner: ExternalResourceProvisioner
@@ -94,9 +89,6 @@ class ExternalResource(BaseModel):
94
89
  # used to map AWS cache name to resource_identifier
95
90
  redis_replication_group_id: str | None = None
96
91
 
97
- class Config:
98
- arbitrary_types_allowed = True
99
-
100
92
  @property
101
93
  def key(self) -> tuple:
102
94
  return (
@@ -106,7 +98,8 @@ class ExternalResource(BaseModel):
106
98
  self.resource_identifier,
107
99
  )
108
100
 
109
- @validator("resource_engine_version", pre=True)
101
+ @field_validator("resource_engine_version", mode="before")
102
+ @classmethod
110
103
  def parse_resource_engine_version(
111
104
  cls, v: str | semver.VersionInfo
112
105
  ) -> semver.VersionInfo:
@@ -114,7 +107,8 @@ class ExternalResource(BaseModel):
114
107
  return v
115
108
  return parse_semver(str(v), optional_minor_and_patch=True)
116
109
 
117
- @root_validator(pre=True)
110
+ @model_validator(mode="before")
111
+ @classmethod
118
112
  def set_resource_engine_version_format(cls, values: dict) -> dict:
119
113
  resource_engine_version, resource_engine_version_format = (
120
114
  str(values.get("resource_engine_version")),
@@ -62,8 +62,8 @@ class QontractServerDatafileDiff(BaseModel):
62
62
 
63
63
  datafilepath: str
64
64
  datafileschema: str
65
- old: dict[str, Any] | None
66
- new: dict[str, Any] | None
65
+ old: dict[str, Any] | None = None
66
+ new: dict[str, Any] | None = None
67
67
 
68
68
  @property
69
69
  def old_datafilepath(self) -> str | None:
@@ -119,7 +119,7 @@ class QontractServerResourcefileDiffState(BaseModel):
119
119
  content: str
120
120
  resourcefileschema: str | None = Field(..., alias="$schema")
121
121
  sha256sum: str
122
- backrefs: list[QontractServerResourcefileBackref] | None
122
+ backrefs: list[QontractServerResourcefileBackref] | None = None
123
123
 
124
124
 
125
125
  class QontractServerResourcefileDiff(BaseModel):
@@ -155,7 +155,8 @@ class ChangeLogIntegration(QontractReconcileIntegration[ChangeLogIntegrationPara
155
155
  changes = aggregate_resource_changes(
156
156
  bundle_changes=aggregate_file_moves(parse_bundle_changes(diff)),
157
157
  content_store={
158
- c.path: c.dict(by_alias=True) for c in namespaces + jenkins_configs
158
+ c.path: c.model_dump(by_alias=True)
159
+ for c in namespaces + jenkins_configs
159
160
  },
160
161
  supported_schemas={
161
162
  "/openshift/namespace-1.yml",
@@ -239,4 +240,4 @@ class ChangeLogIntegration(QontractReconcileIntegration[ChangeLogIntegrationPara
239
240
  change_log.items, key=lambda i: i.merged_at, reverse=True
240
241
  )
241
242
  if not dry_run:
242
- integration_state.add(BUNDLE_DIFFS_OBJ, change_log.dict(), force=True)
243
+ integration_state.add(BUNDLE_DIFFS_OBJ, change_log.model_dump(), force=True)
@@ -140,7 +140,7 @@ def write_coverage_report_to_mr(
140
140
  approver_reachability = set()
141
141
  for d in change_decisions:
142
142
  approvers = [
143
- f"{cr.context} - {' '.join([f'@{a.org_username}' if a.tag_on_merge_requests else a.org_username for a in cr.approvers])}"
143
+ f"{cr.context} - {' '.join([f'@{a.org_username}' if (a.tag_on_merge_requests or len(cr.approvers) == 1) else a.org_username for a in cr.approvers])}"
144
144
  for cr in d.change_responsibles
145
145
  ]
146
146
  if d.coverable_by_fragment_decisions: