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
@@ -32,6 +32,7 @@ from reconcile.typed_queries.app_interface_vault_settings import (
32
32
  get_app_interface_vault_settings,
33
33
  )
34
34
  from reconcile.typed_queries.clusters_with_peering import get_clusters_with_peering
35
+ from reconcile.typed_queries.external_resources import get_settings
35
36
  from reconcile.typed_queries.terraform_tgw_attachments.aws_accounts import (
36
37
  get_aws_accounts,
37
38
  )
@@ -68,7 +69,7 @@ class ValidationError(Exception):
68
69
  class TGWAccountProviderInfo(BaseModel):
69
70
  name: str
70
71
  uid: str
71
- assume_role: str | None
72
+ assume_role: str | None = None
72
73
  assume_region: str
73
74
 
74
75
 
@@ -80,10 +81,10 @@ class Requester(BaseModel):
80
81
  tgw_id: str
81
82
  tgw_arn: str
82
83
  region: str
83
- routes: list[dict] | None
84
- rules: list[dict] | None
85
- hostedzones: list[str] | None
86
- cidr_block: str | None
84
+ routes: list[dict] | None = None
85
+ rules: list[dict] | None = None
86
+ hostedzones: list[str] | None = None
87
+ cidr_block: str | None = None
87
88
  cidr_blocks: list[str]
88
89
  account: TGWAccountProviderInfo
89
90
 
@@ -91,11 +92,11 @@ class Requester(BaseModel):
91
92
  class Accepter(BaseModel):
92
93
  cidr_block: str
93
94
  region: str
94
- vpc_id: str | None
95
- route_table_ids: list[str] | None
96
- subnets_id_az: list[dict[str, str]] | None
95
+ vpc_id: str | None = None
96
+ route_table_ids: list[str] | None = None
97
+ subnets_id_az: list[dict[str, str]] | None = None
97
98
  account: ClusterAccountProviderInfo
98
- api_security_group_id: str | None
99
+ api_security_group_id: str | None = None
99
100
 
100
101
 
101
102
  class DesiredStateItem(BaseModel):
