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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (356) hide show
  1. {qontract_reconcile-0.10.2.dev349.dist-info → qontract_reconcile-0.10.2.dev414.dist-info}/METADATA +12 -11
  2. {qontract_reconcile-0.10.2.dev349.dist-info → qontract_reconcile-0.10.2.dev414.dist-info}/RECORD +356 -350
  3. reconcile/acs_rbac.py +2 -2
  4. reconcile/aus/advanced_upgrade_service.py +15 -12
  5. reconcile/aus/base.py +26 -27
  6. reconcile/aus/cluster_version_data.py +15 -5
  7. reconcile/aus/models.py +1 -1
  8. reconcile/automated_actions/config/integration.py +15 -3
  9. reconcile/aws_account_manager/integration.py +8 -8
  10. reconcile/aws_account_manager/reconciler.py +3 -3
  11. reconcile/aws_ami_cleanup/integration.py +8 -12
  12. reconcile/aws_ami_share.py +69 -62
  13. reconcile/aws_cloudwatch_log_retention/integration.py +155 -126
  14. reconcile/aws_ecr_image_pull_secrets.py +2 -2
  15. reconcile/aws_iam_keys.py +7 -41
  16. reconcile/aws_saml_idp/integration.py +12 -4
  17. reconcile/aws_saml_roles/integration.py +32 -25
  18. reconcile/aws_version_sync/integration.py +6 -12
  19. reconcile/change_owners/bundle.py +3 -3
  20. reconcile/change_owners/change_log_tracking.py +3 -2
  21. reconcile/change_owners/change_owners.py +1 -1
  22. reconcile/change_owners/diff.py +2 -4
  23. reconcile/checkpoint.py +11 -3
  24. reconcile/cli.py +33 -8
  25. reconcile/dashdotdb_dora.py +5 -12
  26. reconcile/dashdotdb_slo.py +1 -1
  27. reconcile/database_access_manager.py +123 -117
  28. reconcile/dynatrace_token_provider/integration.py +1 -1
  29. reconcile/endpoints_discovery/integration.py +4 -1
  30. reconcile/endpoints_discovery/merge_request.py +1 -1
  31. reconcile/endpoints_discovery/merge_request_manager.py +9 -11
  32. reconcile/external_resources/factories.py +5 -12
  33. reconcile/external_resources/integration.py +1 -1
  34. reconcile/external_resources/manager.py +24 -10
  35. reconcile/external_resources/meta.py +0 -1
  36. reconcile/external_resources/metrics.py +1 -1
  37. reconcile/external_resources/model.py +13 -13
  38. reconcile/external_resources/reconciler.py +7 -4
  39. reconcile/external_resources/secrets_sync.py +6 -8
  40. reconcile/external_resources/state.py +60 -17
  41. reconcile/fleet_labeler/integration.py +1 -1
  42. reconcile/gabi_authorized_users.py +8 -5
  43. reconcile/gcp_image_mirror.py +2 -2
  44. reconcile/github_org.py +1 -1
  45. reconcile/github_owners.py +4 -0
  46. reconcile/gitlab_housekeeping.py +13 -15
  47. reconcile/gitlab_members.py +6 -12
  48. reconcile/gitlab_mr_sqs_consumer.py +2 -2
  49. reconcile/gitlab_owners.py +15 -11
  50. reconcile/gitlab_permissions.py +8 -12
  51. reconcile/glitchtip_project_alerts/integration.py +3 -1
  52. reconcile/gql_definitions/acs/acs_instances.py +5 -5
  53. reconcile/gql_definitions/acs/acs_policies.py +5 -5
  54. reconcile/gql_definitions/acs/acs_rbac.py +5 -5
  55. reconcile/gql_definitions/advanced_upgrade_service/aus_clusters.py +5 -5
  56. reconcile/gql_definitions/advanced_upgrade_service/aus_organization.py +5 -5
  57. reconcile/gql_definitions/app_interface_metrics_exporter/onboarding_status.py +5 -5
  58. reconcile/gql_definitions/app_sre_tekton_access_revalidation/roles.py +5 -5
  59. reconcile/gql_definitions/app_sre_tekton_access_revalidation/users.py +5 -5
  60. reconcile/gql_definitions/automated_actions/instance.py +46 -7
  61. reconcile/gql_definitions/aws_account_manager/aws_accounts.py +5 -5
  62. reconcile/gql_definitions/aws_ami_cleanup/aws_accounts.py +15 -5
  63. reconcile/gql_definitions/aws_cloudwatch_log_retention/aws_accounts.py +27 -66
  64. reconcile/gql_definitions/aws_saml_idp/aws_accounts.py +15 -5
  65. reconcile/gql_definitions/aws_saml_roles/aws_accounts.py +15 -5
  66. reconcile/gql_definitions/aws_saml_roles/roles.py +5 -5
  67. reconcile/gql_definitions/aws_version_sync/clusters.py +5 -5
  68. reconcile/gql_definitions/aws_version_sync/namespaces.py +5 -5
  69. reconcile/gql_definitions/change_owners/queries/change_types.py +5 -5
  70. reconcile/gql_definitions/change_owners/queries/self_service_roles.py +5 -5
  71. reconcile/gql_definitions/cluster_auth_rhidp/clusters.py +5 -5
  72. reconcile/gql_definitions/common/alerting_services_settings.py +5 -5
  73. reconcile/gql_definitions/common/app_code_component_repos.py +5 -5
  74. reconcile/gql_definitions/common/app_interface_custom_messages.py +5 -5
  75. reconcile/gql_definitions/common/app_interface_dms_settings.py +5 -5
  76. reconcile/gql_definitions/common/app_interface_repo_settings.py +5 -5
  77. reconcile/gql_definitions/common/app_interface_roles.py +5 -5
  78. reconcile/gql_definitions/common/app_interface_state_settings.py +5 -5
  79. reconcile/gql_definitions/common/app_interface_vault_settings.py +5 -5
  80. reconcile/gql_definitions/common/app_quay_repos_escalation_policies.py +5 -5
  81. reconcile/gql_definitions/common/apps.py +5 -5
  82. reconcile/gql_definitions/common/aws_vpc_requests.py +15 -5
  83. reconcile/gql_definitions/common/aws_vpcs.py +5 -5
  84. reconcile/gql_definitions/common/clusters.py +7 -5
  85. reconcile/gql_definitions/common/clusters_minimal.py +5 -5
  86. reconcile/gql_definitions/common/clusters_with_dms.py +5 -5
  87. reconcile/gql_definitions/common/clusters_with_peering.py +5 -5
  88. reconcile/gql_definitions/common/github_orgs.py +5 -5
  89. reconcile/gql_definitions/common/jira_settings.py +5 -5
  90. reconcile/gql_definitions/common/jiralert_settings.py +5 -5
  91. reconcile/gql_definitions/common/ldap_settings.py +5 -5
  92. reconcile/gql_definitions/common/namespaces.py +5 -5
  93. reconcile/gql_definitions/common/namespaces_minimal.py +7 -5
  94. reconcile/gql_definitions/common/ocm_env_telemeter.py +5 -5
  95. reconcile/gql_definitions/common/ocm_environments.py +5 -5
  96. reconcile/gql_definitions/common/pagerduty_instances.py +5 -5
  97. reconcile/gql_definitions/common/pgp_reencryption_settings.py +5 -5
  98. reconcile/gql_definitions/common/pipeline_providers.py +5 -5
  99. reconcile/gql_definitions/common/quay_instances.py +5 -5
  100. reconcile/gql_definitions/common/quay_orgs.py +5 -5
  101. reconcile/gql_definitions/common/reserved_networks.py +5 -5
  102. reconcile/gql_definitions/common/rhcs_provider_settings.py +5 -5
  103. reconcile/gql_definitions/common/saas_files.py +5 -5
  104. reconcile/gql_definitions/common/saas_target_namespaces.py +5 -5
  105. reconcile/gql_definitions/common/saasherder_settings.py +5 -5
  106. reconcile/gql_definitions/common/slack_workspaces.py +5 -5
  107. reconcile/gql_definitions/common/smtp_client_settings.py +5 -5
  108. reconcile/gql_definitions/common/state_aws_account.py +5 -5
  109. reconcile/gql_definitions/common/users.py +5 -5
  110. reconcile/gql_definitions/common/users_with_paths.py +5 -5
  111. reconcile/gql_definitions/cost_report/app_names.py +5 -5
  112. reconcile/gql_definitions/cost_report/cost_namespaces.py +5 -5
  113. reconcile/gql_definitions/cost_report/settings.py +5 -5
  114. reconcile/gql_definitions/dashdotdb_slo/slo_documents_query.py +5 -5
  115. reconcile/gql_definitions/dynatrace_token_provider/dynatrace_bootstrap_tokens.py +5 -5
  116. reconcile/gql_definitions/dynatrace_token_provider/token_specs.py +5 -5
  117. reconcile/gql_definitions/email_sender/apps.py +5 -5
  118. reconcile/gql_definitions/email_sender/emails.py +5 -5
  119. reconcile/gql_definitions/email_sender/users.py +5 -5
  120. reconcile/gql_definitions/endpoints_discovery/apps.py +5 -5
  121. reconcile/gql_definitions/external_resources/aws_accounts.py +5 -5
  122. reconcile/gql_definitions/external_resources/external_resources_modules.py +5 -5
  123. reconcile/gql_definitions/external_resources/external_resources_namespaces.py +89 -6
  124. reconcile/gql_definitions/external_resources/external_resources_settings.py +7 -5
  125. reconcile/gql_definitions/external_resources/fragments/external_resources_module_overrides.py +5 -5
  126. reconcile/gql_definitions/fleet_labeler/fleet_labels.py +5 -5
  127. reconcile/gql_definitions/fragments/aus_organization.py +5 -5
  128. reconcile/gql_definitions/fragments/aws_account_common.py +7 -5
  129. reconcile/gql_definitions/fragments/aws_account_managed.py +5 -5
  130. reconcile/gql_definitions/fragments/aws_account_sso.py +5 -5
  131. reconcile/gql_definitions/fragments/aws_infra_management_account.py +5 -5
  132. reconcile/gql_definitions/fragments/aws_organization.py +33 -0
  133. reconcile/gql_definitions/fragments/aws_vpc.py +5 -5
  134. reconcile/gql_definitions/fragments/aws_vpc_request.py +7 -5
  135. reconcile/gql_definitions/fragments/container_image_mirror.py +5 -5
  136. reconcile/gql_definitions/fragments/deploy_resources.py +5 -5
  137. reconcile/gql_definitions/fragments/disable.py +5 -5
  138. reconcile/gql_definitions/fragments/email_service.py +5 -5
  139. reconcile/gql_definitions/fragments/email_user.py +5 -5
  140. reconcile/gql_definitions/fragments/jumphost_common_fields.py +5 -5
  141. reconcile/gql_definitions/fragments/membership_source.py +5 -5
  142. reconcile/gql_definitions/fragments/minimal_ocm_organization.py +5 -5
  143. reconcile/gql_definitions/fragments/oc_connection_cluster.py +5 -5
  144. reconcile/gql_definitions/fragments/ocm_environment.py +5 -5
  145. reconcile/gql_definitions/fragments/pipeline_provider_retention.py +5 -5
  146. reconcile/gql_definitions/fragments/prometheus_instance.py +5 -5
  147. reconcile/gql_definitions/fragments/resource_limits_requirements.py +5 -5
  148. reconcile/gql_definitions/fragments/resource_requests_requirements.py +5 -5
  149. reconcile/gql_definitions/fragments/resource_values.py +5 -5
  150. reconcile/gql_definitions/fragments/saas_slo_document.py +5 -5
  151. reconcile/gql_definitions/fragments/saas_target_namespace.py +5 -5
  152. reconcile/gql_definitions/fragments/serviceaccount_token.py +5 -5
  153. reconcile/gql_definitions/fragments/terraform_state.py +5 -5
  154. reconcile/gql_definitions/fragments/upgrade_policy.py +5 -5
  155. reconcile/gql_definitions/fragments/user.py +5 -5
  156. reconcile/gql_definitions/fragments/vault_secret.py +5 -5
  157. reconcile/gql_definitions/gcp/gcp_docker_repos.py +5 -5
  158. reconcile/gql_definitions/gcp/gcp_projects.py +5 -5
  159. reconcile/gql_definitions/gitlab_members/gitlab_instances.py +5 -5
  160. reconcile/gql_definitions/gitlab_members/permissions.py +5 -5
  161. reconcile/gql_definitions/glitchtip/glitchtip_instance.py +5 -5
  162. reconcile/gql_definitions/glitchtip/glitchtip_project.py +5 -5
  163. reconcile/gql_definitions/glitchtip_project_alerts/glitchtip_project.py +5 -5
  164. reconcile/gql_definitions/integrations/integrations.py +5 -5
  165. reconcile/gql_definitions/introspection.json +2137 -1053
  166. reconcile/gql_definitions/jenkins_configs/jenkins_configs.py +5 -5
  167. reconcile/gql_definitions/jenkins_configs/jenkins_instances.py +5 -5
  168. reconcile/gql_definitions/jira/jira_servers.py +5 -5
  169. reconcile/gql_definitions/jira_permissions_validator/jira_boards_for_permissions_validator.py +9 -5
  170. reconcile/gql_definitions/jumphosts/jumphosts.py +5 -5
  171. reconcile/gql_definitions/ldap_groups/roles.py +5 -5
  172. reconcile/gql_definitions/ldap_groups/settings.py +5 -5
  173. reconcile/gql_definitions/maintenance/maintenances.py +5 -5
  174. reconcile/gql_definitions/membershipsources/roles.py +5 -5
  175. reconcile/gql_definitions/ocm_labels/clusters.py +5 -5
  176. reconcile/gql_definitions/ocm_labels/organizations.py +5 -5
  177. reconcile/gql_definitions/openshift_cluster_bots/clusters.py +5 -5
  178. reconcile/gql_definitions/openshift_groups/managed_groups.py +5 -5
  179. reconcile/gql_definitions/openshift_groups/managed_roles.py +5 -5
  180. reconcile/gql_definitions/openshift_serviceaccount_tokens/tokens.py +5 -5
  181. reconcile/gql_definitions/quay_membership/quay_membership.py +5 -5
  182. reconcile/gql_definitions/rhcs/certs.py +5 -5
  183. reconcile/gql_definitions/rhidp/organizations.py +5 -5
  184. reconcile/gql_definitions/service_dependencies/jenkins_instance_fragment.py +5 -5
  185. reconcile/gql_definitions/service_dependencies/service_dependencies.py +5 -5
  186. reconcile/gql_definitions/sharding/aws_accounts.py +5 -5
  187. reconcile/gql_definitions/sharding/ocm_organization.py +5 -5
  188. reconcile/gql_definitions/skupper_network/site_controller_template.py +5 -5
  189. reconcile/gql_definitions/skupper_network/skupper_networks.py +5 -5
  190. reconcile/gql_definitions/slack_usergroups/clusters.py +5 -5
  191. reconcile/gql_definitions/slack_usergroups/permissions.py +5 -5
  192. reconcile/gql_definitions/slack_usergroups/users.py +5 -5
  193. reconcile/gql_definitions/slo_documents/slo_documents.py +5 -5
  194. reconcile/gql_definitions/status_board/status_board.py +5 -5
  195. reconcile/gql_definitions/statuspage/statuspages.py +5 -5
  196. reconcile/gql_definitions/templating/template_collection.py +5 -5
  197. reconcile/gql_definitions/templating/templates.py +5 -5
  198. reconcile/gql_definitions/terraform_cloudflare_dns/app_interface_cloudflare_dns_settings.py +5 -5
  199. reconcile/gql_definitions/terraform_cloudflare_dns/terraform_cloudflare_zones.py +5 -5
  200. reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_accounts.py +5 -5
  201. reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_resources.py +5 -5
  202. reconcile/gql_definitions/terraform_cloudflare_users/app_interface_setting_cloudflare_and_vault.py +5 -5
  203. reconcile/gql_definitions/terraform_cloudflare_users/terraform_cloudflare_roles.py +5 -5
  204. reconcile/gql_definitions/terraform_init/aws_accounts.py +19 -5
  205. reconcile/gql_definitions/terraform_repo/terraform_repo.py +5 -5
  206. reconcile/gql_definitions/terraform_resources/database_access_manager.py +5 -5
  207. reconcile/gql_definitions/terraform_resources/terraform_resources_namespaces.py +38 -6
  208. reconcile/gql_definitions/terraform_tgw_attachments/aws_accounts.py +15 -5
  209. reconcile/gql_definitions/unleash_feature_toggles/feature_toggles.py +5 -5
  210. reconcile/gql_definitions/vault_instances/vault_instances.py +5 -5
  211. reconcile/gql_definitions/vault_policies/vault_policies.py +5 -5
  212. reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator.py +5 -5
  213. reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator_peered_cluster_fragment.py +5 -5
  214. reconcile/integrations_manager.py +3 -3
  215. reconcile/jenkins_worker_fleets.py +10 -8
  216. reconcile/jira_permissions_validator.py +237 -122
  217. reconcile/ldap_groups/integration.py +1 -1
  218. reconcile/ocm/types.py +35 -56
  219. reconcile/ocm_aws_infrastructure_access.py +1 -1
  220. reconcile/ocm_clusters.py +4 -4
  221. reconcile/ocm_labels/integration.py +3 -2
  222. reconcile/ocm_machine_pools.py +23 -23
  223. reconcile/openshift_base.py +53 -2
  224. reconcile/openshift_cluster_bots.py +3 -2
  225. reconcile/openshift_namespace_labels.py +1 -1
  226. reconcile/openshift_namespaces.py +97 -101
  227. reconcile/openshift_resources_base.py +6 -2
  228. reconcile/openshift_rhcs_certs.py +5 -5
  229. reconcile/openshift_rolebindings.py +7 -11
  230. reconcile/openshift_saas_deploy.py +6 -7
  231. reconcile/openshift_saas_deploy_change_tester.py +9 -7
  232. reconcile/openshift_saas_deploy_trigger_cleaner.py +3 -5
  233. reconcile/openshift_serviceaccount_tokens.py +2 -2
  234. reconcile/openshift_upgrade_watcher.py +4 -4
  235. reconcile/oum/labelset.py +5 -3
  236. reconcile/oum/models.py +1 -4
  237. reconcile/prometheus_rules_tester/integration.py +3 -3
  238. reconcile/quay_mirror.py +1 -1
  239. reconcile/queries.py +131 -1
  240. reconcile/rhidp/common.py +3 -5
  241. reconcile/rhidp/sso_client/base.py +1 -1
  242. reconcile/saas_auto_promotions_manager/merge_request_manager/renderer.py +1 -1
  243. reconcile/saas_auto_promotions_manager/subscriber.py +4 -3
  244. reconcile/skupper_network/integration.py +2 -2
  245. reconcile/slack_usergroups.py +35 -14
  246. reconcile/sql_query.py +1 -0
  247. reconcile/status_board.py +6 -6
  248. reconcile/statuspage/atlassian.py +7 -7
  249. reconcile/statuspage/integrations/maintenances.py +4 -3
  250. reconcile/statuspage/page.py +4 -9
  251. reconcile/statuspage/status.py +5 -8
  252. reconcile/templates/rosa-classic-cluster-creation.sh.j2 +4 -0
  253. reconcile/templates/rosa-hcp-cluster-creation.sh.j2 +3 -0
  254. reconcile/templating/lib/rendering.py +3 -3
  255. reconcile/templating/renderer.py +4 -3
  256. reconcile/terraform_aws_route53.py +7 -1
  257. reconcile/terraform_cloudflare_dns.py +3 -3
  258. reconcile/terraform_cloudflare_resources.py +5 -5
  259. reconcile/terraform_cloudflare_users.py +3 -2
  260. reconcile/terraform_init/integration.py +187 -23
  261. reconcile/terraform_repo.py +16 -12
  262. reconcile/terraform_resources.py +17 -7
  263. reconcile/terraform_tgw_attachments.py +27 -19
  264. reconcile/terraform_users.py +7 -0
  265. reconcile/terraform_vpc_peerings.py +14 -3
  266. reconcile/terraform_vpc_resources/integration.py +10 -1
  267. reconcile/typed_queries/aws_account_tags.py +41 -0
  268. reconcile/typed_queries/cost_report/app_names.py +1 -1
  269. reconcile/typed_queries/cost_report/cost_namespaces.py +2 -2
  270. reconcile/typed_queries/saas_files.py +13 -13
  271. reconcile/typed_queries/status_board.py +2 -2
  272. reconcile/unleash_feature_toggles/integration.py +4 -2
  273. reconcile/utils/acs/base.py +6 -3
  274. reconcile/utils/acs/policies.py +2 -2
  275. reconcile/utils/aggregated_list.py +4 -3
  276. reconcile/utils/aws_api.py +51 -54
  277. reconcile/utils/aws_api_typed/api.py +38 -9
  278. reconcile/utils/aws_api_typed/cloudformation.py +149 -0
  279. reconcile/utils/aws_api_typed/logs.py +73 -0
  280. reconcile/utils/aws_api_typed/organization.py +4 -2
  281. reconcile/utils/datetime_util.py +67 -0
  282. reconcile/utils/deadmanssnitch_api.py +1 -1
  283. reconcile/utils/differ.py +2 -3
  284. reconcile/utils/early_exit_cache.py +11 -12
  285. reconcile/utils/expiration.py +7 -3
  286. reconcile/utils/external_resource_spec.py +24 -1
  287. reconcile/utils/filtering.py +1 -1
  288. reconcile/utils/gitlab_api.py +7 -5
  289. reconcile/utils/glitchtip/client.py +6 -2
  290. reconcile/utils/glitchtip/models.py +25 -28
  291. reconcile/utils/gql.py +4 -7
  292. reconcile/utils/helm.py +2 -1
  293. reconcile/utils/helpers.py +1 -1
  294. reconcile/utils/instrumented_wrappers.py +1 -1
  295. reconcile/utils/internal_groups/client.py +2 -2
  296. reconcile/utils/internal_groups/models.py +8 -17
  297. reconcile/utils/jinja2/utils.py +6 -101
  298. reconcile/utils/jira_client.py +82 -63
  299. reconcile/utils/jjb_client.py +9 -12
  300. reconcile/utils/jobcontroller/controller.py +1 -1
  301. reconcile/utils/jobcontroller/models.py +17 -1
  302. reconcile/utils/json.py +70 -0
  303. reconcile/utils/membershipsources/app_interface_resolver.py +4 -2
  304. reconcile/utils/membershipsources/models.py +16 -23
  305. reconcile/utils/membershipsources/resolver.py +4 -2
  306. reconcile/utils/merge_request_manager/merge_request_manager.py +4 -4
  307. reconcile/utils/merge_request_manager/parser.py +6 -6
  308. reconcile/utils/metrics.py +5 -5
  309. reconcile/utils/models.py +304 -82
  310. reconcile/utils/mr/app_interface_reporter.py +2 -2
  311. reconcile/utils/mr/base.py +2 -2
  312. reconcile/utils/mr/notificator.py +3 -3
  313. reconcile/utils/mr/update_access_report_base.py +3 -4
  314. reconcile/utils/mr/user_maintenance.py +3 -2
  315. reconcile/utils/oc.py +118 -97
  316. reconcile/utils/oc_filters.py +3 -3
  317. reconcile/utils/ocm/addons.py +0 -1
  318. reconcile/utils/ocm/base.py +17 -20
  319. reconcile/utils/ocm/cluster_groups.py +1 -1
  320. reconcile/utils/ocm/identity_providers.py +2 -2
  321. reconcile/utils/ocm/labels.py +1 -1
  322. reconcile/utils/ocm/products.py +9 -3
  323. reconcile/utils/ocm/search_filters.py +3 -6
  324. reconcile/utils/ocm/service_log.py +4 -6
  325. reconcile/utils/ocm/sre_capability_labels.py +20 -13
  326. reconcile/utils/openshift_resource.py +10 -5
  327. reconcile/utils/output.py +3 -2
  328. reconcile/utils/pagerduty_api.py +10 -7
  329. reconcile/utils/promotion_state.py +6 -11
  330. reconcile/utils/raw_github_api.py +1 -1
  331. reconcile/utils/rhcsv2_certs.py +1 -4
  332. reconcile/utils/runtime/integration.py +2 -3
  333. reconcile/utils/runtime/runner.py +2 -2
  334. reconcile/utils/saasherder/interfaces.py +13 -20
  335. reconcile/utils/saasherder/models.py +25 -21
  336. reconcile/utils/saasherder/saasherder.py +35 -24
  337. reconcile/utils/slack_api.py +26 -4
  338. reconcile/utils/sloth.py +171 -2
  339. reconcile/utils/sqs_gateway.py +2 -1
  340. reconcile/utils/state.py +2 -1
  341. reconcile/utils/structs.py +1 -1
  342. reconcile/utils/terraform_client.py +5 -4
  343. reconcile/utils/terrascript_aws_client.py +171 -114
  344. reconcile/utils/unleash/server.py +2 -8
  345. reconcile/utils/vault.py +5 -12
  346. reconcile/utils/vcs.py +8 -8
  347. reconcile/vault_replication.py +107 -42
  348. tools/app_interface_reporter.py +4 -4
  349. tools/cli_commands/cost_report/cost_management_api.py +3 -3
  350. tools/cli_commands/cost_report/view.py +7 -6
  351. tools/cli_commands/erv2.py +3 -1
  352. tools/cli_commands/systems_and_tools.py +5 -1
  353. tools/qontract_cli.py +31 -18
  354. tools/template_validation.py +3 -1
  355. {qontract_reconcile-0.10.2.dev349.dist-info → qontract_reconcile-0.10.2.dev414.dist-info}/WHEEL +0 -0
  356. {qontract_reconcile-0.10.2.dev349.dist-info → qontract_reconcile-0.10.2.dev414.dist-info}/entry_points.txt +0 -0
@@ -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
@@ -1,5 +1,4 @@
1
1
  import hashlib
2
- import json
3
2
  from collections.abc import Callable
4
3
  from threading import Lock
5
4
  from typing import Any
@@ -7,7 +6,6 @@ from typing import Any
7
6
  from jsonpath_ng.exceptions import JsonPathParserError
8
7
  from pydantic import (
9
8
  BaseModel,
10
- Extra,
11
9
  Field,
12
10
  Json,
13
11
  )
@@ -48,10 +46,16 @@ from reconcile.utils.exceptions import (
48
46
  AppInterfaceSettingsError,
49
47
  ParameterError,
50
48
  )
49
+ 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[:]:
@@ -302,7 +302,7 @@ def convert_parameters_to_json_string(root: dict[str, Any]) -> dict[str, Any]:
302
302
  """Find all parameter occurrences and convert them to a json string."""
303
303
  for key, value in root.items():
304
304
  if key in {"parameters", "labels"}:
305
- root[key] = json.dumps(value) if value is not None else None
305
+ root[key] = json_dumps(value) if value is not None else None
306
306
  elif isinstance(value, dict):
307
307
  root[key] = convert_parameters_to_json_string(value)
308
308
  elif isinstance(value, list):
@@ -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
 
@@ -1,8 +1,9 @@
1
- import json
2
1
  import logging