@@ -192,7 +193,7 @@ def _build_desired_state_tgw_connection(
192
193
  yield None
193
194
 
194
195
  account_tgws = awsapi.get_tgws_details(
195
- peer_connection.account.dict(by_alias=True),
196
+ peer_connection.account.model_dump(by_alias=True),
196
197
  cluster_region,
197
198
  cluster_cidr_block,
198
199
  tags=peer_connection.tags or {},
@@ -274,7 +275,7 @@ def _build_accepter(
274
275
  )
275
276
  (vpc_id, route_table_ids, subnets_id_az, api_security_group_id) = (
276
277
  awsapi.get_cluster_vpc_details(
277
- account.dict(by_alias=True),
278
+ account.model_dump(by_alias=True),
278
279
  route_tables=bool(peer_connection.manage_routes),
279
280
  subnets=True,
280
281
  hcp_vpc_endpoint_sg=allow_hcp_private_api_access,
@@ -317,12 +318,12 @@ def _build_ocm_map(
317
318
  clusters: Iterable[ClusterV1],
318
319
  vault_settings: AppInterfaceSettingsV1,
319
320
  ) -> OCMMap | None:
320
- ocm_clusters = [c.dict(by_alias=True) for c in clusters if c.ocm]
321
+ ocm_clusters = [c.model_dump(by_alias=True) for c in clusters if c.ocm]
321
322
  return (
322
323
  OCMMap(
323
324
  clusters=ocm_clusters,
324
325
  integration=QONTRACT_INTEGRATION,
325
- settings=vault_settings.dict(by_alias=True),
326
+ settings=vault_settings.model_dump(by_alias=True),
326
327
  )
327
328
  if ocm_clusters
328
329
  # this is a case for an OCP cluster which is not provisioned
@@ -346,7 +347,7 @@ def _populate_tgw_attachments_working_dirs(
346
347
  accounts_by_infra_account_name: dict[str, list[dict[str, Any]]] = {}
347
348
  for item in desired_state:
348
349
  accounts_by_infra_account_name.setdefault(item.infra_acount_name, []).append(
349
- item.accepter.account.dict(by_alias=True)
350
+ item.accepter.account.model_dump(by_alias=True)
350
351
  )
351
352
  for infra_account_name, accounts in accounts_by_infra_account_name.items():
352
353
  ts.populate_additional_providers(infra_account_name, accounts)
@@ -428,7 +429,9 @@ def setup(
428
429
  print_to_file: str | None = None,
429
430
  ) -> tuple[SecretReaderBase, AWSApi, Terraform, Terrascript]:
430
431
  tgw_clusters = desired_state_data_source.clusters
431
- all_accounts = [a.dict(by_alias=True) for a in desired_state_data_source.accounts]
432
+ all_accounts = [
433
+ a.model_dump(by_alias=True) for a in desired_state_data_source.accounts
434
+ ]
432
435
  account_by_name = {a["name"]: a for a in all_accounts}
433
436
  vault_settings = get_app_interface_vault_settings()
434
437
  secret_reader = create_secret_reader(vault_settings.vault)
@@ -444,13 +447,18 @@ def setup(
444
447
  raise RuntimeError("Could not find VPC ID for cluster")
445
448
 
446
449
  _validate_tgw_connection_names(desired_state)
447
-
450
+ try:
451
+ default_tags = get_settings().default_tags
452
+ except ValueError:
453
+ # no external resources settings found
454
+ default_tags = None
448
455
  ts = Terrascript(
449
456
  QONTRACT_INTEGRATION,
450
457
  "",
451
458
  thread_pool_size,
452
459
  tgw_accounts,
453
- settings=vault_settings.dict(by_alias=True),
460
+ settings=vault_settings.model_dump(by_alias=True),
461
+ default_tags=default_tags,
454
462
  )
455
463
  tgw_rosa_cluster_accounts = [
456
464
  account_by_name[c.spec.account.name]
@@ -510,7 +518,7 @@ def run(
510
518
  ) -> None:
511
519
  desired_state_data_source = _fetch_desired_state_data_source(account_name)
512
520
  tgw_accounts = [
513
- a.dict(by_alias=True)
521
+ a.model_dump(by_alias=True)
514
522
  for a in _filter_tgw_accounts(
515
523
  desired_state_data_source.accounts, desired_state_data_source.clusters
516
524
  )
@@ -567,7 +575,7 @@ def early_exit_desired_state(*args: Any, **kwargs: Any) -> dict[str, Any]:
567
575
  desired_state = _fetch_desired_state_data_source()
568
576
  for a in desired_state.accounts:
569
577
  a.deletion_approvals = []
570
- return desired_state.dict(by_alias=True)
578
+ return desired_state.model_dump(by_alias=True)
571
579
 
572
580
 
573
581
  def desired_state_shard_config() -> DesiredStateShardConfig:
@@ -11,6 +11,7 @@ from reconcile import (
11
11
  )
12
12
  from reconcile.change_owners.diff import IDENTIFIER_FIELD_NAME
13
13
  from reconcile.gql_definitions.common.pgp_reencryption_settings import query
14
+ from reconcile.typed_queries.external_resources import get_settings
14
15
  from reconcile.utils import (
15
16
  expiration,
16
17
  gql,
@@ -126,12 +127,18 @@ def setup(
126
127
  participating_aws_accounts = _filter_participating_aws_accounts(accounts, roles)
127
128
 
128
129
  settings = queries.get_app_interface_settings()
130
+ try:
131
+ default_tags = get_settings().default_tags
132
+ except ValueError:
133
+ # no external resources settings found
134
+ default_tags = None
129
135
  ts = Terrascript(
130
136
  QONTRACT_INTEGRATION,
131
137
  QONTRACT_TF_PREFIX,
132
138
  thread_pool_size,
133
139
  participating_aws_accounts,
134
140
  settings=settings,
141
+ default_tags=default_tags,
135
142
  )
136
143
  err = ts.populate_users(
137
144
  roles,
@@ -7,6 +7,7 @@ from typing import Any, TypedDict
7
7
  import reconcile.utils.terraform_client as terraform
8
8
  import reconcile.utils.terrascript_aws_client as terrascript
9
9
  from reconcile import queries
10
+ from reconcile.typed_queries import external_resources
10
11
  from reconcile.utils import (
11
12
  aws_api,
12
13
  ocm,
@@ -654,8 +655,18 @@ def run(
654
655
  ])
655
656
 
656
657
  account_by_name = {a["name"]: a for a in accounts}
658
+ try:
659
+ default_tags = external_resources.get_settings().default_tags
660
+ except ValueError:
661
+ # no external resources settings found
662
+ default_tags = None
657
663
  with terrascript.TerrascriptClient(
658
- QONTRACT_INTEGRATION, "", thread_pool_size, infra_accounts, settings=settings
664
+ QONTRACT_INTEGRATION,
665
+ "",
666
+ thread_pool_size,
667
+ infra_accounts,
668
+ settings=settings,
669
+ default_tags=default_tags,
659
670
  ) as ts:
660
671
  rosa_cluster_accounts = [
661
672
  account_by_name[c["spec"]["account"]["name"]]
@@ -664,8 +675,8 @@ def run(
664
675
  ]
665
676
  ts.populate_configs(rosa_cluster_accounts)
666
677
 
667
- for infra_account_name, items in participating_accounts.items():
668
- ts.populate_additional_providers(infra_account_name, items)
678
+ for infra_account_name, accounts in participating_accounts.items():
679
+ ts.populate_additional_providers(infra_account_name, accounts)
669
680
  ts.populate_vpc_peerings(desired_state)
670
681
  working_dirs = ts.dump(print_to_file=print_to_file)
671
682
  terraform_configurations = ts.terraform_configurations()
@@ -20,6 +20,7 @@ from reconcile.typed_queries.app_interface_vault_settings import (
20
20
  get_app_interface_vault_settings,
21
21
  )
22
22
  from reconcile.typed_queries.aws_vpc_requests import get_aws_vpc_requests
23
+ from reconcile.typed_queries.external_resources import get_settings
23
24
  from reconcile.typed_queries.github_orgs import get_github_orgs
24
25
  from reconcile.typed_queries.gitlab_instances import get_gitlab_instances
25
26
  from reconcile.utils import gql
@@ -161,13 +162,21 @@ class TerraformVpcResources(QontractReconcileIntegration[TerraformVpcResourcesPa
161
162
  logging.debug("No VPC requests found, nothing to do.")
162
163
  sys.exit(ExitCodes.SUCCESS)
163
164
 
164
- accounts_untyped: list[dict] = [acc.dict(by_alias=True) for acc in accounts]
165
+ accounts_untyped: list[dict] = [
166
+ acc.model_dump(by_alias=True) for acc in accounts
167
+ ]
168
+ try:
169
+ default_tags = get_settings().default_tags
170
+ except ValueError:
171
+ # no external resources settings found
172
+ default_tags = None
165
173
  with TerrascriptClient(
166
174
  integration=QONTRACT_INTEGRATION,
167
175
  integration_prefix=QONTRACT_TF_PREFIX,
168
176
  thread_pool_size=thread_pool_size,
169
177
  accounts=accounts_untyped,
170
178
  secret_reader=secret_reader,
179
+ default_tags=default_tags,
171
180
  ) as ts_client:
172
181
  ts_client.populate_vpc_requests(data, AWS_PROVIDER_VERSION)
173
182
 
@@ -0,0 +1,41 @@
1
+ import json
2
+ from collections.abc import Mapping
3
+
4
+ from reconcile.gql_definitions.fragments.aws_organization import (
5
+ AWSOrganization,
6
+ )
7
+
8
+
9
+ def get_aws_account_tags(
10
+ organization: AWSOrganization | Mapping | None,
11
+ ) -> dict[str, str]:
12
+ """
13
+ Get AWS account tags by merging payer account tags
14
+
15
+ Args:
16
+ organization: AWSOrganization | Mapping | None - The organization object from which to extract tags.
17
+
18
+ Returns:
19
+ dict[str, str]: A dictionary containing the merged tags from the payer account and the account itself.
20
+ """
21
+ if organization is None:
22
+ return {}
23
+
24
+ match organization:
25
+ case AWSOrganization():
26
+ payer_account_tags = (
27
+ organization.payer_account.organization_account_tags or {}
28
+ )
29
+ account_tags = organization.tags or {}
30
+ case Mapping():
31
+ payer_account_tags = organization.get("payerAccount", {}).get(
32
+ "organizationAccountTags", {}
33
+ )
34
+ if isinstance(payer_account_tags, str):
35
+ payer_account_tags = json.loads(payer_account_tags)
36
+
37
+ account_tags = organization.get("tags", {})
38
+ if isinstance(account_tags, str):
39
+ account_tags = json.loads(account_tags)
40
+
41
+ return payer_account_tags | account_tags
@@ -6,7 +6,7 @@ from reconcile.utils.gql import GqlApi
6
6
 
7
7
  class App(BaseModel):
8
8
  name: str
9
- parent_app_name: str | None
9
+ parent_app_name: str | None = None
10
10
 
11
11
 
12
12
  def get_app_names(
@@ -13,7 +13,7 @@ class CostNamespace(BaseModel, frozen=True):
13
13
  labels: CostNamespaceLabels
14
14
  app_name: str
15
15
  cluster_name: str
16
- cluster_external_id: str | None
16
+ cluster_external_id: str | None = None
17
17
 
18
18
 
19
19
  def get_cost_namespaces(
@@ -32,7 +32,7 @@ def get_cost_namespaces(
32
32
  return [
33
33
  CostNamespace(
34
34
  name=namespace.name,
35
- labels=CostNamespaceLabels.parse_obj(namespace.labels or {}),
35
+ labels=CostNamespaceLabels.model_validate(namespace.labels or {}),
36
36
  app_name=namespace.app.name,
37
37
  cluster_name=namespace.cluster.name,
38
38
  cluster_external_id=namespace.cluster.spec.external_id
@@ -6,7 +6,6 @@ from typing import Any
6
6
  from jsonpath_ng.exceptions import JsonPathParserError
7
7
  from pydantic import (
8
8
  BaseModel,
9
- Extra,
10
9
  Field,
11
10
  Json,
12
11
  )
@@ -51,7 +50,12 @@ from reconcile.utils.json import json_dumps
51
50
  from reconcile.utils.jsonpath import parse_jsonpath
52
51
 
53
52
 
54
- class SaasResourceTemplateTarget(ConfiguredBaseModel):
53
+ class SaasResourceTemplateTarget(
54
+ ConfiguredBaseModel,
55
+ validate_by_alias=True,
56
+ # ignore `namespaceSelector` and 'provider' fields from the GQL schema
57
+ extra="ignore",
58
+ ):
55
59
  path: str | None = Field(..., alias="path")
56
60
  name: str | None = Field(..., alias="name")
57
61
  # the namespace must be required to fulfill the saas file schema (utils.saasherder.interface.SaasFile)
@@ -79,12 +83,8 @@ class SaasResourceTemplateTarget(ConfiguredBaseModel):
79
83
  digest_size=20,
80
84
  ).hexdigest()
81
85
 
82
- class Config:
83
- # ignore `namespaceSelector` and 'provider' fields from the GQL schema
84
- extra = Extra.ignore
85
-
86
86
 
87
- class SaasResourceTemplate(ConfiguredBaseModel):
87
+ class SaasResourceTemplate(ConfiguredBaseModel, validate_by_alias=True):
88
88
  name: str = Field(..., alias="name")
89
89
  url: str = Field(..., alias="url")
90
90
  path: str = Field(..., alias="path")
@@ -97,7 +97,7 @@ class SaasResourceTemplate(ConfiguredBaseModel):
97
97
  targets: list[SaasResourceTemplateTarget] = Field(..., alias="targets")
98
98
 
99
99
 
100
- class SaasFile(ConfiguredBaseModel):
100
+ class SaasFile(ConfiguredBaseModel, validate_by_alias=True):
101
101
  path: str = Field(..., alias="path")
102
102
  name: str = Field(..., alias="name")
103
103
  labels: Json | None = Field(..., alias="labels")
@@ -221,7 +221,7 @@ class SaasFileList:
221
221
  with self._namespaces_as_dict_lock:
222
222
  self._namespaces_as_dict_cache = {
223
223
  "namespace": [
224
- ns.dict(by_alias=True, exclude_none=True)
224
+ ns.model_dump(by_alias=True, exclude_none=True)
225
225
  for ns in self.namespaces
226
226
  ]
227
227
  }
@@ -283,7 +283,7 @@ class SaasFileList:
283
283
  if app_name and saas_file.app.name != app_name:
284
284
  continue
285
285
 
286
- sf = saas_file.copy(deep=True)
286
+ sf = saas_file.model_copy(deep=True)
287
287
  if env_name:
288
288
  for rt in sf.resource_templates[:]:
289
289
  for target in rt.targets[:]:
@@ -314,7 +314,7 @@ def convert_parameters_to_json_string(root: dict[str, Any]) -> dict[str, Any]:
314
314
 
315
315
 
316
316
  def export_model(model: BaseModel) -> dict[str, Any]:
317
- return convert_parameters_to_json_string(model.dict(by_alias=True))
317
+ return convert_parameters_to_json_string(model.model_dump(by_alias=True))
318
318
 
319
319
 
320
320
  def get_saas_files(
@@ -32,7 +32,7 @@ def get_selected_app_names(
32
32
  prefix = f"{namespace.app.parent_app.name}-"
33
33
  name = f"{prefix}{namespace.app.name}"
34
34
  selected_app_names.add(name)
35
- app = namespace.app.dict(by_alias=True)
35
+ app = namespace.app.model_dump(by_alias=True)
36
36
  app["name"] = name
37
37
  apps["apps"].append(app)
38
38
 
@@ -40,7 +40,7 @@ def get_selected_app_names(
40
40
  name = f"{namespace.app.name}-{child.name}"
41
41
  if name not in selected_app_names:
42
42
  selected_app_names.add(f"{namespace.app.name}-{child.name}")
43
- child_dict = child.dict(by_alias=True)
43
+ child_dict = child.model_dump(by_alias=True)
44
44
  child_dict["name"] = name
45
45
  apps["apps"].append(child_dict)
46
46
 
@@ -31,7 +31,7 @@ QONTRACT_INTEGRATION_VERSION = make_semver(1, 0, 0)
31
31
 
32
32
 
33
33
  class UnleashTogglesIntegrationParams(PydanticRunParams):
34
- instance: str | None
34
+ instance: str | None = None
35
35
 
36
36
 
37
37
  def feature_toggle_equal(c: FeatureToggle, d: FeatureToggleUnleashV1) -> bool:
@@ -68,7 +68,9 @@ class UnleashTogglesIntegration(
68
68
  if not query_func:
69
69
  query_func = gql.get_api().query
70
70
  return {
71
- "toggles": [ft.dict() for ft in self.get_unleash_instances(query_func)],
71
+ "toggles": [
72
+ ft.model_dump() for ft in self.get_unleash_instances(query_func)
73
+ ],
72
74
  }
73
75
 
74
76
  def get_unleash_instances(
@@ -6,7 +6,7 @@ from typing import (
6
6
  )
7
7
 
8
8
  import requests
9
- from pydantic import BaseModel
9
+ from pydantic import BaseModel, ConfigDict
10
10
 
11
11
  from reconcile.gql_definitions.acs.acs_instances import AcsInstanceV1
12
12
  from reconcile.gql_definitions.acs.acs_instances import query as acs_instances_query
@@ -19,8 +19,11 @@ class AcsBaseApi(BaseModel):
19
19
  timeout: int = 30
20
20
  session: requests.Session = requests.Session()
21
21
 
22
- class Config:
23
- arbitrary_types_allowed = True
22
+ model_config = ConfigDict(
23
+ validate_by_name=True,
24
+ validate_by_alias=True,
25
+ arbitrary_types_allowed=True,
26
+ )
24
27
 
25
28
  def __enter__(self) -> Self:
26
29
  return self
@@ -11,7 +11,7 @@ class Scope(BaseModel):
11
11
  """
12
12
 
13
13
  cluster: str
14
- namespace: str | None
14
+ namespace: str | None = None
15
15
 
16
16
 
17
17
  class PolicyCondition(BaseModel):
@@ -23,7 +23,7 @@ class PolicyCondition(BaseModel):
23
23
  """
24
24
 
25
25
  field_name: str
26
- negate: bool | None
26
+ negate: bool | None = None
27
27
  values: list[str]
28
28
 
29
29
 
@@ -3,7 +3,6 @@ from __future__ import annotations
3
3
  import logging
4
4
  import operator
5
5
  import os
6
- import re
7
6
  from functools import lru_cache
8
7
  from threading import Lock
9
8
  from typing import (
@@ -25,6 +24,7 @@ import reconcile.utils.lean_terraform_client as terraform
25
24
  from reconcile.utils.secret_reader import SecretReader, SecretReaderBase
26
25
 
27
26
  if TYPE_CHECKING:
27
+ import re
28
28
  from collections.abc import (
29
29
  Iterable,
30
30
  Iterator,
@@ -1074,28 +1074,40 @@ class AWSApi:
1074
1074
  return [rt["RouteTableId"] for rt in vpc_route_tables]
1075
1075
 
1076
1076
  @staticmethod
1077
- def _filter_amis(
1078
- images: Iterable[ImageTypeDef], regex: str
1079
- ) -> list[dict[str, Any]]:
1080
- results = []
1081
- pattern = re.compile(regex)
1082
- for i in images:
1083
- if not re.search(pattern, i["Name"]):
1084
- continue
1085
- if i["State"] != "available":
1086
- continue
1087
- item = {"image_id": i["ImageId"], "tags": i.get("Tags", [])}
1088
- results.append(item)
1077
+ def normalize_tags(tags: Iterable[TagTypeDef]) -> dict[str, str]:
1078
+ return {tag["Key"]: tag["Value"] for tag in tags}
1089
1079
 
1090
- return results
1080
+ @staticmethod
1081
+ def _filter_amis(
1082
+ images: Iterable[ImageTypeDef],
1083
+ regex: re.Pattern,
1084
+ ) -> dict[str, dict[str, str]]:
1085
+ return {
1086
+ image["ImageId"]: AWSApi.normalize_tags(image.get("Tags", []))
1087
+ for image in images
1088
+ if regex.search(image["Name"]) and image["State"] == "available"
1089
+ }
1091
1090
 
1092
1091
  def get_amis_details(
1093
1092
  self,
1094
1093
  account: Mapping[str, Any],
1095
1094
  owner_account: Mapping[str, Any],
1096
- regex: str,
1095
+ regex: re.Pattern,
1097
1096
  region: str | None = None,
1098
- ) -> list[dict[str, Any]]:
1097
+ ) -> dict[str, dict[str, str]]:
1098
+ """
1099
+ Get AMI details for an account, find AMI name matches regex and state is available.
1100
+ Return ImageId and normalized tags.
1101
+
1102
+ Args:
1103
+ account: AWS account
1104
+ owner_account: AMI owner AWS account uid
1105
+ regex: regex to filter AMI name
1106
+ region: AWS account region
1107
+
1108
+ Returns:
1109
+ dict[str, dict[str, str]]: Key is AMI ImageId, value is AMI normalized tags.
1110
+ """
1099
1111
  ec2 = self._account_ec2_client(account["name"], region_name=region)
1100
1112
  images = self.get_account_amis(ec2, owner=owner_account["uid"])
1101
1113
  return self._filter_amis(images, regex)
@@ -1175,12 +1187,31 @@ class AWSApi:
1175
1187
  client = self._account_cloudwatch_client(account_name, region_name=region_name)
1176
1188
  client.delete_log_group(logGroupName=group_name)
1177
1189
 
1178
- def create_tag(
1179
- self, account: Mapping[str, Any], resource_id: str, tag: Mapping[str, str]
1190
+ def create_tags(
1191
+ self,
1192
+ account: Mapping[str, Any],
1193
+ resource_id: str,
1194
+ tags: Mapping[str, str],
1180
1195
  ) -> None:
1196
+ """
1197
+ Create tags on EC2 resources (AMI)
1198
+
1199
+ Args:
1200
+ account: AWS account
1201
+ resource_id: AWS resource id
1202
+ tags: tags to update
1203
+
1204
+ Returns:
1205
+ None
1206
+ """
1181
1207
  ec2 = self._account_ec2_client(account["name"])
1182
- tag_type_def: TagTypeDef = {"Key": tag["Key"], "Value": tag["Value"]}
1183
- ec2.create_tags(Resources=[resource_id], Tags=[tag_type_def])
1208
+ formatted_tags: list[TagTypeDef] = [
1209
+ {"Key": k, "Value": v} for k, v in tags.items()
1210
+ ]
1211
+ ec2.create_tags(
1212
+ Resources=[resource_id],
1213
+ Tags=formatted_tags,
1214
+ )
1184
1215
 
1185
1216
  def get_alb_network_interface_ips(
1186
1217
  self, account: awsh.Account, service_name: str
@@ -6,9 +6,11 @@ from functools import cached_property
6
6
  from typing import TYPE_CHECKING, Any, TypeVar
7
7
 
8
8
  from boto3 import Session
9
+ from botocore.config import Config
9
10
  from pydantic import BaseModel
10
11
 
11
12
  import reconcile.utils.aws_api_typed.account
13
+ import reconcile.utils.aws_api_typed.cloudformation
12
14
  import reconcile.utils.aws_api_typed.dynamodb
13
15
  import reconcile.utils.aws_api_typed.iam
14
16
  import reconcile.utils.aws_api_typed.organization
@@ -17,8 +19,10 @@ import reconcile.utils.aws_api_typed.service_quotas
17
19
  import reconcile.utils.aws_api_typed.sts
18
20
  import reconcile.utils.aws_api_typed.support
19
21
  from reconcile.utils.aws_api_typed.account import AWSApiAccount
22
+ from reconcile.utils.aws_api_typed.cloudformation import AWSApiCloudFormation
20
23
  from reconcile.utils.aws_api_typed.dynamodb import AWSApiDynamoDB
21
24
  from reconcile.utils.aws_api_typed.iam import AWSApiIam
25
+ from reconcile.utils.aws_api_typed.logs import AWSApiLogs
22
26
  from reconcile.utils.aws_api_typed.organization import AWSApiOrganizations
23
27
  from reconcile.utils.aws_api_typed.s3 import AWSApiS3
24
28
  from reconcile.utils.aws_api_typed.service_quotas import AWSApiServiceQuotas
@@ -31,7 +35,9 @@ if TYPE_CHECKING:
31
35
  SubApi = TypeVar(
32
36
  "SubApi",
33
37
  AWSApiAccount,
38
+ AWSApiCloudFormation,
34
39
  AWSApiDynamoDB,
40
+ AWSApiLogs,
35
41
  AWSApiIam,
36
42
  AWSApiOrganizations,
37
43
  AWSApiS3,
@@ -40,6 +46,13 @@ SubApi = TypeVar(
40
46
  AWSApiSupport,
41
47
  )
42
48
 
49
+ DEFAULT_CONFIG = Config(
50
+ retries={
51
+ "mode": "standard",
52
+ "total_max_attempts": 10,
53
+ },
54
+ )
55
+
43
56
 
44
57
  class AWSCredentials(ABC):
45
58
  @abstractmethod
@@ -174,28 +187,34 @@ class AWSApi:
174
187
  """Return a new or cached sub api client."""
175
188
  match api_cls:
176
189
  case reconcile.utils.aws_api_typed.account.AWSApiAccount:
177
- client = self.session.client("account")
190
+ client = self.session.client("account", config=DEFAULT_CONFIG)
191
+ api = api_cls(client)
192
+ case reconcile.utils.aws_api_typed.cloudformation.AWSApiCloudFormation:
193
+ client = self.session.client("cloudformation", config=DEFAULT_CONFIG)
178
194
  api = api_cls(client)
179
195
  case reconcile.utils.aws_api_typed.dynamodb.AWSApiDynamoDB:
180
- client = self.session.client("dynamodb")
196
+ client = self.session.client("dynamodb", config=DEFAULT_CONFIG)
181
197
  api = api_cls(client)
182
198
  case reconcile.utils.aws_api_typed.iam.AWSApiIam:
183
- client = self.session.client("iam")
199
+ client = self.session.client("iam", config=DEFAULT_CONFIG)
200
+ api = api_cls(client)
201
+ case reconcile.utils.aws_api_typed.logs.AWSApiLogs:
202
+ client = self.session.client("logs", config=DEFAULT_CONFIG)
184
203
  api = api_cls(client)
185
204
  case reconcile.utils.aws_api_typed.organization.AWSApiOrganizations:
186
- client = self.session.client("organizations")
205
+ client = self.session.client("organizations", config=DEFAULT_CONFIG)
187
206
  api = api_cls(client)
188
207
  case reconcile.utils.aws_api_typed.s3.AWSApiS3:
189
- client = self.session.client("s3")
208
+ client = self.session.client("s3", config=DEFAULT_CONFIG)
190
209
  api = api_cls(client)
191
210
  case reconcile.utils.aws_api_typed.service_quotas.AWSApiServiceQuotas:
192
- client = self.session.client("service-quotas")
211
+ client = self.session.client("service-quotas", config=DEFAULT_CONFIG)
193
212
  api = api_cls(client)
194
213
  case reconcile.utils.aws_api_typed.sts.AWSApiSts:
195
- client = self.session.client("sts")
214
+ client = self.session.client("sts", config=DEFAULT_CONFIG)
196
215
  api = api_cls(client)
197
216
  case reconcile.utils.aws_api_typed.support.AWSApiSupport:
198
- client = self.session.client("support")
217
+ client = self.session.client("support", config=DEFAULT_CONFIG)
199
218
  api = api_cls(client)
200
219
  case _:
201
220
  raise ValueError(f"Unknown API class: {api_cls}")
@@ -205,9 +224,14 @@ class AWSApi:
205
224
 
206
225
  @cached_property
207
226
  def account(self) -> AWSApiAccount:
208
- """Return an AWS Acount Api client"""
227
+ """Return an AWS Account Api client"""
209
228
  return self._init_sub_api(AWSApiAccount)
210
229
 
230
+ @cached_property
231
+ def cloudformation(self) -> AWSApiCloudFormation:
232
+ """Return an AWS CloudFormation Api client"""
233
+ return self._init_sub_api(AWSApiCloudFormation)
234
+
211
235
  @cached_property
212
236
  def dynamodb(self) -> AWSApiDynamoDB:
213
237
  """Return an AWS DynamoDB Api client"""
@@ -218,6 +242,11 @@ class AWSApi:
218
242
  """Return an AWS IAM Api client."""
219
243
  return self._init_sub_api(AWSApiIam)
220
244
 
245
+ @cached_property
246
+ def logs(self) -> AWSApiLogs:
247
+ """Return an AWS Logs Api client."""
248
+ return self._init_sub_api(AWSApiLogs)
249
+
221
250
  @cached_property
222
251
  def organizations(self) -> AWSApiOrganizations:
223
252
  """Return an AWS Organizations Api client."""