3
2
  from collections.abc import Callable, KeysView
4
3
  from typing import Any, TypedDict
5
4
 
5
+ from reconcile.utils.json import json_dumps
6
+
6
7
  Action = Callable[[Any, list[Any]], bool]
7
8
  Cond = Callable[[Any], bool]
8
9
 
@@ -89,11 +90,11 @@ class AggregatedList:
89
90
  return list(self._dict.values())
90
91
 
91
92
  def to_json(self) -> str:
92
- return json.dumps(self.dump(), indent=4)
93
+ return json_dumps(self.dump(), indent=4)
93
94
 
94
95
  @staticmethod
95
96
  def hash_params(params: Any) -> int:
96
- return hash(json.dumps(params, sort_keys=True))
97
+ return hash(json_dumps(params))
97
98
 
98
99
 
99
100
  class AggregatedDiffRunner:
@@ -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,
@@ -583,40 +583,6 @@ class AWSApi:
583
583
 
584
584
  return error, service_account_recycle_complete
585
585
 
586
- def disable_keys(
587
- self,
588
- dry_run: bool,
589
- keys_to_disable: Mapping,
590
- ) -> bool:
591
- error = False
592
- users_keys = self.get_users_keys()
593
- for account, s in self.sessions.items():
594
- iam = self.get_session_client(s, "iam")
595
- keys = keys_to_disable.get(account, [])
596
- for key in keys:
597
- user_and_user_keys = [
598
- (user, user_keys)
599
- for user, user_keys in users_keys[account].items()
600
- if key in user_keys
601
- ]
602
- if not user_and_user_keys:
603
- continue
604
- if len(user_and_user_keys) > 1:
605
- raise RuntimeError(
606
- f"key {key} returned multiple users: {user_and_user_keys}"
607
- )
608
- user = user_and_user_keys[0][0]
609
- key_status = self.get_user_key_status(iam, user, key)
610
- if key_status == "Active":
611
- logging.info(["disable_key", account, user, key])
612
- if not dry_run:
613
- iam.update_access_key(
614
- UserName=user, AccessKeyId=key, Status="Inactive"
615
- )
616
- else:
617
- logging.info(["key_already_disabled", account, user, key])
618
- return error
619
-
620
586
  def get_users_keys(self) -> dict:
621
587
  users_keys = {}
622
588
  for account, s in self.sessions.items():
@@ -1108,28 +1074,40 @@ class AWSApi:
1108
1074
  return [rt["RouteTableId"] for rt in vpc_route_tables]
1109
1075
 
1110
1076
  @staticmethod
1111
- def _filter_amis(
1112
- images: Iterable[ImageTypeDef], regex: str
1113
- ) -> list[dict[str, Any]]:
1114
- results = []
1115
- pattern = re.compile(regex)
1116
- for i in images:
1117
- if not re.search(pattern, i["Name"]):
1118
- continue
1119
- if i["State"] != "available":
1120
- continue
1121
- item = {"image_id": i["ImageId"], "tags": i.get("Tags", [])}
1122
- results.append(item)
1077
+ def normalize_tags(tags: Iterable[TagTypeDef]) -> dict[str, str]:
1078
+ return {tag["Key"]: tag["Value"] for tag in tags}
1123
1079
 
1124
- 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
+ }
1125
1090
 
1126
1091
  def get_amis_details(
1127
1092
  self,
1128
1093
  account: Mapping[str, Any],
1129
1094
  owner_account: Mapping[str, Any],
1130
- regex: str,
1095
+ regex: re.Pattern,
1131
1096
  region: str | None = None,
1132
- ) -> 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
+ """
1133
1111
  ec2 = self._account_ec2_client(account["name"], region_name=region)
1134
1112
  images = self.get_account_amis(ec2, owner=owner_account["uid"])
1135
1113
  return self._filter_amis(images, regex)
@@ -1209,12 +1187,31 @@ class AWSApi:
1209
1187
  client = self._account_cloudwatch_client(account_name, region_name=region_name)
1210
1188
  client.delete_log_group(logGroupName=group_name)
1211
1189
 
1212
- def create_tag(
1213
- 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],
1214
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
+ """
1215
1207
  ec2 = self._account_ec2_client(account["name"])
1216
- tag_type_def: TagTypeDef = {"Key": tag["Key"], "Value": tag["Value"]}
1217
- 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
+ )
1218
1215
 
1219
1216
  def get_alb_network_interface_ips(
1220
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."""