qontract-reconcile 0.10.2.dev299__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 (403) hide show
  1. {qontract_reconcile-0.10.2.dev299.dist-info → qontract_reconcile-0.10.2.dev430.dist-info}/METADATA +13 -12
  2. {qontract_reconcile-0.10.2.dev299.dist-info → qontract_reconcile-0.10.2.dev430.dist-info}/RECORD +399 -394
  3. reconcile/acs_rbac.py +2 -2
  4. reconcile/aus/advanced_upgrade_service.py +18 -12
  5. reconcile/aus/base.py +134 -32
  6. reconcile/aus/cluster_version_data.py +15 -5
  7. reconcile/aus/models.py +3 -1
  8. reconcile/aus/ocm_addons_upgrade_scheduler_org.py +1 -0
  9. reconcile/aus/ocm_upgrade_scheduler.py +8 -1
  10. reconcile/aus/ocm_upgrade_scheduler_org.py +20 -5
  11. reconcile/aus/version_gates/sts_version_gate_handler.py +54 -1
  12. reconcile/automated_actions/config/integration.py +16 -4
  13. reconcile/aws_account_manager/integration.py +8 -8
  14. reconcile/aws_account_manager/reconciler.py +3 -3
  15. reconcile/aws_ami_cleanup/integration.py +8 -12
  16. reconcile/aws_ami_share.py +69 -62
  17. reconcile/aws_cloudwatch_log_retention/integration.py +155 -126
  18. reconcile/aws_ecr_image_pull_secrets.py +4 -4
  19. reconcile/aws_iam_keys.py +1 -0
  20. reconcile/aws_saml_idp/integration.py +12 -4
  21. reconcile/aws_saml_roles/integration.py +32 -25
  22. reconcile/aws_version_sync/integration.py +125 -84
  23. reconcile/change_owners/bundle.py +3 -3
  24. reconcile/change_owners/change_log_tracking.py +3 -2
  25. reconcile/change_owners/change_owners.py +1 -1
  26. reconcile/change_owners/diff.py +2 -4
  27. reconcile/checkpoint.py +12 -4
  28. reconcile/cli.py +111 -18
  29. reconcile/cluster_deployment_mapper.py +2 -3
  30. reconcile/dashdotdb_dora.py +5 -12
  31. reconcile/dashdotdb_slo.py +1 -1
  32. reconcile/database_access_manager.py +125 -121
  33. reconcile/deadmanssnitch.py +1 -5
  34. reconcile/dynatrace_token_provider/integration.py +1 -1
  35. reconcile/endpoints_discovery/integration.py +4 -1
  36. reconcile/endpoints_discovery/merge_request.py +1 -1
  37. reconcile/endpoints_discovery/merge_request_manager.py +9 -11
  38. reconcile/external_resources/factories.py +5 -12
  39. reconcile/external_resources/integration.py +1 -1
  40. reconcile/external_resources/manager.py +8 -5
  41. reconcile/external_resources/meta.py +0 -1
  42. reconcile/external_resources/metrics.py +1 -1
  43. reconcile/external_resources/model.py +20 -20
  44. reconcile/external_resources/reconciler.py +7 -4
  45. reconcile/external_resources/secrets_sync.py +8 -11
  46. reconcile/external_resources/state.py +26 -16
  47. reconcile/fleet_labeler/integration.py +1 -1
  48. reconcile/gabi_authorized_users.py +8 -5
  49. reconcile/gcp_image_mirror.py +2 -2
  50. reconcile/github_org.py +1 -1
  51. reconcile/github_owners.py +4 -0
  52. reconcile/gitlab_housekeeping.py +13 -15
  53. reconcile/gitlab_members.py +6 -12
  54. reconcile/gitlab_mr_sqs_consumer.py +2 -2
  55. reconcile/gitlab_owners.py +15 -11
  56. reconcile/gitlab_permissions.py +8 -12
  57. reconcile/glitchtip_project_alerts/integration.py +3 -1
  58. reconcile/gql_definitions/acs/acs_instances.py +10 -10
  59. reconcile/gql_definitions/acs/acs_policies.py +5 -5
  60. reconcile/gql_definitions/acs/acs_rbac.py +6 -6
  61. reconcile/gql_definitions/advanced_upgrade_service/aus_clusters.py +32 -32
  62. reconcile/gql_definitions/advanced_upgrade_service/aus_organization.py +26 -26
  63. reconcile/gql_definitions/app_interface_metrics_exporter/onboarding_status.py +6 -7
  64. reconcile/gql_definitions/app_sre_tekton_access_revalidation/roles.py +5 -5
  65. reconcile/gql_definitions/app_sre_tekton_access_revalidation/users.py +5 -5
  66. reconcile/gql_definitions/automated_actions/instance.py +51 -12
  67. reconcile/gql_definitions/aws_account_manager/aws_accounts.py +11 -11
  68. reconcile/gql_definitions/aws_ami_cleanup/aws_accounts.py +20 -10
  69. reconcile/gql_definitions/aws_cloudwatch_log_retention/aws_accounts.py +28 -68
  70. reconcile/gql_definitions/aws_saml_idp/aws_accounts.py +20 -10
  71. reconcile/gql_definitions/aws_saml_roles/aws_accounts.py +20 -10
  72. reconcile/gql_definitions/aws_saml_roles/roles.py +5 -5
  73. reconcile/gql_definitions/aws_version_sync/clusters.py +10 -10
  74. reconcile/gql_definitions/aws_version_sync/namespaces.py +5 -5
  75. reconcile/gql_definitions/change_owners/queries/change_types.py +5 -5
  76. reconcile/gql_definitions/change_owners/queries/self_service_roles.py +9 -9
  77. reconcile/gql_definitions/cluster_auth_rhidp/clusters.py +18 -18
  78. reconcile/gql_definitions/common/alerting_services_settings.py +9 -9
  79. reconcile/gql_definitions/common/app_code_component_repos.py +5 -5
  80. reconcile/gql_definitions/common/app_interface_custom_messages.py +5 -5
  81. reconcile/gql_definitions/common/app_interface_dms_settings.py +5 -5
  82. reconcile/gql_definitions/common/app_interface_repo_settings.py +5 -5
  83. reconcile/gql_definitions/common/app_interface_roles.py +120 -0
  84. reconcile/gql_definitions/common/app_interface_state_settings.py +10 -10
  85. reconcile/gql_definitions/common/app_interface_vault_settings.py +5 -5
  86. reconcile/gql_definitions/common/app_quay_repos_escalation_policies.py +5 -5
  87. reconcile/gql_definitions/common/apps.py +5 -5
  88. reconcile/gql_definitions/common/aws_vpc_requests.py +23 -10
  89. reconcile/gql_definitions/common/aws_vpcs.py +11 -11
  90. reconcile/gql_definitions/common/clusters.py +37 -35
  91. reconcile/gql_definitions/common/clusters_minimal.py +14 -14
  92. reconcile/gql_definitions/common/clusters_with_dms.py +6 -6
  93. reconcile/gql_definitions/common/clusters_with_peering.py +29 -30
  94. reconcile/gql_definitions/common/github_orgs.py +10 -10
  95. reconcile/gql_definitions/common/jira_settings.py +10 -10
  96. reconcile/gql_definitions/common/jiralert_settings.py +5 -5
  97. reconcile/gql_definitions/common/ldap_settings.py +5 -5
  98. reconcile/gql_definitions/common/namespaces.py +42 -44
  99. reconcile/gql_definitions/common/namespaces_minimal.py +15 -13
  100. reconcile/gql_definitions/common/ocm_env_telemeter.py +12 -12
  101. reconcile/gql_definitions/common/ocm_environments.py +19 -19
  102. reconcile/gql_definitions/common/pagerduty_instances.py +9 -9
  103. reconcile/gql_definitions/common/pgp_reencryption_settings.py +6 -6
  104. reconcile/gql_definitions/common/pipeline_providers.py +29 -29
  105. reconcile/gql_definitions/common/quay_instances.py +5 -5
  106. reconcile/gql_definitions/common/quay_orgs.py +5 -5
  107. reconcile/gql_definitions/common/reserved_networks.py +5 -5
  108. reconcile/gql_definitions/common/rhcs_provider_settings.py +5 -5
  109. reconcile/gql_definitions/common/saas_files.py +44 -44
  110. reconcile/gql_definitions/common/saas_target_namespaces.py +10 -10
  111. reconcile/gql_definitions/common/saasherder_settings.py +5 -5
  112. reconcile/gql_definitions/common/slack_workspaces.py +5 -5
  113. reconcile/gql_definitions/common/smtp_client_settings.py +19 -19
  114. reconcile/gql_definitions/common/state_aws_account.py +7 -8
  115. reconcile/gql_definitions/common/users.py +5 -5
  116. reconcile/gql_definitions/common/users_with_paths.py +5 -5
  117. reconcile/gql_definitions/cost_report/app_names.py +5 -5
  118. reconcile/gql_definitions/cost_report/cost_namespaces.py +5 -5
  119. reconcile/gql_definitions/cost_report/settings.py +9 -9
  120. reconcile/gql_definitions/dashdotdb_slo/slo_documents_query.py +43 -43
  121. reconcile/gql_definitions/dynatrace_token_provider/dynatrace_bootstrap_tokens.py +10 -10
  122. reconcile/gql_definitions/dynatrace_token_provider/token_specs.py +5 -5
  123. reconcile/gql_definitions/email_sender/apps.py +5 -5
  124. reconcile/gql_definitions/email_sender/emails.py +8 -8
  125. reconcile/gql_definitions/email_sender/users.py +6 -6
  126. reconcile/gql_definitions/endpoints_discovery/apps.py +10 -10
  127. reconcile/gql_definitions/external_resources/aws_accounts.py +9 -9
  128. reconcile/gql_definitions/external_resources/external_resources_modules.py +23 -23
  129. reconcile/gql_definitions/external_resources/external_resources_namespaces.py +492 -410
  130. reconcile/gql_definitions/external_resources/external_resources_settings.py +28 -26
  131. reconcile/gql_definitions/external_resources/fragments/external_resources_module_overrides.py +5 -5
  132. reconcile/gql_definitions/fleet_labeler/fleet_labels.py +40 -40
  133. reconcile/gql_definitions/fragments/aus_organization.py +5 -5
  134. reconcile/gql_definitions/fragments/aws_account_common.py +7 -5
  135. reconcile/gql_definitions/fragments/aws_account_managed.py +5 -5
  136. reconcile/gql_definitions/fragments/aws_account_sso.py +5 -5
  137. reconcile/gql_definitions/fragments/aws_infra_management_account.py +5 -5
  138. reconcile/gql_definitions/fragments/{aws_vpc_request_subnet.py → aws_organization.py} +12 -8
  139. reconcile/gql_definitions/fragments/aws_vpc.py +5 -5
  140. reconcile/gql_definitions/fragments/aws_vpc_request.py +10 -5
  141. reconcile/gql_definitions/fragments/container_image_mirror.py +5 -5
  142. reconcile/gql_definitions/fragments/deploy_resources.py +5 -5
  143. reconcile/gql_definitions/fragments/disable.py +5 -5
  144. reconcile/gql_definitions/fragments/email_service.py +5 -5
  145. reconcile/gql_definitions/fragments/email_user.py +5 -5
  146. reconcile/gql_definitions/fragments/jumphost_common_fields.py +5 -5
  147. reconcile/gql_definitions/fragments/membership_source.py +5 -5
  148. reconcile/gql_definitions/fragments/minimal_ocm_organization.py +5 -5
  149. reconcile/gql_definitions/fragments/oc_connection_cluster.py +5 -5
  150. reconcile/gql_definitions/fragments/ocm_environment.py +5 -5
  151. reconcile/gql_definitions/fragments/pipeline_provider_retention.py +5 -5
  152. reconcile/gql_definitions/fragments/prometheus_instance.py +5 -5
  153. reconcile/gql_definitions/fragments/resource_limits_requirements.py +5 -5
  154. reconcile/gql_definitions/fragments/resource_requests_requirements.py +5 -5
  155. reconcile/gql_definitions/fragments/resource_values.py +5 -5
  156. reconcile/gql_definitions/fragments/saas_slo_document.py +5 -5
  157. reconcile/gql_definitions/fragments/saas_target_namespace.py +5 -5
  158. reconcile/gql_definitions/fragments/serviceaccount_token.py +5 -5
  159. reconcile/gql_definitions/fragments/terraform_state.py +5 -5
  160. reconcile/gql_definitions/fragments/upgrade_policy.py +5 -5
  161. reconcile/gql_definitions/fragments/user.py +5 -5
  162. reconcile/gql_definitions/fragments/vault_secret.py +5 -5
  163. reconcile/gql_definitions/gcp/gcp_docker_repos.py +9 -9
  164. reconcile/gql_definitions/gcp/gcp_projects.py +9 -9
  165. reconcile/gql_definitions/gitlab_members/gitlab_instances.py +9 -9
  166. reconcile/gql_definitions/gitlab_members/permissions.py +9 -9
  167. reconcile/gql_definitions/glitchtip/glitchtip_instance.py +9 -9
  168. reconcile/gql_definitions/glitchtip/glitchtip_project.py +11 -11
  169. reconcile/gql_definitions/glitchtip_project_alerts/glitchtip_project.py +9 -9
  170. reconcile/gql_definitions/integrations/integrations.py +48 -51
  171. reconcile/gql_definitions/introspection.json +3050 -1393
  172. reconcile/gql_definitions/jenkins_configs/jenkins_configs.py +11 -11
  173. reconcile/gql_definitions/jenkins_configs/jenkins_instances.py +10 -10
  174. reconcile/gql_definitions/jira/jira_servers.py +5 -5
  175. reconcile/gql_definitions/jira_permissions_validator/jira_boards_for_permissions_validator.py +14 -10
  176. reconcile/gql_definitions/jumphosts/jumphosts.py +13 -13
  177. reconcile/gql_definitions/ldap_groups/roles.py +5 -5
  178. reconcile/gql_definitions/ldap_groups/settings.py +9 -9
  179. reconcile/gql_definitions/maintenance/maintenances.py +5 -5
  180. reconcile/gql_definitions/membershipsources/roles.py +5 -5
  181. reconcile/gql_definitions/ocm_labels/clusters.py +18 -19
  182. reconcile/gql_definitions/ocm_labels/organizations.py +5 -5
  183. reconcile/gql_definitions/openshift_cluster_bots/clusters.py +22 -22
  184. reconcile/gql_definitions/openshift_groups/managed_groups.py +5 -5
  185. reconcile/gql_definitions/openshift_groups/managed_roles.py +6 -6
  186. reconcile/gql_definitions/openshift_serviceaccount_tokens/tokens.py +10 -10
  187. reconcile/gql_definitions/quay_membership/quay_membership.py +6 -6
  188. reconcile/gql_definitions/rhcs/certs.py +33 -87
  189. reconcile/gql_definitions/rhcs/openshift_resource_rhcs_cert.py +43 -0
  190. reconcile/gql_definitions/rhidp/organizations.py +18 -18
  191. reconcile/gql_definitions/service_dependencies/jenkins_instance_fragment.py +5 -5
  192. reconcile/gql_definitions/service_dependencies/service_dependencies.py +8 -8
  193. reconcile/gql_definitions/sharding/aws_accounts.py +10 -10
  194. reconcile/gql_definitions/sharding/ocm_organization.py +8 -8
  195. reconcile/gql_definitions/skupper_network/site_controller_template.py +5 -5
  196. reconcile/gql_definitions/skupper_network/skupper_networks.py +10 -10
  197. reconcile/gql_definitions/slack_usergroups/clusters.py +5 -5
  198. reconcile/gql_definitions/slack_usergroups/permissions.py +9 -9
  199. reconcile/gql_definitions/slack_usergroups/users.py +5 -5
  200. reconcile/gql_definitions/slo_documents/slo_documents.py +5 -5
  201. reconcile/gql_definitions/status_board/status_board.py +6 -7
  202. reconcile/gql_definitions/statuspage/statuspages.py +9 -9
  203. reconcile/gql_definitions/templating/template_collection.py +5 -5
  204. reconcile/gql_definitions/templating/templates.py +5 -5
  205. reconcile/gql_definitions/terraform_cloudflare_dns/app_interface_cloudflare_dns_settings.py +6 -6
  206. reconcile/gql_definitions/terraform_cloudflare_dns/terraform_cloudflare_zones.py +11 -11
  207. reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_accounts.py +11 -11
  208. reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_resources.py +20 -25
  209. reconcile/gql_definitions/terraform_cloudflare_users/app_interface_setting_cloudflare_and_vault.py +6 -6
  210. reconcile/gql_definitions/terraform_cloudflare_users/terraform_cloudflare_roles.py +12 -12
  211. reconcile/gql_definitions/terraform_init/aws_accounts.py +23 -9
  212. reconcile/gql_definitions/terraform_repo/terraform_repo.py +9 -9
  213. reconcile/gql_definitions/terraform_resources/database_access_manager.py +5 -5
  214. reconcile/gql_definitions/terraform_resources/terraform_resources_namespaces.py +448 -402
  215. reconcile/gql_definitions/terraform_tgw_attachments/aws_accounts.py +23 -17
  216. reconcile/gql_definitions/unleash_feature_toggles/feature_toggles.py +9 -9
  217. reconcile/gql_definitions/vault_instances/vault_instances.py +61 -61
  218. reconcile/gql_definitions/vault_policies/vault_policies.py +11 -11
  219. reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator.py +8 -8
  220. reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator_peered_cluster_fragment.py +5 -5
  221. reconcile/integrations_manager.py +3 -3
  222. reconcile/jenkins_job_builder.py +1 -1
  223. reconcile/jenkins_worker_fleets.py +80 -11
  224. reconcile/jira_permissions_validator.py +237 -122
  225. reconcile/ldap_groups/integration.py +1 -1
  226. reconcile/ocm/types.py +35 -56
  227. reconcile/ocm_aws_infrastructure_access.py +1 -1
  228. reconcile/ocm_clusters.py +4 -4
  229. reconcile/ocm_labels/integration.py +3 -2
  230. reconcile/ocm_machine_pools.py +33 -27
  231. reconcile/openshift_base.py +122 -10
  232. reconcile/openshift_cluster_bots.py +5 -5
  233. reconcile/openshift_groups.py +5 -0
  234. reconcile/openshift_limitranges.py +1 -1
  235. reconcile/openshift_namespace_labels.py +1 -1
  236. reconcile/openshift_namespaces.py +97 -101
  237. reconcile/openshift_resources_base.py +10 -5
  238. reconcile/openshift_rhcs_certs.py +77 -40
  239. reconcile/openshift_rolebindings.py +230 -130
  240. reconcile/openshift_saas_deploy.py +6 -7
  241. reconcile/openshift_saas_deploy_change_tester.py +9 -7
  242. reconcile/openshift_saas_deploy_trigger_cleaner.py +3 -5
  243. reconcile/openshift_serviceaccount_tokens.py +8 -7
  244. reconcile/openshift_tekton_resources.py +1 -1
  245. reconcile/openshift_upgrade_watcher.py +4 -4
  246. reconcile/openshift_users.py +5 -3
  247. reconcile/oum/labelset.py +5 -3
  248. reconcile/oum/models.py +1 -4
  249. reconcile/oum/providers.py +1 -1
  250. reconcile/prometheus_rules_tester/integration.py +4 -4
  251. reconcile/quay_mirror.py +1 -1
  252. reconcile/queries.py +131 -0
  253. reconcile/requests_sender.py +8 -3
  254. reconcile/resource_scraper.py +1 -5
  255. reconcile/rhidp/common.py +5 -5
  256. reconcile/rhidp/sso_client/base.py +19 -10
  257. reconcile/saas_auto_promotions_manager/merge_request_manager/renderer.py +1 -1
  258. reconcile/saas_auto_promotions_manager/subscriber.py +4 -3
  259. reconcile/sendgrid_teammates.py +20 -9
  260. reconcile/skupper_network/integration.py +2 -2
  261. reconcile/slack_usergroups.py +35 -14
  262. reconcile/sql_query.py +1 -0
  263. reconcile/status.py +2 -2
  264. reconcile/status_board.py +6 -6
  265. reconcile/statuspage/atlassian.py +7 -7
  266. reconcile/statuspage/integrations/maintenances.py +4 -3
  267. reconcile/statuspage/page.py +4 -9
  268. reconcile/statuspage/status.py +5 -8
  269. reconcile/templates/rosa-classic-cluster-creation.sh.j2 +4 -0
  270. reconcile/templates/rosa-hcp-cluster-creation.sh.j2 +3 -0
  271. reconcile/templating/lib/merge_request_manager.py +2 -2
  272. reconcile/templating/lib/rendering.py +3 -3
  273. reconcile/templating/renderer.py +12 -13
  274. reconcile/terraform_aws_route53.py +18 -8
  275. reconcile/terraform_cloudflare_dns.py +3 -3
  276. reconcile/terraform_cloudflare_resources.py +12 -13
  277. reconcile/terraform_cloudflare_users.py +3 -2
  278. reconcile/terraform_init/integration.py +187 -23
  279. reconcile/terraform_repo.py +16 -12
  280. reconcile/terraform_resources.py +18 -10
  281. reconcile/terraform_tgw_attachments.py +27 -19
  282. reconcile/terraform_users.py +29 -21
  283. reconcile/terraform_vpc_peerings.py +16 -4
  284. reconcile/terraform_vpc_resources/integration.py +32 -2
  285. reconcile/typed_queries/app_interface_roles.py +10 -0
  286. reconcile/typed_queries/aws_account_tags.py +41 -0
  287. reconcile/typed_queries/cost_report/app_names.py +1 -1
  288. reconcile/typed_queries/cost_report/cost_namespaces.py +2 -2
  289. reconcile/typed_queries/saas_files.py +13 -13
  290. reconcile/typed_queries/status_board.py +2 -2
  291. reconcile/unleash_feature_toggles/integration.py +4 -2
  292. reconcile/utils/acs/base.py +6 -3
  293. reconcile/utils/acs/policies.py +2 -2
  294. reconcile/utils/aggregated_list.py +4 -3
  295. reconcile/utils/aws_api.py +51 -20
  296. reconcile/utils/aws_api_typed/api.py +38 -9
  297. reconcile/utils/aws_api_typed/cloudformation.py +149 -0
  298. reconcile/utils/aws_api_typed/logs.py +73 -0
  299. reconcile/utils/aws_api_typed/organization.py +4 -2
  300. reconcile/utils/binary.py +7 -12
  301. reconcile/utils/datetime_util.py +67 -0
  302. reconcile/utils/deadmanssnitch_api.py +1 -1
  303. reconcile/utils/differ.py +2 -3
  304. reconcile/utils/early_exit_cache.py +11 -12
  305. reconcile/utils/expiration.py +7 -3
  306. reconcile/utils/external_resource_spec.py +24 -1
  307. reconcile/utils/filtering.py +1 -1
  308. reconcile/utils/gitlab_api.py +7 -5
  309. reconcile/utils/glitchtip/client.py +6 -2
  310. reconcile/utils/glitchtip/models.py +25 -28
  311. reconcile/utils/gpg.py +5 -3
  312. reconcile/utils/gql.py +4 -7
  313. reconcile/utils/helm.py +2 -1
  314. reconcile/utils/helpers.py +1 -1
  315. reconcile/utils/imap_client.py +1 -1
  316. reconcile/utils/instrumented_wrappers.py +1 -1
  317. reconcile/utils/internal_groups/client.py +2 -2
  318. reconcile/utils/internal_groups/models.py +8 -17
  319. reconcile/utils/jenkins_api.py +24 -1
  320. reconcile/utils/jinja2/utils.py +6 -8
  321. reconcile/utils/jira_client.py +82 -63
  322. reconcile/utils/jjb_client.py +59 -43
  323. reconcile/utils/jobcontroller/controller.py +2 -2
  324. reconcile/utils/jobcontroller/models.py +17 -1
  325. reconcile/utils/json.py +74 -0
  326. reconcile/utils/ldap_client.py +4 -3
  327. reconcile/utils/lean_terraform_client.py +3 -1
  328. reconcile/utils/membershipsources/app_interface_resolver.py +4 -2
  329. reconcile/utils/membershipsources/models.py +16 -23
  330. reconcile/utils/membershipsources/resolver.py +4 -2
  331. reconcile/utils/merge_request_manager/merge_request_manager.py +4 -4
  332. reconcile/utils/merge_request_manager/parser.py +6 -6
  333. reconcile/utils/metrics.py +5 -5
  334. reconcile/utils/models.py +304 -82
  335. reconcile/utils/mr/__init__.py +3 -1
  336. reconcile/utils/mr/app_interface_reporter.py +6 -3
  337. reconcile/utils/mr/aws_access.py +1 -1
  338. reconcile/utils/mr/base.py +7 -13
  339. reconcile/utils/mr/clusters_updates.py +4 -2
  340. reconcile/utils/mr/notificator.py +3 -3
  341. reconcile/utils/mr/ocm_upgrade_scheduler_org_updates.py +4 -1
  342. reconcile/utils/mr/promote_qontract.py +28 -12
  343. reconcile/utils/mr/update_access_report_base.py +3 -4
  344. reconcile/utils/mr/user_maintenance.py +7 -6
  345. reconcile/utils/oc.py +445 -336
  346. reconcile/utils/oc_filters.py +3 -3
  347. reconcile/utils/ocm/addons.py +0 -1
  348. reconcile/utils/ocm/base.py +27 -20
  349. reconcile/utils/ocm/cluster_groups.py +1 -1
  350. reconcile/utils/ocm/identity_providers.py +2 -2
  351. reconcile/utils/ocm/labels.py +1 -1
  352. reconcile/utils/ocm/ocm.py +81 -71
  353. reconcile/utils/ocm/products.py +9 -3
  354. reconcile/utils/ocm/search_filters.py +3 -6
  355. reconcile/utils/ocm/service_log.py +4 -6
  356. reconcile/utils/ocm/sre_capability_labels.py +20 -13
  357. reconcile/utils/ocm_base_client.py +4 -4
  358. reconcile/utils/openshift_resource.py +83 -52
  359. reconcile/utils/openssl.py +2 -2
  360. reconcile/utils/output.py +3 -2
  361. reconcile/utils/pagerduty_api.py +10 -7
  362. reconcile/utils/promotion_state.py +6 -11
  363. reconcile/utils/raw_github_api.py +11 -8
  364. reconcile/utils/repo_owners.py +21 -29
  365. reconcile/utils/rhcsv2_certs.py +138 -35
  366. reconcile/utils/rosa/session.py +16 -0
  367. reconcile/utils/runtime/integration.py +2 -3
  368. reconcile/utils/runtime/meta.py +2 -1
  369. reconcile/utils/runtime/runner.py +2 -2
  370. reconcile/utils/saasherder/interfaces.py +13 -20
  371. reconcile/utils/saasherder/models.py +25 -21
  372. reconcile/utils/saasherder/saasherder.py +60 -32
  373. reconcile/utils/secret_reader.py +6 -6
  374. reconcile/utils/sharding.py +1 -1
  375. reconcile/utils/slack_api.py +26 -4
  376. reconcile/utils/sloth.py +224 -0
  377. reconcile/utils/sqs_gateway.py +16 -11
  378. reconcile/utils/state.py +2 -1
  379. reconcile/utils/structs.py +4 -4
  380. reconcile/utils/terraform_client.py +32 -29
  381. reconcile/utils/terrascript_aws_client.py +658 -480
  382. reconcile/utils/three_way_diff_strategy.py +1 -1
  383. reconcile/utils/throughput.py +1 -1
  384. reconcile/utils/unleash/server.py +2 -8
  385. reconcile/utils/vault.py +44 -41
  386. reconcile/utils/vcs.py +8 -8
  387. reconcile/vault_replication.py +119 -58
  388. reconcile/vpc_peerings_validator.py +2 -2
  389. tools/app_interface_reporter.py +4 -4
  390. tools/cli_commands/cost_report/cost_management_api.py +3 -3
  391. tools/cli_commands/cost_report/view.py +7 -6
  392. tools/cli_commands/erv2.py +1 -1
  393. tools/cli_commands/gpg_encrypt.py +4 -1
  394. tools/cli_commands/systems_and_tools.py +5 -1
  395. tools/qontract_cli.py +36 -21
  396. tools/sre_checkpoints/util.py +5 -3
  397. tools/template_validation.py +3 -1
  398. reconcile/gql_definitions/ocm_oidc_idp/__init__.py +0 -0
  399. reconcile/gql_definitions/ocm_subscription_labels/__init__.py +0 -0
  400. reconcile/jenkins/__init__.py +0 -0
  401. reconcile/jenkins/types.py +0 -77
  402. {qontract_reconcile-0.10.2.dev299.dist-info → qontract_reconcile-0.10.2.dev430.dist-info}/WHEEL +0 -0
  403. {qontract_reconcile-0.10.2.dev299.dist-info → qontract_reconcile-0.10.2.dev430.dist-info}/entry_points.txt +0 -0
@@ -1,4 +1,6 @@
1
1
  # ruff: noqa: N801
2
+ from __future__ import annotations
3
+
2
4
  import base64
3
5
  import enum
4
6
  import json
@@ -9,7 +11,6 @@ import re
9
11
  import string
10
12
  import tempfile
11
13
  from collections import Counter
12
- from collections.abc import Iterable, Mapping, MutableMapping
13
14
  from dataclasses import dataclass, field
14
15
  from ipaddress import (
15
16
  ip_address,
@@ -18,7 +19,9 @@ from ipaddress import (
18
19
  from json import JSONDecodeError
19
20
  from threading import Lock
20
21
  from typing import (
22
+ TYPE_CHECKING,
21
23
  Any,
24
+ Self,
22
25
  cast,
23
26
  )
24
27
 
@@ -34,12 +37,14 @@ from terrascript import (
34
37
  Backend,
35
38
  Block,
36
39
  Data,
40
+ Locals,
37
41
  Module,
38
42
  Output,
39
43
  Provider,
40
44
  Resource,
41
45
  Terraform,
42
46
  Terrascript,
47
+ Variable,
43
48
  data,
44
49
  provider,
45
50
  )
@@ -143,12 +148,10 @@ import reconcile.utils.aws_helper as awsh
143
148
  from reconcile import queries
144
149
  from reconcile.cli import TERRAFORM_VERSION
145
150
  from reconcile.github_org import get_default_config
146
- from reconcile.gql_definitions.fragments.aws_vpc_request import (
147
- VPCRequest,
148
- )
149
151
  from reconcile.gql_definitions.terraform_resources.terraform_resources_namespaces import (
150
152
  NamespaceTerraformResourceLifecycleV1,
151
153
  )
154
+ from reconcile.typed_queries.aws_account_tags import get_aws_account_tags
152
155
  from reconcile.utils import gql
153
156
  from reconcile.utils.aws_api import (
154
157
  AmiTag,
@@ -168,10 +171,6 @@ from reconcile.utils.exceptions import (
168
171
  FetchResourceError,
169
172
  PrintToFileInGitRepositoryError,
170
173
  )
171
- from reconcile.utils.external_resource_spec import (
172
- ExternalResourceSpec,
173
- ExternalResourceSpecInventory,
174
- )
175
174
  from reconcile.utils.external_resources import (
176
175
  PROVIDER_AWS,
177
176
  get_external_resource_specs,
@@ -179,8 +178,11 @@ from reconcile.utils.external_resources import (
179
178
  from reconcile.utils.git import is_file_in_git_repo
180
179
  from reconcile.utils.gitlab_api import GitLabApi
181
180
  from reconcile.utils.jenkins_api import JenkinsApi
182
- from reconcile.utils.jinja2.utils import process_extracurlyjinja2_template
183
- from reconcile.utils.ocm import OCMMap
181
+ from reconcile.utils.jinja2.utils import (
182
+ process_extracurlyjinja2_template,
183
+ process_jinja2_template,
184
+ )
185
+ from reconcile.utils.json import json_dumps
184
186
  from reconcile.utils.password_validator import (
185
187
  PasswordPolicy,
186
188
  PasswordValidator,
@@ -189,6 +191,25 @@ from reconcile.utils.secret_reader import SecretReader, SecretReaderBase
189
191
  from reconcile.utils.terraform import safe_resource_id
190
192
  from reconcile.utils.vcs import VCS
191
193
 
194
+ if TYPE_CHECKING:
195
+ from collections.abc import Iterable, Mapping, MutableMapping
196
+
197
+ from reconcile.gql_definitions.fragments.aws_vpc_request import (
198
+ VPCRequest,
199
+ )
200
+ from reconcile.terraform_tgw_attachments import DesiredStateItem
201
+ from reconcile.terraform_users import Role
202
+ from reconcile.utils.external_resource_spec import (
203
+ ExternalResourceSpec,
204
+ ExternalResourceSpecInventory,
205
+ )
206
+ from reconcile.utils.ocm import OCMMap
207
+
208
+
209
+ type TFResource = type[
210
+ Resource | Data | Module | Provider | Variable | Output | Locals | Terraform
211
+ ]
212
+
192
213
  GH_BASE_URL = os.environ.get("GITHUB_API", "https://api.github.com")
193
214
  ROSA_AUTH_LOGTOES_RELEASE = "repos/app-sre/logs-to-elasticsearch-lambda/releases/latest"
194
215
  ROSA_AUTH_KINESIS_TO_OS_RELEASE = (
@@ -250,6 +271,7 @@ VARIABLE_KEYS = [
250
271
  "extra_tags",
251
272
  "lifecycle",
252
273
  "max_session_duration",
274
+ "secret_format",
253
275
  ]
254
276
 
255
277
  EMAIL_REGEX = re.compile(r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$")
@@ -265,15 +287,10 @@ SUPPORTED_ALB_LISTENER_RULE_CONDITION_TYPE_MAPPING = {
265
287
  "host-header": "host_header",
266
288
  "http-request-method": "http_request_method",
267
289
  "path-pattern": "path_pattern",
290
+ "query-string": "query_string",
268
291
  "source-ip": "source_ip",
269
292
  }
270
293
 
271
- DEFAULT_TAGS = {
272
- "tags": {
273
- "app": "app-sre-infra",
274
- },
275
- }
276
-
277
294
  AWS_ELB_ACCOUNT_IDS = {
278
295
  "us-east-1": "127311923021",
279
296
  "us-east-2": "033677994240",
@@ -304,9 +321,12 @@ AWS_US_GOV_ELB_ACCOUNT_IDS = {
304
321
  "us-gov-east-1": "190560391635",
305
322
  }
306
323
 
324
+ VPC_REQUEST_DEFAULT_PRIVATE_SUBNET_TAGS = {"kubernetes.io/role/internal-elb": "1"}
325
+ VPC_REQUEST_DEFAULT_PUBLIC_SUBNET_TAGS = {"kubernetes.io/role/elb": "1"}
326
+
307
327
 
308
328
  class OutputResourceNameNotUniqueError(Exception):
309
- def __init__(self, namespace, duplicates):
329
+ def __init__(self, namespace: str | None, duplicates: Iterable[str]) -> None:
310
330
  self.namespace, self.duplicates = namespace, duplicates
311
331
  super().__init__(
312
332
  str.format(
@@ -326,7 +346,7 @@ class StateInaccessibleError(Exception):
326
346
 
327
347
 
328
348
  class UnknownProviderError(Exception):
329
- def __init__(self, msg):
349
+ def __init__(self, msg: str) -> None:
330
350
  super().__init__("unknown provider error: " + str(msg))
331
351
 
332
352
 
@@ -453,9 +473,10 @@ class TerrascriptClient:
453
473
  integration: str,
454
474
  integration_prefix: str,
455
475
  thread_pool_size: int,
456
- accounts: Iterable[dict[str, Any]],
476
+ accounts: Iterable[MutableMapping[str, Any]],
477
+ default_tags: Mapping[str, str] | None,
457
478
  settings: Mapping[str, Any] | None = None,
458
- prefetch_resources_by_schemas: list[str] | None = None,
479
+ prefetch_resources_by_schemas: Iterable[str] | None = None,
459
480
  secret_reader: SecretReaderBase | None = None,
460
481
  ) -> None:
461
482
  self.integration = integration
@@ -467,6 +488,7 @@ class TerrascriptClient:
467
488
  else:
468
489
  self.secret_reader = SecretReader(settings=settings)
469
490
  self.configs: dict[str, dict] = {}
491
+ self.default_tags = default_tags or {"app": "app-sre-infra"}
470
492
  self.populate_configs(filtered_accounts)
471
493
  self.versions: dict[str, str] = {
472
494
  a["name"]: a["providerVersion"] for a in filtered_accounts
@@ -487,7 +509,7 @@ class TerrascriptClient:
487
509
  region=region,
488
510
  alias=region,
489
511
  skip_region_validation=True,
490
- default_tags=DEFAULT_TAGS,
512
+ default_tags={"tags": config["tags"]},
491
513
  )
492
514
 
493
515
  # Add default region, which will be in resourcesDefaultRegion
@@ -496,7 +518,7 @@ class TerrascriptClient:
496
518
  secret_key=config["aws_secret_access_key"],
497
519
  region=config["resourcesDefaultRegion"],
498
520
  skip_region_validation=True,
499
- default_tags=DEFAULT_TAGS,
521
+ default_tags={"tags": config["tags"]},
500
522
  )
501
523
 
502
524
  ts += Terraform(
@@ -541,10 +563,10 @@ class TerrascriptClient:
541
563
  self.accounts = {a["name"]: a for a in filtered_accounts}
542
564
  self.uids = {a["name"]: a["uid"] for a in filtered_accounts}
543
565
  # default_regions info is needed in populate_tf_resource_rds, even in disabled accounts
544
- self.default_regions = {
566
+ self.default_regions: dict[str, str] = {
545
567
  a["name"]: a["resourcesDefaultRegion"] for a in accounts
546
568
  }
547
- self.partitions = {
569
+ self.partitions: dict[str, str] = {
548
570
  a["name"]: a.get("partition") or "aws" for a in filtered_accounts
549
571
  }
550
572
  self.rosa_auth_logtoes_zip = ""
@@ -566,19 +588,19 @@ class TerrascriptClient:
566
588
  for schema in prefetch_resources_by_schemas:
567
589
  self._resource_cache.update(self.prefetch_resources(schema))
568
590
 
569
- def __enter__(self):
591
+ def __enter__(self) -> Self:
570
592
  return self
571
593
 
572
- def __exit__(self, *exc):
594
+ def __exit__(self, *exc: Any) -> None:
573
595
  self.cleanup()
574
596
 
575
- def cleanup(self):
597
+ def cleanup(self) -> None:
576
598
  if self.gitlab is not None:
577
599
  self.gitlab.cleanup()
578
600
 
579
601
  @staticmethod
580
602
  def state_bucket_for_account(
581
- integration: str, account_name: str, config: dict[str, Any]
603
+ integration: str, account_name: str, config: Mapping[str, Any]
582
604
  ) -> Backend:
583
605
  # creds
584
606
  access_key_backend_value = config["aws_access_key_id"]
@@ -643,7 +665,7 @@ class TerrascriptClient:
643
665
  f.write(r.content)
644
666
  return zip_file
645
667
 
646
- def get_logtoes_zip(self, release_url):
668
+ def get_logtoes_zip(self, release_url: str) -> str:
647
669
  if not self.rosa_auth_logtoes_zip:
648
670
  with self.rosa_auth_logtoes_zip_lock:
649
671
  # this may have already happened, so we check again
@@ -656,7 +678,7 @@ class TerrascriptClient:
656
678
  return self.rosa_auth_logtoes_zip
657
679
  return self.download_logtoes_zip(release_url)
658
680
 
659
- def download_logtoes_zip(self, release_url):
681
+ def download_logtoes_zip(self, release_url: str) -> str:
660
682
  headers = {"Authorization": "token " + self.token}
661
683
  r = requests.get(GH_BASE_URL + "/" + release_url, headers=headers, timeout=60)
662
684
  r.raise_for_status()
@@ -670,7 +692,7 @@ class TerrascriptClient:
670
692
  f.write(r.content)
671
693
  return zip_file
672
694
 
673
- def get_rosa_auth_pre_signup_zip(self, release_url):
695
+ def get_rosa_auth_pre_signup_zip(self, release_url: str) -> str:
674
696
  if not self.rosa_auth_pre_signup_zip:
675
697
  with self.rosa_auth_pre_signup_zip_lock:
676
698
  # this may have already happened, so we check again
@@ -685,7 +707,7 @@ class TerrascriptClient:
685
707
  return self.rosa_auth_pre_signup_zip
686
708
  return self.download_rosa_auth_pre_signup_zip(release_url)
687
709
 
688
- def download_rosa_auth_pre_signup_zip(self, release_url):
710
+ def download_rosa_auth_pre_signup_zip(self, release_url: str) -> str:
689
711
  headers = {"Authorization": "token " + self.token}
690
712
  r = requests.get(GH_BASE_URL + "/" + release_url, headers=headers, timeout=60)
691
713
  r.raise_for_status()
@@ -699,7 +721,7 @@ class TerrascriptClient:
699
721
  f.write(r.content)
700
722
  return zip_file
701
723
 
702
- def get_rosa_auth_pre_token_zip(self, release_url):
724
+ def get_rosa_auth_pre_token_zip(self, release_url: str) -> str:
703
725
  if not self.rosa_auth_pre_token_zip:
704
726
  with self.rosa_auth_pre_token_zip_lock:
705
727
  # this may have already happened, so we check again
@@ -714,7 +736,7 @@ class TerrascriptClient:
714
736
  return self.rosa_auth_pre_token_zip
715
737
  return self.download_rosa_auth_pre_token_zip(release_url)
716
738
 
717
- def download_rosa_auth_pre_token_zip(self, release_url):
739
+ def download_rosa_auth_pre_token_zip(self, release_url: str) -> str:
718
740
  headers = {"Authorization": "token " + self.token}
719
741
  r = requests.get(GH_BASE_URL + "/" + release_url, headers=headers, timeout=60)
720
742
  r.raise_for_status()
@@ -744,7 +766,7 @@ class TerrascriptClient:
744
766
  self.gitlab = GitLabApi(instance, secret_reader=self.secret_reader)
745
767
  return self.gitlab
746
768
 
747
- def init_jenkins(self, instance: dict) -> JenkinsApi:
769
+ def init_jenkins(self, instance: Mapping[str, Any]) -> JenkinsApi:
748
770
  instance_name = instance["name"]
749
771
  if not self.jenkins_map.get(instance_name):
750
772
  with self.jenkins_lock:
@@ -758,8 +780,8 @@ class TerrascriptClient:
758
780
  return self.jenkins_map[instance_name]
759
781
 
760
782
  def filter_disabled_accounts(
761
- self, accounts: Iterable[dict[str, Any]]
762
- ) -> list[dict[str, Any]]:
783
+ self, accounts: Iterable[MutableMapping[str, Any]]
784
+ ) -> list[MutableMapping[str, Any]]:
763
785
  filtered_accounts = []
764
786
  for account in accounts:
765
787
  integration = self.integration.replace("_", "-")
@@ -767,7 +789,7 @@ class TerrascriptClient:
767
789
  filtered_accounts.append(account)
768
790
  return filtered_accounts
769
791
 
770
- def populate_configs(self, accounts: Iterable[awsh.Account]):
792
+ def populate_configs(self, accounts: Iterable[awsh.Account]) -> None:
771
793
  results = threaded.run(
772
794
  awsh.get_tf_secrets,
773
795
  accounts,
@@ -779,16 +801,19 @@ class TerrascriptClient:
779
801
  config["supportedDeploymentRegions"] = account["supportedDeploymentRegions"]
780
802
  config["resourcesDefaultRegion"] = account["resourcesDefaultRegion"]
781
803
  config["terraformState"] = account["terraformState"]
804
+ config["tags"] = dict(self.default_tags) | get_aws_account_tags(
805
+ account.get("organization", None)
806
+ )
782
807
  self.configs[account_name] = config
783
808
 
784
- def _get_partition(self, account):
809
+ def _get_partition(self, account: str) -> str:
785
810
  return self.partitions.get(account) or "aws"
786
811
 
787
812
  @staticmethod
788
- def get_tf_iam_group(group_name):
813
+ def get_tf_iam_group(group_name: str) -> aws_iam_group:
789
814
  return aws_iam_group(group_name, name=group_name)
790
815
 
791
- def get_tf_iam_user(self, user_name):
816
+ def get_tf_iam_user(self, user_name: str) -> aws_iam_user:
792
817
  return aws_iam_user(
793
818
  user_name,
794
819
  name=user_name,
@@ -796,8 +821,8 @@ class TerrascriptClient:
796
821
  tags={"managed_by_integration": self.integration},
797
822
  )
798
823
 
799
- def populate_iam_groups(self, roles):
800
- groups = {}
824
+ def populate_iam_groups(self, roles: Iterable[Role]) -> dict[str, dict[str, str]]:
825
+ groups: dict[str, dict[str, str]] = {}
801
826
  for role in roles:
802
827
  users = role["users"]
803
828
  if len(users) == 0:
@@ -840,7 +865,7 @@ class TerrascriptClient:
840
865
  return groups
841
866
 
842
867
  @staticmethod
843
- def _get_aws_username(user):
868
+ def _get_aws_username(user: Mapping[str, str]) -> str:
844
869
  return user.get("aws_username") or user["org_username"]
845
870
 
846
871
  @staticmethod
@@ -865,10 +890,10 @@ class TerrascriptClient:
865
890
 
866
891
  def populate_iam_users(
867
892
  self,
868
- roles,
869
- skip_reencrypt_accounts: list[str],
893
+ roles: Iterable[Role],
894
+ skip_reencrypt_accounts: Iterable[str],
870
895
  appsre_pgp_key: str | None,
871
- ):
896
+ ) -> bool:
872
897
  error = False
873
898
  for role in roles:
874
899
  users = role["users"]
@@ -997,10 +1022,10 @@ class TerrascriptClient:
997
1022
 
998
1023
  def populate_users(
999
1024
  self,
1000
- roles,
1001
- skip_reencrypt_accounts: list[str],
1025
+ roles: Iterable[Role],
1026
+ skip_reencrypt_accounts: Iterable[str],
1002
1027
  appsre_pgp_key: str | None = None,
1003
- ):
1028
+ ) -> bool:
1004
1029
  self.populate_iam_groups(roles)
1005
1030
  err = self.populate_iam_users(
1006
1031
  roles,
@@ -1019,7 +1044,7 @@ class TerrascriptClient:
1019
1044
 
1020
1045
  @staticmethod
1021
1046
  def get_resource_lifecycle(
1022
- common_values: dict[str, Any],
1047
+ common_values: Mapping[str, Any],
1023
1048
  ) -> dict[str, Any] | None:
1024
1049
  if lifecycle := common_values.get("lifecycle"):
1025
1050
  lifecycle = NamespaceTerraformResourceLifecycleV1(**lifecycle)
@@ -1033,10 +1058,14 @@ class TerrascriptClient:
1033
1058
  ignore_changes = (
1034
1059
  "all" if "all" in lifecycle.ignore_changes else lifecycle.ignore_changes
1035
1060
  )
1036
- return lifecycle.dict(by_alias=True) | {"ignore_changes": ignore_changes}
1061
+ return lifecycle.model_dump(by_alias=True) | {
1062
+ "ignore_changes": ignore_changes
1063
+ }
1037
1064
  return None
1038
1065
 
1039
- def populate_additional_providers(self, infra_account_name: str, accounts):
1066
+ def populate_additional_providers(
1067
+ self, infra_account_name: str, accounts: Iterable[Mapping[str, Any]]
1068
+ ) -> None:
1040
1069
  for account in accounts:
1041
1070
  account_name = account["name"]
1042
1071
  assume_role = account.get("assume_role")
@@ -1046,28 +1075,18 @@ class TerrascriptClient:
1046
1075
  config = self.configs[account_name]
1047
1076
  existing_provider_aliases = {p.get("alias") for p in ts["provider"]["aws"]}
1048
1077
  if alias not in existing_provider_aliases:
1049
- if assume_role:
1050
- ts += provider.aws(
1051
- access_key=config["aws_access_key_id"],
1052
- secret_key=config["aws_secret_access_key"],
1053
- region=region,
1054
- alias=alias,
1055
- assume_role={"role_arn": assume_role},
1056
- skip_region_validation=True,
1057
- default_tags=DEFAULT_TAGS,
1058
- )
1059
- else:
1060
- ts += provider.aws(
1061
- access_key=config["aws_access_key_id"],
1062
- secret_key=config["aws_secret_access_key"],
1063
- region=region,
1064
- alias=alias,
1065
- skip_region_validation=True,
1066
- default_tags=DEFAULT_TAGS,
1067
- )
1078
+ ts += provider.aws(
1079
+ access_key=config["aws_access_key_id"],
1080
+ secret_key=config["aws_secret_access_key"],
1081
+ region=region,
1082
+ alias=alias,
1083
+ skip_region_validation=True,
1084
+ default_tags={"tags": config["tags"]},
1085
+ **{"assume_role": {"role_arn": assume_role}} if assume_role else {},
1086
+ )
1068
1087
 
1069
1088
  def populate_route53(
1070
- self, desired_state: Iterable[dict[str, Any]], default_ttl: int = 300
1089
+ self, desired_state: Iterable[Mapping[str, Any]], default_ttl: int = 300
1071
1090
  ) -> None:
1072
1091
  for zone in desired_state:
1073
1092
  acct_name = zone["account_name"]
@@ -1086,10 +1105,10 @@ class TerrascriptClient:
1086
1105
  def populate_route53_records(
1087
1106
  self,
1088
1107
  acct_name: str,
1089
- zone: dict[str, Any],
1108
+ zone: Mapping[str, Any],
1090
1109
  zone_resource: aws_route53_zone,
1091
1110
  default_ttl: int = 300,
1092
- ):
1111
+ ) -> None:
1093
1112
  counts = {}
1094
1113
  for record in zone.get("records") or []:
1095
1114
  record_fqdn = f"{record['name']}.{zone['name']}"
@@ -1166,7 +1185,7 @@ class TerrascriptClient:
1166
1185
  record_resource = aws_route53_record(record_id, **record_values)
1167
1186
  self.add_resource(acct_name, record_resource)
1168
1187
 
1169
- def populate_vpc_peerings(self, desired_state):
1188
+ def populate_vpc_peerings(self, desired_state: Iterable[Mapping[str, Any]]) -> None:
1170
1189
  for item in desired_state:
1171
1190
  if item["deleted"]:
1172
1191
  continue
@@ -1187,7 +1206,7 @@ class TerrascriptClient:
1187
1206
 
1188
1207
  # Requester's side of the connection - the cluster's account
1189
1208
  identifier = f"{requester['vpc_id']}-{accepter['vpc_id']}"
1190
- values = {
1209
+ values: dict[str, Any] = {
1191
1210
  # adding the alias to the provider will add this resource
1192
1211
  # to the cluster's AWS account
1193
1212
  "provider": "aws." + req_alias,
@@ -1308,25 +1327,32 @@ class TerrascriptClient:
1308
1327
  "version": vpc_module_version,
1309
1328
  "name": request.identifier,
1310
1329
  "cidr": request.cidr_block.network_address,
1311
- "private_subnet_tags": {"kubernetes.io/role/internal-elb": "1"},
1312
- "public_subnet_tags": {"kubernetes.io/role/elb": "1"},
1313
1330
  "create_database_subnet_group": False,
1314
1331
  "enable_dns_hostnames": True,
1332
+ "vpc_tags": request.vpc_tags or {},
1315
1333
  "tags": {
1316
1334
  "managed_by_integration": self.integration,
1317
1335
  },
1318
1336
  }
1319
1337
 
1320
- if request.subnets and request.subnets.public:
1321
- vpc_module_values["public_subnets"] = request.subnets.public
1322
- if request.subnets and request.subnets.private:
1323
- vpc_module_values["private_subnets"] = request.subnets.private
1324
- if request.subnets and request.subnets.availability_zones:
1325
- vpc_module_values["azs"] = request.subnets.availability_zones
1326
-
1327
- # We only want to enable nat_gateway if we have public and private subnets
1328
- if request.subnets and request.subnets.public and request.subnets.private:
1329
- vpc_module_values["enable_nat_gateway"] = True
1338
+ if request.subnets:
1339
+ if request.subnets.public:
1340
+ vpc_module_values["public_subnets"] = request.subnets.public
1341
+ vpc_module_values["public_subnet_tags"] = (
1342
+ VPC_REQUEST_DEFAULT_PUBLIC_SUBNET_TAGS
1343
+ | (request.subnets.public_subnet_tags or {})
1344
+ )
1345
+ if request.subnets.private:
1346
+ vpc_module_values["private_subnets"] = request.subnets.private
1347
+ vpc_module_values["private_subnet_tags"] = (
1348
+ VPC_REQUEST_DEFAULT_PRIVATE_SUBNET_TAGS
1349
+ | (request.subnets.private_subnet_tags or {})
1350
+ )
1351
+ if request.subnets.availability_zones:
1352
+ vpc_module_values["azs"] = request.subnets.availability_zones
1353
+ # We only want to enable nat_gateway if we have public and private subnets
1354
+ if request.subnets.public and request.subnets.private:
1355
+ vpc_module_values["enable_nat_gateway"] = True
1330
1356
 
1331
1357
  aws_account = request.account.name
1332
1358
  vpc_module = Module(request.identifier, **vpc_module_values)
@@ -1367,21 +1393,24 @@ class TerrascriptClient:
1367
1393
  )
1368
1394
  self.add_resource(aws_account, vpc_cidr_block_output)
1369
1395
 
1370
- if request.subnets and request.subnets.private:
1371
- private_subnets_output = Output(
1372
- f"{request.identifier}-private_subnets",
1373
- value=f"${{module.{request.identifier}.private_subnets}}",
1374
- )
1375
- self.add_resource(aws_account, private_subnets_output)
1396
+ if request.subnets:
1397
+ if request.subnets.private:
1398
+ private_subnets_output = Output(
1399
+ f"{request.identifier}-private_subnets",
1400
+ value=f"${{module.{request.identifier}.private_subnets}}",
1401
+ )
1402
+ self.add_resource(aws_account, private_subnets_output)
1376
1403
 
1377
- if request.subnets and request.subnets.public:
1378
- public_subnets_output = Output(
1379
- f"{request.identifier}-public_subnets",
1380
- value=f"${{module.{request.identifier}.public_subnets}}",
1381
- )
1382
- self.add_resource(aws_account, public_subnets_output)
1404
+ if request.subnets.public:
1405
+ public_subnets_output = Output(
1406
+ f"{request.identifier}-public_subnets",
1407
+ value=f"${{module.{request.identifier}.public_subnets}}",
1408
+ )
1409
+ self.add_resource(aws_account, public_subnets_output)
1383
1410
 
1384
- def populate_tgw_attachments(self, desired_state):
1411
+ def populate_tgw_attachments(
1412
+ self, desired_state: Iterable[DesiredStateItem]
1413
+ ) -> None:
1385
1414
  for item in desired_state:
1386
1415
  if item.deleted:
1387
1416
  continue
@@ -1397,37 +1426,39 @@ class TerrascriptClient:
1397
1426
  req_account_name = req_account.name
1398
1427
  # Accepter's side of the connection - the cluster's account
1399
1428
  acc_account = accepter.account
1400
- acc_alias = self.get_provider_alias(acc_account.dict(by_alias=True))
1429
+ acc_alias = self.get_provider_alias(acc_account.model_dump(by_alias=True))
1401
1430
  acc_uid = acc_account.uid
1402
1431
  if acc_account.assume_role:
1403
1432
  acc_uid = awsh.get_account_uid_from_arn(acc_account.assume_role)
1404
1433
 
1405
1434
  tags = {"managed_by_integration": self.integration, "Name": connection_name}
1406
1435
  # add resource share
1407
- values = {
1436
+ values_share: dict[str, Any] = {
1408
1437
  "name": connection_name,
1409
1438
  "allow_external_principals": True,
1410
1439
  "tags": tags,
1411
1440
  }
1412
1441
  if self._multiregion_account(req_account_name):
1413
- values["provider"] = "aws." + requester.region
1414
- tf_resource_share = aws_ram_resource_share(connection_name, **values)
1442
+ values_share["provider"] = "aws." + requester.region
1443
+ tf_resource_share = aws_ram_resource_share(connection_name, **values_share)
1415
1444
  self.add_resource(infra_account_name, tf_resource_share)
1416
1445
 
1417
1446
  # share with accepter aws account
1418
- values = {
1447
+ values_resource_principal_association: dict[str, str] = {
1419
1448
  "principal": acc_uid,
1420
1449
  "resource_share_arn": "${" + tf_resource_share.arn + "}",
1421
1450
  }
1422
1451
  if self._multiregion_account(req_account_name):
1423
- values["provider"] = "aws." + requester.region
1424
- tf_resource_association = aws_ram_principal_association(
1425
- connection_name, **values
1452
+ values_resource_principal_association["provider"] = (
1453
+ "aws." + requester.region
1454
+ )
1455
+ tf_resource_principal_association = aws_ram_principal_association(
1456
+ connection_name, **values_resource_principal_association
1426
1457
  )
1427
- self.add_resource(infra_account_name, tf_resource_association)
1458
+ self.add_resource(infra_account_name, tf_resource_principal_association)
1428
1459
 
1429
1460
  # accept resource share from accepter aws account
1430
- values = {
1461
+ values_share_resource_accepter: dict[str, Any] = {
1431
1462
  "provider": "aws." + acc_alias,
1432
1463
  "share_arn": "${" + tf_resource_share.arn + "}",
1433
1464
  "depends_on": [
@@ -1436,7 +1467,7 @@ class TerrascriptClient:
1436
1467
  ],
1437
1468
  }
1438
1469
  tf_resource_share_accepter = aws_ram_resource_share_accepter(
1439
- connection_name, **values
1470
+ connection_name, **values_share_resource_accepter
1440
1471
  )
1441
1472
  self.add_resource(infra_account_name, tf_resource_share_accepter)
1442
1473
 
@@ -1446,20 +1477,23 @@ class TerrascriptClient:
1446
1477
 
1447
1478
  # tgw share association
1448
1479
  identifier = f"{requester.tgw_id}-{accepter.vpc_id}"
1449
- values = {
1480
+ values_resource_association: dict[str, str] = {
1450
1481
  "resource_arn": requester.tgw_arn,
1451
1482
  "resource_share_arn": "${" + tf_resource_share.arn + "}",
1452
1483
  }
1453
1484
  if self._multiregion_account(req_account_name):
1454
- values["provider"] = "aws." + requester.region
1455
- tf_resource_association = aws_ram_resource_association(identifier, **values)
1485
+ values_resource_association["provider"] = "aws." + requester.region
1486
+ tf_resource_association = aws_ram_resource_association(
1487
+ identifier, **values_resource_association
1488
+ )
1456
1489
  self.add_resource(infra_account_name, tf_resource_association)
1457
1490
 
1458
1491
  # now that the tgw is shared to the cluster's aws account
1459
1492
  # we can create a vpc attachment to the tgw
1460
1493
  subnets_id_az = accepter.subnets_id_az
1494
+ assert subnets_id_az is not None # make mypy happy
1461
1495
  subnets = self.get_az_unique_subnet_ids(subnets_id_az)
1462
- values = {
1496
+ values_resource_attachment: dict[str, Any] = {
1463
1497
  "provider": "aws." + acc_alias,
1464
1498
  "subnet_ids": subnets,
1465
1499
  "transit_gateway_id": requester.tgw_id,
@@ -1471,20 +1505,22 @@ class TerrascriptClient:
1471
1505
  "tags": tags,
1472
1506
  }
1473
1507
  tf_resource_attachment = aws_ec2_transit_gateway_vpc_attachment(
1474
- identifier, **values
1508
+ identifier, **values_resource_attachment
1475
1509
  )
1476
1510
  # we send the attachment from the cluster's aws account
1477
1511
  self.add_resource(infra_account_name, tf_resource_attachment)
1478
1512
 
1479
1513
  # and accept the attachment in the non cluster's aws account
1480
- values = {
1514
+ values_attachment_accepter = {
1481
1515
  "transit_gateway_attachment_id": "${" + tf_resource_attachment.id + "}",
1482
1516
  "tags": tags,
1483
1517
  }
1484
1518
  if self._multiregion_account(req_account_name):
1485
- values["provider"] = "aws." + requester.region
1519
+ values_attachment_accepter["provider"] = "aws." + requester.region
1486
1520
  tf_resource_attachment_accepter = (
1487
- aws_ec2_transit_gateway_vpc_attachment_accepter(identifier, **values)
1521
+ aws_ec2_transit_gateway_vpc_attachment_accepter(
1522
+ identifier, **values_attachment_accepter
1523
+ )
1488
1524
  )
1489
1525
  self.add_resource(infra_account_name, tf_resource_attachment_accepter)
1490
1526
 
@@ -1530,16 +1566,16 @@ class TerrascriptClient:
1530
1566
  + f"unsupported region: {route_region}"
1531
1567
  )
1532
1568
  continue
1533
- values = {
1569
+ values_gateway_router = {
1534
1570
  "destination_cidr_block": route["cidr_block"],
1535
1571
  "transit_gateway_attachment_id": route["tgw_attachment_id"],
1536
1572
  "transit_gateway_route_table_id": route["tgw_route_table_id"],
1537
1573
  }
1538
1574
  if self._multiregion_account(req_account_name):
1539
- values["provider"] = "aws." + route_region
1575
+ values_gateway_router["provider"] = "aws." + route_region
1540
1576
  route_identifier = f"{identifier}-{route['tgw_id']}"
1541
1577
  tf_resource = aws_ec2_transit_gateway_route(
1542
- route_identifier, **values
1578
+ route_identifier, **values_gateway_router
1543
1579
  )
1544
1580
  self.add_resource(infra_account_name, tf_resource)
1545
1581
 
@@ -1569,7 +1605,7 @@ class TerrascriptClient:
1569
1605
  + f"unsupported region: {rule_region}"
1570
1606
  )
1571
1607
  continue
1572
- values = {
1608
+ values_rule = {
1573
1609
  "type": "ingress",
1574
1610
  "from_port": 0,
1575
1611
  "to_port": 0,
@@ -1578,31 +1614,37 @@ class TerrascriptClient:
1578
1614
  "security_group_id": rule["security_group_id"],
1579
1615
  }
1580
1616
  if self._multiregion_account(req_account_name):
1581
- values["provider"] = "aws." + rule_region
1617
+ values_rule["provider"] = "aws." + rule_region
1582
1618
  rule_identifier = f"{identifier}-{rule['vpc_id']}"
1583
- tf_resource = aws_security_group_rule(rule_identifier, **values)
1619
+ tf_resource = aws_security_group_rule(
1620
+ rule_identifier, **values_rule
1621
+ )
1584
1622
  self.add_resource(infra_account_name, tf_resource)
1585
1623
 
1586
1624
  for zone in requester.hostedzones or []:
1587
1625
  id = f"{identifier}-{zone}"
1588
- values = {
1626
+ values_authorization = {
1589
1627
  "vpc_id": accepter.vpc_id,
1590
1628
  "vpc_region": accepter.region,
1591
1629
  "zone_id": zone,
1592
1630
  }
1593
- authorization = aws_route53_vpc_association_authorization(id, **values)
1631
+ authorization = aws_route53_vpc_association_authorization(
1632
+ id, **values_authorization
1633
+ )
1594
1634
  self.add_resource(infra_account_name, authorization)
1595
- values = {
1635
+ values_association = {
1596
1636
  "provider": "aws." + acc_alias,
1597
1637
  "vpc_id": f"${{aws_route53_vpc_association_authorization.{id}.vpc_id}}",
1598
1638
  "vpc_region": accepter.region,
1599
1639
  "zone_id": f"${{aws_route53_vpc_association_authorization.{id}.zone_id}}",
1600
1640
  }
1601
- association = aws_route53_zone_association(id, **values)
1641
+ association = aws_route53_zone_association(id, **values_association)
1602
1642
  self.add_resource(infra_account_name, association)
1603
1643
 
1604
1644
  @staticmethod
1605
- def get_az_unique_subnet_ids(subnets_id_az):
1645
+ def get_az_unique_subnet_ids(
1646
+ subnets_id_az: Iterable[Mapping[str, str]],
1647
+ ) -> list[str]:
1606
1648
  """returns a list of subnet ids which are unique per az"""
1607
1649
  results = []
1608
1650
  azs = []
@@ -1710,7 +1752,9 @@ class TerrascriptClient:
1710
1752
 
1711
1753
  self.resource_spec_inventory[spec.id_object()] = spec
1712
1754
 
1713
- def populate_tf_resources(self, spec, ocm_map=None):
1755
+ def populate_tf_resources(
1756
+ self, spec: ExternalResourceSpec, ocm_map: OCMMap | None = None
1757
+ ) -> None:
1714
1758
  if spec.provision_provider != PROVIDER_AWS:
1715
1759
  raise UnknownProviderError(spec.provision_provider)
1716
1760
 
@@ -1769,13 +1813,13 @@ class TerrascriptClient:
1769
1813
  else:
1770
1814
  raise UnknownProviderError(provider)
1771
1815
 
1772
- def populate_tf_resource_rds(self, spec):
1816
+ def populate_tf_resource_rds(self, spec: ExternalResourceSpec) -> None:
1773
1817
  account = spec.provisioner_name
1774
1818
  identifier = spec.identifier
1775
1819
  values = self.init_values(spec)
1776
1820
  output_prefix = spec.output_prefix
1777
1821
 
1778
- tf_resources = []
1822
+ tf_resources: list[TFResource] = []
1779
1823
  self.init_common_outputs(tf_resources, spec)
1780
1824
 
1781
1825
  # we want to allow an empty name, so we
@@ -1804,7 +1848,9 @@ class TerrascriptClient:
1804
1848
  # To get the provider we should use, we get the region
1805
1849
  # and use that as an alias in the provider definition
1806
1850
  if az:
1807
- provider = "aws." + self._region_from_availability_zone(az)
1851
+ region_az = self._region_from_availability_zone(az)
1852
+ assert region_az # make mypy happy
1853
+ provider = "aws." + region_az
1808
1854
  values["provider"] = provider
1809
1855
  if region:
1810
1856
  provider_region = f"aws.{region}"
@@ -1844,7 +1890,7 @@ class TerrascriptClient:
1844
1890
  # 'deps' should contain a list of terraform resource names
1845
1891
  # (not full objects) that must be created
1846
1892
  # before the actual RDS instance should be created
1847
- deps = []
1893
+ deps: list[str] = []
1848
1894
 
1849
1895
  parameter_group = values.pop("parameter_group", None)
1850
1896
  if parameter_group:
@@ -1897,7 +1943,7 @@ class TerrascriptClient:
1897
1943
  em_identifier = f"{identifier}-enhanced-monitoring"
1898
1944
  em_values = {
1899
1945
  "name": em_identifier,
1900
- "assume_role_policy": json.dumps(assume_role_policy, sort_keys=True),
1946
+ "assume_role_policy": json_dumps(assume_role_policy),
1901
1947
  }
1902
1948
  role_tf_resource = aws_iam_role(em_identifier, **em_values)
1903
1949
  tf_resources.append(role_tf_resource)
@@ -1905,14 +1951,14 @@ class TerrascriptClient:
1905
1951
  role_res_name = self.get_dependencies([role_tf_resource])[0]
1906
1952
  deps.append(role_res_name)
1907
1953
 
1908
- em_values = {
1954
+ em_values_attachment: dict[str, Any] = {
1909
1955
  "role": role_tf_resource.name,
1910
1956
  "policy_arn": f"arn:{self._get_partition(account)}:iam::aws:policy/service-role/"
1911
1957
  + "AmazonRDSEnhancedMonitoringRole",
1912
1958
  "depends_on": self.get_dependencies([role_tf_resource]),
1913
1959
  }
1914
1960
  attachment_tf_resource = aws_iam_role_policy_attachment(
1915
- em_identifier, **em_values
1961
+ em_identifier, **em_values_attachment
1916
1962
  )
1917
1963
  tf_resources.append(attachment_tf_resource)
1918
1964
 
@@ -1930,9 +1976,8 @@ class TerrascriptClient:
1930
1976
  if reset_password:
1931
1977
  password = self.generate_random_password()
1932
1978
  else:
1933
- password = spec.get_secret_field("db.password")
1934
- if not password:
1935
- password = self.generate_random_password()
1979
+ db_password = spec.get_secret_field("db.password")
1980
+ password = db_password or self.generate_random_password()
1936
1981
  else:
1937
1982
  password = ""
1938
1983
  values["password"] = password
@@ -2024,7 +2069,7 @@ class TerrascriptClient:
2024
2069
  # to a provider version with this bug fix.
2025
2070
  # https://github.com/hashicorp/terraform-provider-aws/pull/20926
2026
2071
  if enhanced_monitoring and replica_source:
2027
- sleep_vals = {}
2072
+ sleep_vals: dict[str, Any] = {}
2028
2073
  sleep_vals["depends_on"] = [attachment_res_name]
2029
2074
  sleep_vals["create_duration"] = "30s"
2030
2075
 
@@ -2117,7 +2162,7 @@ class TerrascriptClient:
2117
2162
 
2118
2163
  self.add_resources(account, tf_resources)
2119
2164
 
2120
- def _multiregion_account(self, name):
2165
+ def _multiregion_account(self, name: str) -> bool:
2121
2166
  if name not in self.configs:
2122
2167
  return False
2123
2168
 
@@ -2134,11 +2179,11 @@ class TerrascriptClient:
2134
2179
  return spec
2135
2180
  return None
2136
2181
 
2137
- def _get_db_name_from_values(self, values: dict) -> str:
2182
+ def _get_db_name_from_values(self, values: Mapping[str, Any]) -> str:
2138
2183
  return values.get("name") or values.get("db_name") or ""
2139
2184
 
2140
2185
  @staticmethod
2141
- def _region_from_availability_zone(az):
2186
+ def _region_from_availability_zone(az: str) -> str | None:
2142
2187
  # Find the region by removing the last character from the
2143
2188
  # availability zone. Availability zone is defined like
2144
2189
  # us-east-1a, us-east-1b, etc. If there is no availability
@@ -2148,39 +2193,75 @@ class TerrascriptClient:
2148
2193
  return None
2149
2194
 
2150
2195
  @staticmethod
2151
- def _db_needs_auth(config):
2152
- return bool(
2153
- "replicate_source_db" not in config
2154
- and config.get("replica_source", None) is None
2196
+ def _db_needs_auth(config: Mapping[str, Any]) -> bool:
2197
+ return (
2198
+ "replicate_source_db" not in config and config.get("replica_source") is None
2155
2199
  )
2156
2200
 
2157
2201
  @staticmethod
2158
- def validate_db_name(name):
2202
+ def validate_db_name(name: str) -> bool:
2159
2203
  """Handle for Error creating DB Instance:
2160
2204
  InvalidParameterValue: DBName must begin with a letter
2161
2205
  and contain only alphanumeric characters."""
2162
2206
  pattern = r"^[a-zA-Z][a-zA-Z0-9_]+$"
2163
- return re.search(pattern, name) and len(name) < 64
2207
+ return len(name) < 64 and re.search(pattern, name) is not None
2164
2208
 
2165
2209
  @staticmethod
2166
- def generate_random_password(string_length=20):
2210
+ def generate_random_password(string_length: int = 20) -> str:
2167
2211
  """Generate a random string of letters and digits"""
2168
2212
  letters_and_digits = string.ascii_letters + string.digits
2169
2213
  return "".join(random.choice(letters_and_digits) for i in range(string_length))
2170
2214
 
2171
- def populate_tf_resource_s3(self, spec):
2215
+ @staticmethod
2216
+ def _build_tf_resource_s3_lifecycle_rules(
2217
+ versioning: bool,
2218
+ common_values: Mapping[str, Any],
2219
+ ) -> list[dict]:
2220
+ lifecycle_rules = common_values.get("lifecycle_rules") or []
2221
+ if versioning and not any(
2222
+ "noncurrent_version_expiration" in lr for lr in lifecycle_rules
2223
+ ):
2224
+ # Add a default noncurrent object expiration rule
2225
+ # if one isn't already set
2226
+ rule = {
2227
+ "id": "expire_noncurrent_versions",
2228
+ "enabled": True,
2229
+ "noncurrent_version_expiration": {"days": 30},
2230
+ "expiration": {"expired_object_delete_marker": True},
2231
+ "abort_incomplete_multipart_upload_days": 3,
2232
+ }
2233
+ lifecycle_rules.append(rule)
2234
+
2235
+ if storage_class := common_values.get("storage_class"):
2236
+ sc = storage_class.upper()
2237
+ days = "1"
2238
+ if sc.endswith("_IA"):
2239
+ # Infrequent Access storage class has minimum 30 days
2240
+ # before transition
2241
+ days = "30"
2242
+ rule = {
2243
+ "id": sc + "_storage_class",
2244
+ "enabled": True,
2245
+ "transition": {"days": days, "storage_class": sc},
2246
+ "noncurrent_version_transition": {"days": days, "storage_class": sc},
2247
+ }
2248
+ lifecycle_rules.append(rule)
2249
+
2250
+ return lifecycle_rules
2251
+
2252
+ def populate_tf_resource_s3(self, spec: ExternalResourceSpec) -> aws_s3_bucket:
2172
2253
  account = spec.provisioner_name
2173
2254
  identifier = spec.identifier
2174
2255
  common_values = self.init_values(spec)
2175
2256
  output_prefix = spec.output_prefix
2176
2257
 
2177
- tf_resources = []
2258
+ tf_resources: list[TFResource] = []
2178
2259
  self.init_common_outputs(tf_resources, spec)
2179
2260
 
2180
2261
  # s3 bucket
2181
2262
  # Terraform resource reference:
2182
2263
  # https://www.terraform.io/docs/providers/aws/r/s3_bucket.html
2183
- values = {}
2264
+ values: dict[str, Any] = {}
2184
2265
  values["bucket"] = identifier
2185
2266
  versioning = common_values.get("versioning", True)
2186
2267
  values["versioning"] = {"enabled": versioning}
@@ -2207,47 +2288,11 @@ class TerrascriptClient:
2207
2288
  request_payer = common_values.get("request_payer")
2208
2289
  if request_payer:
2209
2290
  values["request_payer"] = request_payer
2210
- lifecycle_rules = common_values.get("lifecycle_rules")
2211
- if lifecycle_rules:
2212
- # common_values['lifecycle_rules'] is a list of lifecycle_rules
2291
+ if lifecycle_rules := self._build_tf_resource_s3_lifecycle_rules(
2292
+ versioning=versioning,
2293
+ common_values=common_values,
2294
+ ):
2213
2295
  values["lifecycle_rule"] = lifecycle_rules
2214
- if versioning:
2215
- lrs = values.get("lifecycle_rule", [])
2216
- expiration_rule = False
2217
- for lr in lrs:
2218
- if "noncurrent_version_expiration" in lr:
2219
- expiration_rule = True
2220
- break
2221
- if not expiration_rule:
2222
- # Add a default noncurrent object expiration rule if
2223
- # if one isn't already set
2224
- rule = {
2225
- "id": "expire_noncurrent_versions",
2226
- "enabled": "true",
2227
- "noncurrent_version_expiration": {"days": 30},
2228
- }
2229
- if len(lrs) > 0:
2230
- lrs.append(rule)
2231
- else:
2232
- lrs = rule
2233
- sc = common_values.get("storage_class")
2234
- if sc:
2235
- sc = sc.upper()
2236
- days = "1"
2237
- if sc.endswith("_IA"):
2238
- # Infrequent Access storage class has minimum 30 days
2239
- # before transition
2240
- days = "30"
2241
- rule = {
2242
- "id": sc + "_storage_class",
2243
- "enabled": "true",
2244
- "transition": {"days": days, "storage_class": sc},
2245
- "noncurrent_version_transition": {"days": days, "storage_class": sc},
2246
- }
2247
- if values.get("lifecycle_rule"):
2248
- values["lifecycle_rule"].append(rule)
2249
- else:
2250
- values["lifecycle_rule"] = rule
2251
2296
  cors_rules = common_values.get("cors_rules")
2252
2297
  if cors_rules:
2253
2298
  # common_values['cors_rules'] is a list of cors_rules
@@ -2297,7 +2342,7 @@ class TerrascriptClient:
2297
2342
  }
2298
2343
  ],
2299
2344
  }
2300
- rc_values["assume_role_policy"] = json.dumps(role, sort_keys=True)
2345
+ rc_values["assume_role_policy"] = json_dumps(role)
2301
2346
  role_resource = aws_iam_role(id, **rc_values)
2302
2347
  tf_resources.append(role_resource)
2303
2348
 
@@ -2307,7 +2352,7 @@ class TerrascriptClient:
2307
2352
 
2308
2353
  rc_values.clear()
2309
2354
  rc_values["name"] = config["rule_name"] + "_iam_policy"
2310
- policy = {
2355
+ policy: dict[str, Any] = {
2311
2356
  "Version": "2012-10-17",
2312
2357
  "Statement": [
2313
2358
  {
@@ -2335,7 +2380,7 @@ class TerrascriptClient:
2335
2380
  },
2336
2381
  ],
2337
2382
  }
2338
- rc_values["policy"] = json.dumps(policy, sort_keys=True)
2383
+ rc_values["policy"] = json_dumps(policy)
2339
2384
  policy_resource = aws_iam_policy(id, **rc_values)
2340
2385
  tf_resources.append(policy_resource)
2341
2386
 
@@ -2374,6 +2419,7 @@ class TerrascriptClient:
2374
2419
  if len(deps) > 0:
2375
2420
  values["depends_on"] = self.get_dependencies(deps)
2376
2421
  region = common_values.get("region") or self.default_regions.get(account)
2422
+ assert region # make mypy happy
2377
2423
  if self._multiregion_account(account):
2378
2424
  values["provider"] = "aws." + region
2379
2425
  bucket_tf_resource = aws_s3_bucket(identifier, **values)
@@ -2508,10 +2554,12 @@ class TerrascriptClient:
2508
2554
  # https://www.terraform.io/docs/providers/aws/r/iam_access_key.html
2509
2555
 
2510
2556
  # iam user for bucket
2511
- values = {}
2512
- values["name"] = identifier
2513
- values["tags"] = common_values["tags"]
2514
- values["depends_on"] = self.get_dependencies([bucket_tf_resource])
2557
+ values = {
2558
+ "name": identifier,
2559
+ "tags": common_values["tags"],
2560
+ "depends_on": self.get_dependencies([bucket_tf_resource]),
2561
+ }
2562
+
2515
2563
  user_tf_resource = aws_iam_user(identifier, **values)
2516
2564
  tf_resources.append(user_tf_resource)
2517
2565
 
@@ -2521,8 +2569,7 @@ class TerrascriptClient:
2521
2569
  )
2522
2570
 
2523
2571
  # iam user policy for bucket
2524
- values = {}
2525
- values["name"] = identifier
2572
+ values = {"name": identifier}
2526
2573
 
2527
2574
  action = ["s3:*Object*"]
2528
2575
  if common_values.get("acl", "private") == "public-read":
@@ -2548,7 +2595,7 @@ class TerrascriptClient:
2548
2595
  },
2549
2596
  ],
2550
2597
  }
2551
- values["policy"] = json.dumps(policy, sort_keys=True)
2598
+ values["policy"] = json_dumps(policy)
2552
2599
  values["depends_on"] = self.get_dependencies([user_tf_resource])
2553
2600
 
2554
2601
  tf_aws_iam_policy = aws_iam_policy(identifier, **values)
@@ -2566,7 +2613,7 @@ class TerrascriptClient:
2566
2613
 
2567
2614
  return bucket_tf_resource
2568
2615
 
2569
- def populate_tf_resource_elasticache(self, spec):
2616
+ def populate_tf_resource_elasticache(self, spec: ExternalResourceSpec) -> None:
2570
2617
  account = spec.provisioner_name
2571
2618
  identifier = spec.identifier
2572
2619
  values = self.init_values(spec)
@@ -2574,7 +2621,7 @@ class TerrascriptClient:
2574
2621
  values.setdefault("replication_group_id", values["identifier"])
2575
2622
  values.pop("identifier", None)
2576
2623
 
2577
- tf_resources = []
2624
+ tf_resources: list[TFResource] = []
2578
2625
  self.init_common_outputs(tf_resources, spec)
2579
2626
 
2580
2627
  default_region = self.default_regions.get(account)
@@ -2654,19 +2701,23 @@ class TerrascriptClient:
2654
2701
 
2655
2702
  self.add_resources(account, tf_resources)
2656
2703
 
2657
- def populate_tf_resource_service_account(self, spec, ocm_map=None):
2704
+ def populate_tf_resource_service_account(
2705
+ self, spec: ExternalResourceSpec, ocm_map: OCMMap | None = None
2706
+ ) -> None:
2658
2707
  account = spec.provisioner_name
2659
2708
  identifier = spec.identifier
2660
2709
  common_values = self.init_values(spec)
2661
2710
  output_prefix = spec.output_prefix
2662
2711
 
2663
- tf_resources = []
2712
+ tf_resources: list[TFResource] = []
2664
2713
  self.init_common_outputs(tf_resources, spec)
2665
2714
 
2666
2715
  # iam user for bucket
2667
- values = {}
2668
- values["name"] = identifier
2669
- values["tags"] = common_values["tags"]
2716
+ values = {
2717
+ "name": identifier,
2718
+ "tags": common_values["tags"],
2719
+ }
2720
+
2670
2721
  user_tf_resource = aws_iam_user(identifier, **values)
2671
2722
  tf_resources.append(user_tf_resource)
2672
2723
 
@@ -2750,13 +2801,15 @@ class TerrascriptClient:
2750
2801
 
2751
2802
  self.add_resources(account, tf_resources)
2752
2803
 
2753
- def populate_tf_resource_secrets_manager_sa(self, spec):
2804
+ def populate_tf_resource_secrets_manager_sa(
2805
+ self, spec: ExternalResourceSpec
2806
+ ) -> None:
2754
2807
  account = spec.provisioner_name
2755
2808
  identifier = spec.identifier
2756
2809
  common_values = self.init_values(spec)
2757
2810
  output_prefix = spec.output_prefix
2758
2811
 
2759
- tf_resources = []
2812
+ tf_resources: list[TFResource] = []
2760
2813
  self.init_common_outputs(tf_resources, spec)
2761
2814
 
2762
2815
  secrets_prefix = common_values["secrets_prefix"]
@@ -2780,7 +2833,7 @@ class TerrascriptClient:
2780
2833
 
2781
2834
  tf_resources.extend(
2782
2835
  self.get_tf_iam_service_user(
2783
- [], identifier, policy, common_values["tags"], output_prefix
2836
+ None, identifier, policy, common_values["tags"], output_prefix
2784
2837
  )
2785
2838
  )
2786
2839
 
@@ -2790,20 +2843,20 @@ class TerrascriptClient:
2790
2843
 
2791
2844
  self.add_resources(account, tf_resources)
2792
2845
 
2793
- def populate_tf_resource_role(self, spec):
2846
+ def populate_tf_resource_role(self, spec: ExternalResourceSpec) -> None:
2794
2847
  account = spec.provisioner_name
2795
2848
  identifier = spec.identifier
2796
2849
  common_values = self.init_values(spec)
2797
2850
  output_prefix = spec.output_prefix
2798
2851
 
2799
- tf_resources = []
2852
+ tf_resources: list[TFResource] = []
2800
2853
  self.init_common_outputs(tf_resources, spec)
2801
2854
 
2802
2855
  assume_role = common_values["assume_role"]
2803
2856
  assume_role = {k: v for k, v in assume_role.items() if v is not None}
2804
2857
  assume_action = common_values.get("assume_action") or "AssumeRole"
2805
2858
  # assume role policy
2806
- assume_role_policy = {
2859
+ assume_role_policy: dict[str, Any] = {
2807
2860
  "Version": "2012-10-17",
2808
2861
  "Statement": [
2809
2862
  {
@@ -2818,10 +2871,10 @@ class TerrascriptClient:
2818
2871
  assume_role_policy["Statement"][0]["Condition"] = assume_condition
2819
2872
 
2820
2873
  # iam role
2821
- values = {
2874
+ values: dict[str, Any] = {
2822
2875
  "name": identifier,
2823
2876
  "tags": common_values["tags"],
2824
- "assume_role_policy": json.dumps(assume_role_policy),
2877
+ "assume_role_policy": json_dumps(assume_role_policy),
2825
2878
  }
2826
2879
 
2827
2880
  inline_policy = common_values.get("inline_policy")
@@ -2871,9 +2924,11 @@ class TerrascriptClient:
2871
2924
 
2872
2925
  self.add_resources(account, tf_resources)
2873
2926
 
2874
- def populate_iam_policy(self, account: str, name: str, policy: dict[str, Any]):
2927
+ def populate_iam_policy(
2928
+ self, account: str, name: str, policy: Mapping[str, Any]
2929
+ ) -> None:
2875
2930
  tf_aws_iam_policy = aws_iam_policy(
2876
- f"{account}-{name}", name=name, policy=json.dumps(policy)
2931
+ f"{account}-{name}", name=name, policy=json_dumps(policy)
2877
2932
  )
2878
2933
  self.add_resource(account, tf_aws_iam_policy)
2879
2934
 
@@ -2882,8 +2937,8 @@ class TerrascriptClient:
2882
2937
  account: str,
2883
2938
  name: str,
2884
2939
  saml_provider_name: str,
2885
- aws_managed_policies: list[str],
2886
- customer_managed_policies: list[str] | None = None,
2940
+ aws_managed_policies: Iterable[str],
2941
+ customer_managed_policies: Iterable[str] | None = None,
2887
2942
  max_session_duration_hours: int = 1,
2888
2943
  ) -> None:
2889
2944
  """Manage the an IAM role needed for SAML authentication."""
@@ -2915,23 +2970,25 @@ class TerrascriptClient:
2915
2970
  role_tf_resource = aws_iam_role(
2916
2971
  f"{account}-{name}",
2917
2972
  name=name,
2918
- assume_role_policy=json.dumps(assume_role_policy),
2973
+ assume_role_policy=json_dumps(assume_role_policy),
2919
2974
  managed_policy_arns=managed_policy_arns,
2920
2975
  max_session_duration=max_session_duration_hours * 3600,
2921
2976
  )
2922
2977
  self.add_resource(account, role_tf_resource)
2923
2978
 
2924
- def populate_tf_resource_sqs(self, spec):
2979
+ def populate_tf_resource_sqs(self, spec: ExternalResourceSpec) -> None:
2925
2980
  account = spec.provisioner_name
2926
2981
  identifier = spec.identifier
2927
2982
  common_values = self.init_values(spec)
2928
2983
  output_prefix = spec.output_prefix
2929
2984
  uid = self.uids.get(account)
2930
2985
 
2931
- tf_resources = []
2986
+ tf_resources: list[TFResource] = []
2932
2987
  self.init_common_outputs(tf_resources, spec)
2933
2988
  region = common_values.get("region") or self.default_regions.get(account)
2989
+ assert region # make mypy happy
2934
2990
  specs = common_values.get("specs")
2991
+ assert specs is not None # make mypy happy
2935
2992
  all_queues_per_spec = []
2936
2993
  kms_keys = set()
2937
2994
  for _spec in specs:
@@ -2957,7 +3014,7 @@ class TerrascriptClient:
2957
3014
  all_queues.append(queue_name)
2958
3015
  sqs_policy = values.pop("sqs_policy", None)
2959
3016
  if sqs_policy is not None:
2960
- values["policy"] = json.dumps(sqs_policy, sort_keys=True)
3017
+ values["policy"] = json_dumps(sqs_policy)
2961
3018
  dl_queue = values.pop("dl_queue", None)
2962
3019
  if dl_queue is not None:
2963
3020
  max_receive_count = int(values.pop("max_receive_count", 10))
@@ -2971,9 +3028,7 @@ class TerrascriptClient:
2971
3028
  "deadLetterTargetArn": "${" + dl_data.arn + "}",
2972
3029
  "maxReceiveCount": max_receive_count,
2973
3030
  }
2974
- values["redrive_policy"] = json.dumps(
2975
- redrive_policy, sort_keys=True
2976
- )
3031
+ values["redrive_policy"] = json_dumps(redrive_policy)
2977
3032
  kms_master_key_id = values.pop("kms_master_key_id", None)
2978
3033
  if kms_master_key_id is not None:
2979
3034
  if kms_master_key_id.startswith("arn:"):
@@ -3005,9 +3060,11 @@ class TerrascriptClient:
3005
3060
  # https://www.terraform.io/docs/providers/aws/r/iam_access_key.html
3006
3061
 
3007
3062
  # iam user for queue
3008
- values = {}
3009
- values["name"] = identifier
3010
- values["tags"] = common_values["tags"]
3063
+ values = {
3064
+ "name": identifier,
3065
+ "tags": common_values["tags"],
3066
+ }
3067
+
3011
3068
  user_tf_resource = aws_iam_user(identifier, **values)
3012
3069
  tf_resources.append(user_tf_resource)
3013
3070
 
@@ -3021,9 +3078,9 @@ class TerrascriptClient:
3021
3078
  policy_identifier = f"{identifier}-{policy_index}"
3022
3079
  if len(all_queues_per_spec) == 1:
3023
3080
  policy_identifier = identifier
3024
- values = {}
3025
- values["name"] = policy_identifier
3026
- policy = {
3081
+ values = {"name": policy_identifier}
3082
+
3083
+ policy: dict[str, Any] = {
3027
3084
  "Version": "2012-10-17",
3028
3085
  "Statement": [
3029
3086
  {
@@ -3044,32 +3101,34 @@ class TerrascriptClient:
3044
3101
  "Resource": list(kms_keys),
3045
3102
  }
3046
3103
  policy["Statement"].append(kms_statement)
3047
- values["policy"] = json.dumps(policy, sort_keys=True)
3104
+ values["policy"] = json_dumps(policy)
3048
3105
  policy_tf_resource = aws_iam_policy(policy_identifier, **values)
3049
3106
  tf_resources.append(policy_tf_resource)
3050
3107
 
3051
3108
  # iam user policy attachment
3052
- values = {}
3053
- values["user"] = identifier
3054
- values["policy_arn"] = "${" + policy_tf_resource.arn + "}"
3055
- values["depends_on"] = self.get_dependencies([
3056
- user_tf_resource,
3057
- policy_tf_resource,
3058
- ])
3109
+ values = {
3110
+ "user": identifier,
3111
+ "policy_arn": "${" + policy_tf_resource.arn + "}",
3112
+ "depends_on": self.get_dependencies([
3113
+ user_tf_resource,
3114
+ policy_tf_resource,
3115
+ ]),
3116
+ }
3117
+
3059
3118
  tf_resource = aws_iam_user_policy_attachment(policy_identifier, **values)
3060
3119
  tf_resources.append(tf_resource)
3061
3120
 
3062
3121
  self.add_resources(account, tf_resources)
3063
3122
 
3064
- def populate_tf_resource_sns(self, spec):
3123
+ def populate_tf_resource_sns(self, spec: ExternalResourceSpec) -> None:
3065
3124
  account = spec.provisioner_name
3066
3125
  identifier = spec.identifier
3067
3126
  common_values = self.init_values(spec)
3068
3127
  output_prefix = spec.output_prefix
3069
3128
  policy = common_values.get("inline_policy")
3070
3129
  region = common_values.get("region") or self.default_regions.get(account)
3071
-
3072
- values = {}
3130
+ assert region # make mypy happy
3131
+ values: dict[str, Any] = {}
3073
3132
  fifo_topic = common_values.get("fifo_topic", False)
3074
3133
  topic_name = identifier + ".fifo" if fifo_topic else identifier
3075
3134
 
@@ -3077,16 +3136,18 @@ class TerrascriptClient:
3077
3136
  values["policy"] = policy
3078
3137
  values["fifo_topic"] = fifo_topic
3079
3138
 
3080
- tf_resources = []
3139
+ tf_resources: list[TFResource] = []
3081
3140
  self.init_common_outputs(tf_resources, spec)
3082
3141
  tf_resource = aws_sns_topic(identifier, **values)
3083
3142
  tf_resources.append(tf_resource)
3084
3143
 
3085
3144
  if "subscriptions" in common_values:
3086
- subscriptions = common_values.get("subscriptions")
3145
+ subscriptions = common_values["subscriptions"]
3087
3146
  for index, sub in enumerate(subscriptions):
3088
- sub_values = {}
3089
- sub_values["topic_arn"] = "${aws_sns_topic" + "." + identifier + ".arn}"
3147
+ sub_values = {
3148
+ "topic_arn": "${aws_sns_topic" + "." + identifier + ".arn}"
3149
+ }
3150
+
3090
3151
  protocol = sub["protocol"]
3091
3152
  endpoint = sub["endpoint"]
3092
3153
  if protocol == "email" and not EMAIL_REGEX.match(endpoint):
@@ -3111,17 +3172,19 @@ class TerrascriptClient:
3111
3172
  tf_resources.append(Output(output_name, value=output_value))
3112
3173
  self.add_resources(account, tf_resources)
3113
3174
 
3114
- def populate_tf_resource_dynamodb(self, spec):
3175
+ def populate_tf_resource_dynamodb(self, spec: ExternalResourceSpec) -> None:
3115
3176
  account = spec.provisioner_name
3116
3177
  identifier = spec.identifier
3117
3178
  common_values = self.init_values(spec)
3118
3179
  output_prefix = spec.output_prefix
3119
3180
  uid = self.uids.get(account)
3120
3181
 
3121
- tf_resources = []
3182
+ tf_resources: list[TFResource] = []
3122
3183
  self.init_common_outputs(tf_resources, spec)
3123
3184
  region = common_values.get("region") or self.default_regions.get(account)
3185
+ assert region # make mypy happy
3124
3186
  specs = common_values.get("specs")
3187
+ assert specs is not None # make mypy happy
3125
3188
  all_tables = []
3126
3189
  for _spec in specs:
3127
3190
  defaults = self.get_values(_spec["defaults"])
@@ -3135,9 +3198,11 @@ class TerrascriptClient:
3135
3198
  # Terraform resource reference:
3136
3199
  # https://www.terraform.io/docs/providers/aws/r/
3137
3200
  # dynamodb_table.html
3138
- values = {}
3139
- values["name"] = table
3140
- values["tags"] = common_values["tags"]
3201
+ values = {
3202
+ "name": table,
3203
+ "tags": common_values["tags"],
3204
+ }
3205
+
3141
3206
  values.update(defaults)
3142
3207
  values["attribute"] = attributes
3143
3208
  if self._multiregion_account(account):
@@ -3158,9 +3223,11 @@ class TerrascriptClient:
3158
3223
  # https://www.terraform.io/docs/providers/aws/r/iam_access_key.html
3159
3224
 
3160
3225
  # iam user for table
3161
- values = {}
3162
- values["name"] = identifier
3163
- values["tags"] = common_values["tags"]
3226
+ values = {
3227
+ "name": identifier,
3228
+ "tags": common_values["tags"],
3229
+ }
3230
+
3164
3231
  user_tf_resource = aws_iam_user(identifier, **values)
3165
3232
  tf_resources.append(user_tf_resource)
3166
3233
 
@@ -3184,7 +3251,7 @@ class TerrascriptClient:
3184
3251
  }
3185
3252
  ],
3186
3253
  }
3187
- values["policy"] = json.dumps(policy, sort_keys=True)
3254
+ values["policy"] = json_dumps(policy)
3188
3255
  values["depends_on"] = self.get_dependencies([user_tf_resource])
3189
3256
 
3190
3257
  tf_aws_iam_policy = aws_iam_policy(identifier, **values)
@@ -3200,23 +3267,24 @@ class TerrascriptClient:
3200
3267
 
3201
3268
  self.add_resources(account, tf_resources)
3202
3269
 
3203
- def populate_tf_resource_ecr(self, spec):
3270
+ def populate_tf_resource_ecr(self, spec: ExternalResourceSpec) -> None:
3204
3271
  account = spec.provisioner_name
3205
3272
  identifier = spec.identifier
3206
3273
  common_values = self.init_values(spec)
3207
3274
  output_prefix = spec.output_prefix
3208
3275
 
3209
- tf_resources = []
3276
+ tf_resources: list[TFResource] = []
3210
3277
  self.init_common_outputs(tf_resources, spec)
3211
3278
 
3212
3279
  # ecr repository
3213
3280
  # Terraform resource reference:
3214
3281
  # https://www.terraform.io/docs/providers/aws/r/ecr_repository.html
3215
- values = {}
3282
+ values: dict[str, Any] = {}
3216
3283
  values["name"] = identifier
3217
3284
  values["tags"] = common_values["tags"]
3218
3285
 
3219
3286
  region = common_values.get("region") or self.default_regions.get(account)
3287
+ assert region # make mypy happy
3220
3288
  if self._multiregion_account(account):
3221
3289
  values["provider"] = "aws." + region
3222
3290
  ecr_tf_resource = aws_ecr_repository(identifier, **values)
@@ -3242,11 +3310,12 @@ class TerrascriptClient:
3242
3310
  # https://www.terraform.io/docs/providers/aws/r/iam_access_key.html
3243
3311
 
3244
3312
  # iam user for repository
3245
- values = {}
3246
- values["name"] = identifier
3247
- values["tags"] = common_values["tags"]
3248
- values["depends_on"] = self.get_dependencies([ecr_tf_resource])
3249
- user_tf_resource = aws_iam_user(identifier, **values)
3313
+ values_iam_user: dict[str, Any] = {
3314
+ "name": identifier,
3315
+ "tags": common_values["tags"],
3316
+ "depends_on": self.get_dependencies([ecr_tf_resource]),
3317
+ }
3318
+ user_tf_resource = aws_iam_user(identifier, **values_iam_user)
3250
3319
  tf_resources.append(user_tf_resource)
3251
3320
 
3252
3321
  # iam access key for user
@@ -3255,8 +3324,7 @@ class TerrascriptClient:
3255
3324
  )
3256
3325
 
3257
3326
  # iam user policy for bucket
3258
- values = {}
3259
- values["name"] = identifier
3327
+ values_policy: dict[str, Any] = {"name": identifier}
3260
3328
  policy = {
3261
3329
  "Version": "2012-10-17",
3262
3330
  "Statement": [
@@ -3293,10 +3361,10 @@ class TerrascriptClient:
3293
3361
  },
3294
3362
  ],
3295
3363
  }
3296
- values["policy"] = json.dumps(policy, sort_keys=True)
3297
- values["depends_on"] = self.get_dependencies([user_tf_resource])
3364
+ values_policy["policy"] = json_dumps(policy)
3365
+ values_policy["depends_on"] = self.get_dependencies([user_tf_resource])
3298
3366
 
3299
- tf_aws_iam_policy = aws_iam_policy(identifier, **values)
3367
+ tf_aws_iam_policy = aws_iam_policy(identifier, **values_policy)
3300
3368
  tf_resources.append(tf_aws_iam_policy)
3301
3369
 
3302
3370
  tf_aws_iam_user_policy_attachment = aws_iam_user_policy_attachment(
@@ -3309,7 +3377,7 @@ class TerrascriptClient:
3309
3377
 
3310
3378
  self.add_resources(account, tf_resources)
3311
3379
 
3312
- def populate_tf_resource_s3_cloudfront(self, spec):
3380
+ def populate_tf_resource_s3_cloudfront(self, spec: ExternalResourceSpec) -> None:
3313
3381
  account = spec.provisioner_name
3314
3382
  identifier = spec.identifier
3315
3383
  common_values = self.init_values(spec)
@@ -3317,17 +3385,15 @@ class TerrascriptClient:
3317
3385
 
3318
3386
  bucket_tf_resource = self.populate_tf_resource_s3(spec)
3319
3387
 
3320
- tf_resources = []
3388
+ tf_resources: list[TFResource] = []
3321
3389
 
3322
3390
  # cloudfront origin access identity
3323
- values = {}
3324
- values["comment"] = f"{identifier}-cf-identity"
3391
+ values = {"comment": f"{identifier}-cf-identity"}
3325
3392
  cf_oai_tf_resource = aws_cloudfront_origin_access_identity(identifier, **values)
3326
3393
  tf_resources.append(cf_oai_tf_resource)
3327
3394
 
3328
3395
  # bucket policy for cloudfront
3329
- values = {}
3330
- values["bucket"] = identifier
3396
+ values_policy: dict[str, Any] = {"bucket": identifier}
3331
3397
  policy = {
3332
3398
  "Version": "2012-10-17",
3333
3399
  "Statement": [
@@ -3345,26 +3411,27 @@ class TerrascriptClient:
3345
3411
  }
3346
3412
  ],
3347
3413
  }
3348
- values["policy"] = json.dumps(policy, sort_keys=True)
3349
- values["depends_on"] = self.get_dependencies([bucket_tf_resource])
3414
+ values_policy["policy"] = json_dumps(policy)
3415
+ values_policy["depends_on"] = self.get_dependencies([bucket_tf_resource])
3350
3416
  region = common_values.get("region") or self.default_regions.get(account)
3417
+ assert region # make mypy happy
3351
3418
  if self._multiregion_account(account):
3352
- values["provider"] = "aws." + region
3353
- bucket_policy_tf_resource = aws_s3_bucket_policy(identifier, **values)
3419
+ values_policy["provider"] = "aws." + region
3420
+ bucket_policy_tf_resource = aws_s3_bucket_policy(identifier, **values_policy)
3354
3421
  tf_resources.append(bucket_policy_tf_resource)
3355
3422
 
3356
- values = common_values.get("distribution_config", {})
3423
+ distribution_config = common_values.get("distribution_config", {})
3357
3424
  # aws_s3_bucket_acl
3358
- if "logging_config" in values:
3425
+ if "logging_config" in distribution_config:
3359
3426
  # we could set this at a global level with a standard name like "cloudfront"
3360
3427
  # but we need all aws accounts upgraded to aws provider >3.60 first
3361
3428
  tf_resources.append(
3362
3429
  aws_cloudfront_log_delivery_canonical_user_id(identifier)
3363
3430
  )
3364
3431
 
3365
- logging_config_bucket = values["logging_config"]
3432
+ logging_config_bucket = distribution_config["logging_config"]
3366
3433
  acl_values = {}
3367
- access_control_policy = {
3434
+ access_control_policy: dict[str, Any] = {
3368
3435
  "owner": {
3369
3436
  "id": "${data.aws_canonical_user_id.current.id}",
3370
3437
  },
@@ -3388,7 +3455,7 @@ class TerrascriptClient:
3388
3455
  }
3389
3456
  external_account_id = logging_config_bucket.pop("external_account_id", None)
3390
3457
  if external_account_id:
3391
- external_account_policy = {
3458
+ external_account_policy: dict[str, Any] = {
3392
3459
  "grantee": {
3393
3460
  "id": external_account_id,
3394
3461
  "type": "CanonicalUser",
@@ -3403,13 +3470,15 @@ class TerrascriptClient:
3403
3470
  tf_resources.append(aws_s3_bucket_acl_resource)
3404
3471
 
3405
3472
  # cloud front distribution
3406
- values["tags"] = common_values["tags"]
3407
- values.setdefault("default_cache_behavior", {}).setdefault(
3473
+ distribution_config["tags"] = common_values["tags"]
3474
+ distribution_config.setdefault("default_cache_behavior", {}).setdefault(
3408
3475
  "target_origin_id", "default"
3409
3476
  )
3410
3477
  origin = {
3411
3478
  "domain_name": "${" + bucket_tf_resource.bucket_domain_name + "}",
3412
- "origin_id": values["default_cache_behavior"]["target_origin_id"],
3479
+ "origin_id": distribution_config["default_cache_behavior"][
3480
+ "target_origin_id"
3481
+ ],
3413
3482
  "s3_origin_config": {
3414
3483
  "origin_access_identity": "origin-access-identity/cloudfront/"
3415
3484
  + "${"
@@ -3417,8 +3486,10 @@ class TerrascriptClient:
3417
3486
  + "}"
3418
3487
  },
3419
3488
  }
3420
- values["origin"] = [origin]
3421
- cf_distribution_tf_resource = aws_cloudfront_distribution(identifier, **values)
3489
+ distribution_config["origin"] = [origin]
3490
+ cf_distribution_tf_resource = aws_cloudfront_distribution(
3491
+ identifier, **distribution_config
3492
+ )
3422
3493
  tf_resources.append(cf_distribution_tf_resource)
3423
3494
 
3424
3495
  # outputs
@@ -3443,7 +3514,7 @@ class TerrascriptClient:
3443
3514
 
3444
3515
  self.add_resources(account, tf_resources)
3445
3516
 
3446
- def populate_tf_resource_s3_sqs(self, spec):
3517
+ def populate_tf_resource_s3_sqs(self, spec: ExternalResourceSpec) -> None:
3447
3518
  account = spec.provisioner_name
3448
3519
  identifier = spec.identifier
3449
3520
  common_values = self.init_values(spec)
@@ -3453,12 +3524,13 @@ class TerrascriptClient:
3453
3524
  bucket_tf_resource = self.populate_tf_resource_s3(spec)
3454
3525
 
3455
3526
  region = common_values.get("region") or self.default_regions.get(account)
3527
+ assert region # make mypy happy
3456
3528
  provider = ""
3457
3529
  if self._multiregion_account(account):
3458
3530
  provider = "aws." + region
3459
- tf_resources = []
3531
+ tf_resources: list[TFResource] = []
3460
3532
  sqs_identifier = f"{identifier}-sqs"
3461
- sqs_values = {"name": sqs_identifier}
3533
+ sqs_values: dict[str, Any] = {"name": sqs_identifier}
3462
3534
 
3463
3535
  sqs_values["visibility_timeout_seconds"] = int(
3464
3536
  common_values.get("visibility_timeout_seconds", 30)
@@ -3485,21 +3557,21 @@ class TerrascriptClient:
3485
3557
  }
3486
3558
  ],
3487
3559
  }
3488
- sqs_values["policy"] = json.dumps(sqs_policy, sort_keys=True)
3560
+ sqs_values["policy"] = json_dumps(sqs_policy)
3489
3561
 
3490
3562
  kms_encryption = common_values.get("kms_encryption", False)
3491
3563
  if kms_encryption:
3492
3564
  kms_identifier = f"{identifier}-kms"
3493
3565
  kms_values = {
3494
- "description": "app-interface created KMS key for" + sqs_identifier
3566
+ "description": "app-interface created KMS key for" + sqs_identifier,
3567
+ "key_usage": str(
3568
+ common_values.get("key_usage", "ENCRYPT_DECRYPT")
3569
+ ).upper(),
3570
+ "customer_master_key_spec": str(
3571
+ common_values.get("customer_master_key_spec", "SYMMETRIC_DEFAULT")
3572
+ ).upper(),
3573
+ "is_enabled": common_values.get("is_enabled", True),
3495
3574
  }
3496
- kms_values["key_usage"] = str(
3497
- common_values.get("key_usage", "ENCRYPT_DECRYPT")
3498
- ).upper()
3499
- kms_values["customer_master_key_spec"] = str(
3500
- common_values.get("customer_master_key_spec", "SYMMETRIC_DEFAULT")
3501
- ).upper()
3502
- kms_values["is_enabled"] = common_values.get("is_enabled", True)
3503
3575
 
3504
3576
  kms_policy = {
3505
3577
  "Version": "2012-10-17",
@@ -3521,7 +3593,7 @@ class TerrascriptClient:
3521
3593
  },
3522
3594
  ],
3523
3595
  }
3524
- kms_values["policy"] = json.dumps(kms_policy, sort_keys=True)
3596
+ kms_values["policy"] = json_dumps(kms_policy)
3525
3597
  if provider:
3526
3598
  kms_values["provider"] = provider
3527
3599
 
@@ -3575,16 +3647,16 @@ class TerrascriptClient:
3575
3647
  # https://www.terraform.io/docs/providers/aws/r/iam_access_key.html
3576
3648
 
3577
3649
  # iam user for queue
3578
- values = {}
3579
- values["name"] = sqs_identifier
3650
+ values: dict[str, Any] = {"name": sqs_identifier}
3580
3651
  user_tf_resource = aws_iam_user(sqs_identifier, **values)
3581
3652
  tf_resources.append(user_tf_resource)
3582
3653
 
3583
3654
  # iam access key for user
3584
- values = {}
3585
- values["user"] = sqs_identifier
3586
- values["depends_on"] = self.get_dependencies([user_tf_resource])
3587
- access_key_tf_resource = aws_iam_access_key(sqs_identifier, **values)
3655
+ values_key: dict[str, Any] = {
3656
+ "user": sqs_identifier,
3657
+ "depends_on": self.get_dependencies([user_tf_resource]),
3658
+ }
3659
+ access_key_tf_resource = aws_iam_access_key(sqs_identifier, **values_key)
3588
3660
  tf_resources.append(access_key_tf_resource)
3589
3661
  # outputs
3590
3662
  # sqs_aws_access_key_id
@@ -3597,9 +3669,8 @@ class TerrascriptClient:
3597
3669
  tf_resources.append(Output(output_name, value=output_value, sensitive=True))
3598
3670
 
3599
3671
  # iam policy for queue
3600
- values = {}
3601
- values["name"] = sqs_identifier
3602
- policy = {
3672
+ values_policy: dict[str, Any] = {"name": sqs_identifier}
3673
+ policy: dict[str, Any] = {
3603
3674
  "Version": "2012-10-17",
3604
3675
  "Statement": [
3605
3676
  {
@@ -3620,20 +3691,21 @@ class TerrascriptClient:
3620
3691
  "Resource": [sqs_values["kms_master_key_id"]],
3621
3692
  }
3622
3693
  policy["Statement"].append(kms_statement)
3623
- values["policy"] = json.dumps(policy, sort_keys=True)
3624
- policy_tf_resource = aws_iam_policy(sqs_identifier, **values)
3694
+ values_policy["policy"] = json_dumps(policy)
3695
+ policy_tf_resource = aws_iam_policy(sqs_identifier, **values_policy)
3625
3696
  tf_resources.append(policy_tf_resource)
3626
3697
 
3627
3698
  # iam user policy attachment
3628
- values = {}
3629
- values["user"] = sqs_identifier
3630
- values["policy_arn"] = "${" + policy_tf_resource.arn + "}"
3631
- values["depends_on"] = self.get_dependencies([
3632
- user_tf_resource,
3633
- policy_tf_resource,
3634
- ])
3699
+ values_user_policy: dict[str, Any] = {
3700
+ "user": sqs_identifier,
3701
+ "policy_arn": "${" + policy_tf_resource.arn + "}",
3702
+ "depends_on": self.get_dependencies([
3703
+ user_tf_resource,
3704
+ policy_tf_resource,
3705
+ ]),
3706
+ }
3635
3707
  user_policy_attachment_tf_resource = aws_iam_user_policy_attachment(
3636
- sqs_identifier, **values
3708
+ sqs_identifier, **values_user_policy
3637
3709
  )
3638
3710
  tf_resources.append(user_policy_attachment_tf_resource)
3639
3711
 
@@ -3644,13 +3716,13 @@ class TerrascriptClient:
3644
3716
 
3645
3717
  self.add_resources(account, tf_resources)
3646
3718
 
3647
- def populate_tf_resource_cloudwatch(self, spec):
3719
+ def populate_tf_resource_cloudwatch(self, spec: ExternalResourceSpec) -> None:
3648
3720
  account = spec.provisioner_name
3649
3721
  identifier = spec.identifier
3650
3722
  common_values = self.init_values(spec)
3651
3723
  output_prefix = spec.output_prefix
3652
3724
 
3653
- tf_resources = []
3725
+ tf_resources: list[TFResource] = []
3654
3726
  self.init_common_outputs(tf_resources, spec)
3655
3727
 
3656
3728
  # ecr repository
@@ -3666,6 +3738,7 @@ class TerrascriptClient:
3666
3738
  }
3667
3739
 
3668
3740
  region = common_values.get("region") or self.default_regions.get(account)
3741
+ assert region # make mypy happy
3669
3742
  provider = ""
3670
3743
  if self._multiregion_account(account):
3671
3744
  provider = "aws." + region
@@ -3689,7 +3762,7 @@ class TerrascriptClient:
3689
3762
  role_identifier = f"{identifier}-lambda-execution-role"
3690
3763
  role_values = {
3691
3764
  "name": role_identifier,
3692
- "assume_role_policy": json.dumps(assume_role_policy, sort_keys=True),
3765
+ "assume_role_policy": json_dumps(assume_role_policy),
3693
3766
  }
3694
3767
 
3695
3768
  role_tf_resource = aws_iam_role(role_identifier, **role_values)
@@ -3721,7 +3794,7 @@ class TerrascriptClient:
3721
3794
 
3722
3795
  policy_values = {
3723
3796
  "role": "${" + role_tf_resource.id + "}",
3724
- "policy": json.dumps(policy, sort_keys=True),
3797
+ "policy": json_dumps(policy),
3725
3798
  }
3726
3799
  policy_tf_resource = aws_iam_role_policy(policy_identifier, **policy_values)
3727
3800
  tf_resources.append(policy_tf_resource)
@@ -3741,29 +3814,26 @@ class TerrascriptClient:
3741
3814
  "filename": zip_file,
3742
3815
  "source_code_hash": '${filebase64sha256("' + zip_file + '")}',
3743
3816
  "role": "${" + role_tf_resource.arn + "}",
3744
- }
3745
-
3746
- lambda_values["function_name"] = lambda_identifier
3747
- lambda_values["runtime"] = common_values.get("runtime", "nodejs18.x")
3748
- lambda_values["timeout"] = common_values.get("timeout", 30)
3749
- lambda_values["handler"] = common_values.get("handler", "index.handler")
3750
- lambda_values["memory_size"] = common_values.get("memory_size", 128)
3751
-
3752
- lambda_values["vpc_config"] = {
3753
- "subnet_ids": "${data.aws_elasticsearch_domain."
3754
- + es_identifier
3755
- + ".vpc_options.0.subnet_ids}",
3756
- "security_group_ids": "${data.aws_elasticsearch_domain."
3757
- + es_identifier
3758
- + ".vpc_options.0.security_group_ids}",
3759
- }
3760
-
3761
- lambda_values["environment"] = {
3762
- "variables": {
3763
- "es_endpoint": "${data.aws_elasticsearch_domain."
3817
+ "function_name": lambda_identifier,
3818
+ "runtime": common_values.get("runtime", "nodejs18.x"),
3819
+ "timeout": common_values.get("timeout", 30),
3820
+ "handler": common_values.get("handler", "index.handler"),
3821
+ "memory_size": common_values.get("memory_size", 128),
3822
+ "vpc_config": {
3823
+ "subnet_ids": "${data.aws_elasticsearch_domain."
3764
3824
  + es_identifier
3765
- + ".endpoint}"
3766
- }
3825
+ + ".vpc_options.0.subnet_ids}",
3826
+ "security_group_ids": "${data.aws_elasticsearch_domain."
3827
+ + es_identifier
3828
+ + ".vpc_options.0.security_group_ids}",
3829
+ },
3830
+ "environment": {
3831
+ "variables": {
3832
+ "es_endpoint": "${data.aws_elasticsearch_domain."
3833
+ + es_identifier
3834
+ + ".endpoint}"
3835
+ }
3836
+ },
3767
3837
  }
3768
3838
 
3769
3839
  if provider:
@@ -3852,7 +3922,7 @@ class TerrascriptClient:
3852
3922
  }
3853
3923
  values = {
3854
3924
  "name": identifier,
3855
- "policy": json.dumps(policy, sort_keys=True),
3925
+ "policy": json_dumps(policy),
3856
3926
  "depends_on": self.get_dependencies([user_tf_resource]),
3857
3927
  }
3858
3928
 
@@ -3869,13 +3939,13 @@ class TerrascriptClient:
3869
3939
 
3870
3940
  self.add_resources(account, tf_resources)
3871
3941
 
3872
- def populate_tf_resource_kms(self, spec):
3942
+ def populate_tf_resource_kms(self, spec: ExternalResourceSpec) -> None:
3873
3943
  account = spec.provisioner_name
3874
3944
  identifier = spec.identifier
3875
3945
  values = self.init_values(spec)
3876
3946
  output_prefix = spec.output_prefix
3877
3947
 
3878
- tf_resources = []
3948
+ tf_resources: list[TFResource] = []
3879
3949
  self.init_common_outputs(tf_resources, spec)
3880
3950
  values.pop("identifier", None)
3881
3951
 
@@ -3892,6 +3962,7 @@ class TerrascriptClient:
3892
3962
  if key in values:
3893
3963
  values[key] = values[key].upper()
3894
3964
  region = values.pop("region", None) or self.default_regions.get(account)
3965
+ assert region # make mypy happy
3895
3966
  if self._multiregion_account(account):
3896
3967
  values["provider"] = "aws." + region
3897
3968
 
@@ -3903,9 +3974,10 @@ class TerrascriptClient:
3903
3974
  output_value = "${" + tf_resource.key_id + "}"
3904
3975
  tf_resources.append(Output(output_name, value=output_value))
3905
3976
 
3906
- alias_values = {}
3907
- alias_values["name"] = "alias/" + identifier
3908
- alias_values["target_key_id"] = "${aws_kms_key." + identifier + ".key_id}"
3977
+ alias_values = {
3978
+ "name": "alias/" + identifier,
3979
+ "target_key_id": "${aws_kms_key." + identifier + ".key_id}",
3980
+ }
3909
3981
  if self._multiregion_account(account):
3910
3982
  alias_values["provider"] = "aws." + region
3911
3983
  tf_resource = aws_kms_alias(identifier, **alias_values)
@@ -3913,28 +3985,30 @@ class TerrascriptClient:
3913
3985
 
3914
3986
  self.add_resources(account, tf_resources)
3915
3987
 
3916
- def populate_tf_resource_kinesis(self, spec):
3988
+ def populate_tf_resource_kinesis(self, spec: ExternalResourceSpec) -> None:
3917
3989
  account = spec.provisioner_name
3918
3990
  identifier = spec.identifier
3919
3991
  common_values = self.init_values(spec)
3920
3992
  output_prefix = spec.output_prefix
3921
3993
 
3922
- tf_resources = []
3994
+ tf_resources: list[TFResource] = []
3923
3995
  self.init_common_outputs(tf_resources, spec)
3924
3996
 
3925
- tags = common_values["tags"]
3926
- kinesis_values = {
3997
+ tags: dict[str, str] = common_values["tags"]
3998
+ kinesis_values: dict[str, Any] = {
3927
3999
  "name": identifier,
3928
4000
  "tags": tags,
4001
+ "shard_count": common_values.get("shard_count"),
4002
+ "retention_period": common_values.get("retention_period", 24),
4003
+ "encryption_type": common_values.get("encryption_type", None),
3929
4004
  }
3930
- kinesis_values["shard_count"] = common_values.get("shard_count")
3931
- kinesis_values["retention_period"] = common_values.get("retention_period", 24)
3932
- kinesis_values["encryption_type"] = common_values.get("encryption_type", None)
4005
+
3933
4006
  if kinesis_values["encryption_type"] == "KMS":
3934
4007
  kinesis_values["kms_key_id"] = common_values.get("kms_key_id")
3935
4008
 
3936
4009
  # get region and set provider if required
3937
4010
  region = common_values.get("region") or self.default_regions.get(account)
4011
+ assert region # make mypy happy
3938
4012
  provider = ""
3939
4013
  if self._multiregion_account(account):
3940
4014
  provider = "aws." + region
@@ -3969,7 +4043,7 @@ class TerrascriptClient:
3969
4043
  role_identifier = f"{identifier}-lambda-execution-role"
3970
4044
  role_values = {
3971
4045
  "name": role_identifier,
3972
- "assume_role_policy": json.dumps(assume_role_policy, sort_keys=True),
4046
+ "assume_role_policy": json_dumps(assume_role_policy),
3973
4047
  "tags": tags,
3974
4048
  }
3975
4049
 
@@ -4006,7 +4080,7 @@ class TerrascriptClient:
4006
4080
  policy_tf_resource = aws_iam_policy(
4007
4081
  policy_identifier,
4008
4082
  name=policy_identifier,
4009
- policy=json.dumps(policy, sort_keys=True),
4083
+ policy=json_dumps(policy),
4010
4084
  tags=tags,
4011
4085
  )
4012
4086
  tf_resources.append(policy_tf_resource)
@@ -4054,34 +4128,31 @@ class TerrascriptClient:
4054
4128
  "source_code_hash": '${filebase64sha256("' + zip_file + '")}',
4055
4129
  "role": "${" + role_tf_resource.arn + "}",
4056
4130
  "tags": tags,
4057
- }
4058
-
4059
- lambda_values["function_name"] = lambda_identifier
4060
- lambda_values["runtime"] = common_values.get("runtime", "python3.9")
4061
- lambda_values["timeout"] = common_values.get("timeout", 30)
4062
- lambda_values["handler"] = common_values.get(
4063
- "handler", "lambda_function.handler"
4064
- )
4065
- lambda_values["memory_size"] = common_values.get("memory_size", 128)
4066
-
4067
- lambda_values["vpc_config"] = {
4068
- "subnet_ids": "${data.aws_elasticsearch_domain."
4069
- + es_identifier
4070
- + ".vpc_options.0.subnet_ids}",
4071
- "security_group_ids": "${data.aws_elasticsearch_domain."
4072
- + es_identifier
4073
- + ".vpc_options.0.security_group_ids}",
4074
- }
4075
-
4076
- index_prefix = common_values.get("index_prefix", f"{identifier}-")
4077
- lambda_values["environment"] = {
4078
- "variables": {
4079
- "es_endpoint": "${data.aws_elasticsearch_domain."
4131
+ "function_name": lambda_identifier,
4132
+ "runtime": common_values.get("runtime", "python3.9"),
4133
+ "timeout": common_values.get("timeout", 30),
4134
+ "handler": common_values.get("handler", "lambda_function.handler"),
4135
+ "memory_size": common_values.get("memory_size", 128),
4136
+ "vpc_config": {
4137
+ "subnet_ids": "${data.aws_elasticsearch_domain."
4080
4138
  + es_identifier
4081
- + ".endpoint}",
4082
- "index_prefix": index_prefix,
4083
- }
4139
+ + ".vpc_options.0.subnet_ids}",
4140
+ "security_group_ids": "${data.aws_elasticsearch_domain."
4141
+ + es_identifier
4142
+ + ".vpc_options.0.security_group_ids}",
4143
+ },
4144
+ "environment": {
4145
+ "variables": {
4146
+ "es_endpoint": "${data.aws_elasticsearch_domain."
4147
+ + es_identifier
4148
+ + ".endpoint}",
4149
+ "index_prefix": common_values.get(
4150
+ "index_prefix", f"{identifier}-"
4151
+ ),
4152
+ }
4153
+ },
4084
4154
  }
4155
+
4085
4156
  secret_name = es_resource.get_secret_field("secret_name")
4086
4157
  if secret_name:
4087
4158
  lambda_values["environment"]["variables"]["secret_name"] = secret_name
@@ -4160,7 +4231,9 @@ class TerrascriptClient:
4160
4231
  self.add_resources(account, tf_resources)
4161
4232
 
4162
4233
  @staticmethod
4163
- def _get_retention_in_days(values, account, identifier):
4234
+ def _get_retention_in_days(
4235
+ values: Mapping[str, Any], account: str, identifier: str
4236
+ ) -> int:
4164
4237
  default_retention_in_days = 14
4165
4238
  allowed_retention_in_days = [
4166
4239
  1,
@@ -4194,17 +4267,21 @@ class TerrascriptClient:
4194
4267
  return retention_in_days
4195
4268
 
4196
4269
  def get_tf_iam_service_user(
4197
- self, dep_tf_resource, identifier, policy, tags, output_prefix
4198
- ):
4270
+ self,
4271
+ dep_tf_resource: TFResource | None,
4272
+ identifier: str,
4273
+ policy: Mapping[str, Any],
4274
+ tags: Mapping[str, str],
4275
+ output_prefix: str,
4276
+ ) -> list[TFResource]:
4199
4277
  # iam resources
4200
4278
  # Terraform resource reference:
4201
4279
  # https://www.terraform.io/docs/providers/aws/r/iam_access_key.html
4202
- tf_resources = []
4280
+ tf_resources: list[TFResource] = []
4203
4281
 
4204
4282
  # iam user
4205
- values = {}
4206
- values["name"] = identifier
4207
- values["tags"] = tags
4283
+ values: dict[str, Any] = {"name": identifier, "tags": tags}
4284
+
4208
4285
  if dep_tf_resource:
4209
4286
  values["depends_on"] = self.get_dependencies([dep_tf_resource])
4210
4287
  user_tf_resource = aws_iam_user(identifier, **values)
@@ -4216,12 +4293,13 @@ class TerrascriptClient:
4216
4293
  )
4217
4294
 
4218
4295
  # iam user policy
4219
- values = {}
4220
- values["name"] = identifier
4221
- values["policy"] = json.dumps(policy, sort_keys=True)
4222
- values["depends_on"] = self.get_dependencies([user_tf_resource])
4296
+ values_policy: dict[str, Any] = {
4297
+ "name": identifier,
4298
+ "policy": json_dumps(policy),
4299
+ "depends_on": self.get_dependencies([user_tf_resource]),
4300
+ }
4223
4301
 
4224
- tf_aws_iam_policy = aws_iam_policy(identifier, **values)
4302
+ tf_aws_iam_policy = aws_iam_policy(identifier, **values_policy)
4225
4303
  tf_resources.append(tf_aws_iam_policy)
4226
4304
 
4227
4305
  tf_aws_iam_user_policy_attachment = aws_iam_user_policy_attachment(
@@ -4234,11 +4312,15 @@ class TerrascriptClient:
4234
4312
 
4235
4313
  return tf_resources
4236
4314
 
4237
- def get_tf_iam_access_key(self, user_tf_resource, identifier, output_prefix):
4238
- tf_resources = []
4239
- values = {}
4240
- values["user"] = identifier
4241
- values["depends_on"] = self.get_dependencies([user_tf_resource])
4315
+ def get_tf_iam_access_key(
4316
+ self, user_tf_resource: aws_iam_user, identifier: str, output_prefix: str
4317
+ ) -> list[TFResource]:
4318
+ tf_resources: list[TFResource] = []
4319
+ values: dict[str, Any] = {
4320
+ "user": identifier,
4321
+ "depends_on": self.get_dependencies([user_tf_resource]),
4322
+ }
4323
+
4242
4324
  tf_resource = aws_iam_access_key(identifier, **values)
4243
4325
  tf_resources.append(tf_resource)
4244
4326
  # outputs
@@ -4253,11 +4335,11 @@ class TerrascriptClient:
4253
4335
 
4254
4336
  return tf_resources
4255
4337
 
4256
- def add_resources(self, account, tf_resources):
4338
+ def add_resources(self, account: str, tf_resources: Iterable[TFResource]) -> None:
4257
4339
  for r in tf_resources:
4258
4340
  self.add_resource(account, r)
4259
4341
 
4260
- def add_resource(self, account, tf_resource):
4342
+ def add_resource(self, account: str, tf_resource: TFResource) -> None:
4261
4343
  if account not in self.locks:
4262
4344
  logging.debug(
4263
4345
  f"integration {self.integration} is disabled for account {account}. "
@@ -4267,7 +4349,7 @@ class TerrascriptClient:
4267
4349
  with self.locks[account]:
4268
4350
  self.tss[account].add(tf_resource)
4269
4351
 
4270
- def add_moved(self, account: str, moved: Moved):
4352
+ def add_moved(self, account: str, moved: Moved) -> None:
4271
4353
  if account not in self.locks:
4272
4354
  logging.debug(
4273
4355
  f"integration {self.integration} is disabled for account {account}. "
@@ -4331,12 +4413,11 @@ class TerrascriptClient:
4331
4413
 
4332
4414
  :return: key is AWS account name and value is terraform configuration
4333
4415
  """
4334
- return {
4335
- name: json.dumps(ts, indent=2, sort_keys=True)
4336
- for name, ts in self.tss.items()
4337
- }
4416
+ return {name: json_dumps(ts, indent=2) for name, ts in self.tss.items()}
4338
4417
 
4339
- def init_values(self, spec: ExternalResourceSpec, init_tags: bool = True) -> dict:
4418
+ def init_values(
4419
+ self, spec: ExternalResourceSpec, init_tags: bool = True
4420
+ ) -> dict[str, Any]:
4340
4421
  """
4341
4422
  Initialize the values of the terraform resource and merge the defaults and
4342
4423
  overrides.
@@ -4389,7 +4470,7 @@ class TerrascriptClient:
4389
4470
  return values
4390
4471
 
4391
4472
  @staticmethod
4392
- def aggregate_values(values):
4473
+ def aggregate_values(values: dict[str, Any]) -> None:
4393
4474
  split_char = "."
4394
4475
  copy = values.copy()
4395
4476
  for k, v in copy.items():
@@ -4403,18 +4484,17 @@ class TerrascriptClient:
4403
4484
  values.pop(k, None)
4404
4485
 
4405
4486
  @staticmethod
4406
- def override_values(values, overrides):
4487
+ def override_values(values: dict[str, Any], overrides: str | None) -> None:
4407
4488
  if overrides is None:
4408
4489
  return
4409
4490
  data = json.loads(overrides)
4410
- for k, v in data.items():
4411
- values[k] = v
4491
+ values.update(data)
4412
4492
 
4413
4493
  def init_common_outputs(
4414
4494
  self,
4415
- tf_resources: list[Resource],
4495
+ tf_resources: list[TFResource],
4416
4496
  spec: ExternalResourceSpec,
4417
- ):
4497
+ ) -> None:
4418
4498
  output_format = "{}__{}_{}"
4419
4499
  # cluster
4420
4500
  output_name = output_format.format(
@@ -4445,15 +4525,15 @@ class TerrascriptClient:
4445
4525
  output_name = output_format.format(
4446
4526
  spec.output_prefix, self.integration_prefix, "annotations"
4447
4527
  )
4448
- anno_json = json.dumps(spec.annotations()).encode("utf-8")
4528
+ anno_json = json_dumps(spec.annotations()).encode("utf-8")
4449
4529
  output_value = base64.b64encode(anno_json).decode()
4450
4530
  tf_resources.append(Output(output_name, value=output_value))
4451
4531
 
4452
- def prefetch_resources(self, schema) -> dict[str, dict[str, str]]:
4532
+ def prefetch_resources(self, schema: str) -> dict[str, dict[str, str]]:
4453
4533
  gqlapi = gql.get_api()
4454
4534
  return {r["path"]: r for r in gqlapi.get_resources_by_schema(schema)}
4455
4535
 
4456
- def get_raw_values(self, path) -> dict[str, str]:
4536
+ def get_raw_values(self, path: str) -> dict[str, str]:
4457
4537
  if path in self._resource_cache:
4458
4538
  return self._resource_cache[path]
4459
4539
 
@@ -4482,7 +4562,7 @@ class TerrascriptClient:
4482
4562
  ]
4483
4563
 
4484
4564
  @staticmethod
4485
- def get_elasticsearch_service_role_tf_resource():
4565
+ def get_elasticsearch_service_role_tf_resource() -> aws_iam_service_linked_role:
4486
4566
  """Service role for ElasticSearch."""
4487
4567
  service_role = {
4488
4568
  "aws_service_name": "es.amazonaws.com",
@@ -4490,7 +4570,7 @@ class TerrascriptClient:
4490
4570
  return aws_iam_service_linked_role("elasticsearch", **service_role)
4491
4571
 
4492
4572
  @staticmethod
4493
- def is_elasticsearch_domain_name_valid(name):
4573
+ def is_elasticsearch_domain_name_valid(name: str) -> bool:
4494
4574
  """Handle for Error creating Elasticsearch:
4495
4575
  InvalidParameterValue: Elasticsearch domain name must start with a
4496
4576
  lowercase letter and must be between 3 and 28 characters. Valid
@@ -4498,7 +4578,7 @@ class TerrascriptClient:
4498
4578
  if len(name) < 3 or len(name) > 28:
4499
4579
  return False
4500
4580
  pattern = r"^[a-z][a-z0-9-]+$"
4501
- return re.search(pattern, name)
4581
+ return re.search(pattern, name) is not None
4502
4582
 
4503
4583
  @staticmethod
4504
4584
  def elasticsearch_log_group_identifier(
@@ -4585,7 +4665,7 @@ class TerrascriptClient:
4585
4665
  }
4586
4666
  log_groups_policy_values = {
4587
4667
  "policy_name": "es-log-publishing-permissions",
4588
- "policy_document": json.dumps(log_groups_policy, sort_keys=True),
4668
+ "policy_document": json_dumps(log_groups_policy),
4589
4669
  }
4590
4670
  resource_policy = aws_cloudwatch_log_resource_policy(
4591
4671
  "es_log_publishing_resource_policy",
@@ -4600,7 +4680,7 @@ class TerrascriptClient:
4600
4680
  resource: Mapping[str, Any],
4601
4681
  values: Mapping[str, Any],
4602
4682
  output_prefix: str,
4603
- ) -> tuple[list[dict[str, Any]], list[dict[str, object]]]:
4683
+ ) -> tuple[list[TFResource], list[dict[str, object]]]:
4604
4684
  """
4605
4685
  Generate cloud_watch_log_group terraform_resources
4606
4686
  for the given resource. Further, generate
@@ -4608,7 +4688,7 @@ class TerrascriptClient:
4608
4688
  by the consumer.
4609
4689
  """
4610
4690
  es_log_group_retention_days = 90
4611
- tf_resources = []
4691
+ tf_resources: list[TFResource] = []
4612
4692
  publishing_options = []
4613
4693
 
4614
4694
  # res.get('', []) won't work, as publish_log_types is
@@ -4711,13 +4791,13 @@ class TerrascriptClient:
4711
4791
 
4712
4792
  return advanced_security_options
4713
4793
 
4714
- def populate_tf_resource_elasticsearch(self, spec):
4794
+ def populate_tf_resource_elasticsearch(self, spec: ExternalResourceSpec) -> None:
4715
4795
  account = spec.provisioner_name
4716
4796
  identifier = spec.identifier
4717
4797
  values = self.init_values(spec)
4718
4798
  output_prefix = spec.output_prefix
4719
4799
 
4720
- tf_resources = []
4800
+ tf_resources: list[TFResource] = []
4721
4801
  self.init_common_outputs(tf_resources, spec)
4722
4802
 
4723
4803
  if not self.is_elasticsearch_domain_name_valid(values["identifier"]):
@@ -4730,10 +4810,11 @@ class TerrascriptClient:
4730
4810
  )
4731
4811
 
4732
4812
  tags = values["tags"]
4733
- es_values = {}
4734
- es_values["domain_name"] = identifier
4735
- es_values["tags"] = tags
4736
- es_values["elasticsearch_version"] = values.get("elasticsearch_version")
4813
+ es_values: dict[str, Any] = {
4814
+ "domain_name": identifier,
4815
+ "tags": tags,
4816
+ "elasticsearch_version": values.get("elasticsearch_version"),
4817
+ }
4737
4818
 
4738
4819
  (
4739
4820
  log_group_resources,
@@ -4916,9 +4997,10 @@ class TerrascriptClient:
4916
4997
  }
4917
4998
  ],
4918
4999
  }
4919
- es_values["access_policies"] = json.dumps(access_policies, sort_keys=True)
5000
+ es_values["access_policies"] = json_dumps(access_policies)
4920
5001
 
4921
5002
  region = values.get("region") or self.default_regions.get(account)
5003
+ assert region # make mypy happy
4922
5004
  provider = ""
4923
5005
  if self._multiregion_account(account):
4924
5006
  provider = "aws." + region
@@ -4971,7 +5053,7 @@ class TerrascriptClient:
4971
5053
 
4972
5054
  version_values = {
4973
5055
  "secret_id": "${" + aws_secret_resource.id + "}",
4974
- "secret_string": json.dumps(master_user, sort_keys=True),
5056
+ "secret_string": json_dumps(master_user),
4975
5057
  }
4976
5058
  if provider:
4977
5059
  version_values["provider"] = provider
@@ -4998,7 +5080,7 @@ class TerrascriptClient:
4998
5080
  iam_policy_resource = aws_iam_policy(
4999
5081
  secret_identifier,
5000
5082
  name=f"{identifier}-secretsmanager-policy",
5001
- policy=json.dumps(policy, sort_keys=True),
5083
+ policy=json_dumps(policy),
5002
5084
  tags=tags,
5003
5085
  )
5004
5086
  tf_resources.append(iam_policy_resource)
@@ -5057,8 +5139,8 @@ class TerrascriptClient:
5057
5139
 
5058
5140
  # TODO: @fishi0x01 remove this function after migration APPSRE-3409
5059
5141
  def _build_es_advanced_security_options_deprecated(
5060
- self, advanced_security_options: MutableMapping[str, Any]
5061
- ) -> MutableMapping[str, Any]:
5142
+ self, advanced_security_options: dict[str, Any]
5143
+ ) -> dict[str, Any]:
5062
5144
  master_user_options = advanced_security_options.pop("master_user_options", {})
5063
5145
 
5064
5146
  if master_user_options:
@@ -5079,13 +5161,13 @@ class TerrascriptClient:
5079
5161
 
5080
5162
  return advanced_security_options
5081
5163
 
5082
- def populate_tf_resource_acm(self, spec):
5164
+ def populate_tf_resource_acm(self, spec: ExternalResourceSpec) -> None:
5083
5165
  account = spec.provisioner_name
5084
5166
  identifier = spec.identifier
5085
5167
  common_values = self.init_values(spec)
5086
5168
  output_prefix = spec.output_prefix
5087
5169
 
5088
- tf_resources = []
5170
+ tf_resources: list[TFResource] = []
5089
5171
  self.init_common_outputs(tf_resources, spec)
5090
5172
 
5091
5173
  values = {}
@@ -5124,6 +5206,7 @@ class TerrascriptClient:
5124
5206
  values["subject_alternative_names"] = alt_names
5125
5207
 
5126
5208
  region = common_values.get("region") or self.default_regions.get(account)
5209
+ assert region # make mypy happy
5127
5210
  if self._multiregion_account(account):
5128
5211
  values["provider"] = "aws." + region
5129
5212
 
@@ -5165,13 +5248,15 @@ class TerrascriptClient:
5165
5248
 
5166
5249
  self.add_resources(account, tf_resources)
5167
5250
 
5168
- def populate_tf_resource_s3_cloudfront_public_key(self, spec):
5251
+ def populate_tf_resource_s3_cloudfront_public_key(
5252
+ self, spec: ExternalResourceSpec
5253
+ ) -> None:
5169
5254
  account = spec.provisioner_name
5170
5255
  identifier = spec.identifier
5171
5256
  common_values = self.init_values(spec)
5172
5257
  output_prefix = spec.output_prefix
5173
5258
 
5174
- tf_resources = []
5259
+ tf_resources: list[TFResource] = []
5175
5260
  self.init_common_outputs(tf_resources, spec)
5176
5261
 
5177
5262
  values = {"name": identifier, "comment": "managed by app-interface"}
@@ -5213,8 +5298,13 @@ class TerrascriptClient:
5213
5298
  self.add_resources(account, tf_resources)
5214
5299
 
5215
5300
  def _get_alb_target_ips_by_openshift_service(
5216
- self, identifier, openshift_service, account_name, namespace_info, ocm_map
5217
- ):
5301
+ self,
5302
+ identifier: str,
5303
+ openshift_service: str,
5304
+ account_name: str,
5305
+ namespace_info: Mapping[str, Any],
5306
+ ocm_map: OCMMap,
5307
+ ) -> set[str]:
5218
5308
  account = self.accounts[account_name]
5219
5309
  cluster = namespace_info["cluster"]
5220
5310
  ocm = ocm_map.get(cluster["name"])
@@ -5240,17 +5330,24 @@ class TerrascriptClient:
5240
5330
  return ips
5241
5331
 
5242
5332
  @staticmethod
5243
- def _get_alb_rule_condition_value(condition):
5333
+ def _get_alb_rule_condition_value(
5334
+ condition: Mapping[str, Any],
5335
+ ) -> dict[str, dict[str, str]]:
5244
5336
  condition_type = condition["type"]
5245
5337
  condition_type_key = SUPPORTED_ALB_LISTENER_RULE_CONDITION_TYPE_MAPPING.get(
5246
5338
  condition_type
5247
5339
  )
5248
5340
  if condition_type_key is None:
5249
5341
  raise KeyError(f"unknown alb rule condition type {condition_type}")
5342
+
5343
+ # Query string conditions use a different structure than other condition types
5344
+ if condition_type == "query-string":
5345
+ return {condition_type_key: condition[condition_type_key]}
5346
+
5250
5347
  return {condition_type_key: {"values": condition[condition_type_key]}}
5251
5348
 
5252
5349
  @staticmethod
5253
- def _get_principal_for_s3_bucket_policy(region: str) -> Mapping[str, str]:
5350
+ def _get_principal_for_s3_bucket_policy(region: str) -> dict[str, str]:
5254
5351
  if region in AWS_ELB_ACCOUNT_IDS:
5255
5352
  return {"AWS": f"arn:aws:iam::{AWS_ELB_ACCOUNT_IDS[region]}:root"}
5256
5353
  if region in AWS_US_GOV_ELB_ACCOUNT_IDS:
@@ -5259,16 +5356,18 @@ class TerrascriptClient:
5259
5356
  }
5260
5357
  return {"Service": "logdelivery.elasticloadbalancing.amazonaws.com"}
5261
5358
 
5262
- def populate_tf_resource_alb(self, spec, ocm_map=None):
5359
+ def populate_tf_resource_alb(
5360
+ self, spec: ExternalResourceSpec, ocm_map: OCMMap | None = None
5361
+ ) -> None:
5263
5362
  account = spec.provisioner_name
5264
5363
  identifier = spec.identifier
5265
5364
  common_values = self.init_values(spec)
5266
5365
  output_prefix = spec.output_prefix
5267
- tf_resources = []
5366
+ tf_resources: list[TFResource] = []
5268
5367
  namespace_info = spec.namespace
5269
5368
  self.init_common_outputs(tf_resources, spec)
5270
5369
 
5271
- default_region = self.default_regions.get(account)
5370
+ default_region = self.default_regions[account]
5272
5371
  cluster_region = namespace_info["cluster"]["spec"]["region"]
5273
5372
 
5274
5373
  if self._multiregion_account(account):
@@ -5393,7 +5492,7 @@ class TerrascriptClient:
5393
5492
  lb_access_logs_s3_bucket_policy_values = {
5394
5493
  "provider": provider,
5395
5494
  "bucket": f"${{{lb_access_logs_s3_bucket_tf_resource.id}}}",
5396
- "policy": json.dumps(policy, sort_keys=True),
5495
+ "policy": json_dumps(policy),
5397
5496
  }
5398
5497
  lb_access_logs_s3_bucket_policy_tf_resource = aws_s3_bucket_policy(
5399
5498
  policy_identifier, **lb_access_logs_s3_bucket_policy_values
@@ -5421,6 +5520,10 @@ class TerrascriptClient:
5421
5520
  t_protocol_version = t.get("protocol_version") or "HTTP1"
5422
5521
 
5423
5522
  if t_openshift_service:
5523
+ if ocm_map is None:
5524
+ raise ValueError(
5525
+ "ocm_map should be not none raising exception to make mypy happy"
5526
+ )
5424
5527
  target_ips = self._get_alb_target_ips_by_openshift_service(
5425
5528
  identifier, t_openshift_service, account, namespace_info, ocm_map
5426
5529
  )
@@ -5593,7 +5696,7 @@ class TerrascriptClient:
5593
5696
  for rule_num, rule in enumerate(resource["rules"]):
5594
5697
  # https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb_listener_rule#type
5595
5698
  action = rule["action"]
5596
- action_values = {}
5699
+ action_values: dict[str, Any] = {}
5597
5700
  action_type = action.get("type")
5598
5701
  if action_type == "forward":
5599
5702
  action_values = {
@@ -5679,18 +5782,19 @@ class TerrascriptClient:
5679
5782
 
5680
5783
  self.add_resources(account, tf_resources)
5681
5784
 
5682
- def populate_tf_resource_secrets_manager(self, spec):
5785
+ def populate_tf_resource_secrets_manager(self, spec: ExternalResourceSpec) -> None:
5683
5786
  account = spec.provisioner_name
5684
5787
  identifier = spec.identifier
5685
5788
  common_values = self.init_values(spec)
5686
5789
  output_prefix = spec.output_prefix
5687
5790
 
5688
- tf_resources = []
5791
+ tf_resources: list[TFResource] = []
5689
5792
  self.init_common_outputs(tf_resources, spec)
5690
5793
 
5691
5794
  values = {"name": identifier}
5692
5795
 
5693
5796
  region = common_values.get("region") or self.default_regions.get(account)
5797
+ assert region # make mypy happy
5694
5798
  if self._multiregion_account(account):
5695
5799
  values["provider"] = "aws." + region
5696
5800
 
@@ -5698,11 +5802,16 @@ class TerrascriptClient:
5698
5802
  tf_resources.append(aws_secret_resource)
5699
5803
 
5700
5804
  secret = common_values.get("secret")
5805
+ assert secret # make mypy happy
5701
5806
  secret_data = self.secret_reader.read_all(secret)
5702
5807
 
5703
- version_values = {
5808
+ secret_format = common_values.get("secret_format")
5809
+ if secret_format is not None:
5810
+ secret_data = self._apply_secret_format(str(secret_format), secret_data)
5811
+
5812
+ version_values: dict[str, Any] = {
5704
5813
  "secret_id": "${" + aws_secret_resource.id + "}",
5705
- "secret_string": json.dumps(secret_data, sort_keys=True),
5814
+ "secret_string": json_dumps(secret_data),
5706
5815
  }
5707
5816
 
5708
5817
  if self._multiregion_account(account):
@@ -5723,6 +5832,66 @@ class TerrascriptClient:
5723
5832
 
5724
5833
  self.add_resources(account, tf_resources)
5725
5834
 
5835
+ @staticmethod
5836
+ def _unflatten_dotted_keys_dict(flat_dict: dict[str, str]) -> dict[str, Any]:
5837
+ """Convert a flat dictionary with dotted keys to a nested dictionary.
5838
+
5839
+ Example:
5840
+ {"db.host": "localhost", "db.port": "5432"} ->
5841
+ {"db": {"host": "localhost", "port": "5432"}}
5842
+
5843
+ Raises:
5844
+ ValueError: If there are conflicting keys (e.g., "a.b" and "a.b.c")
5845
+ """
5846
+ result: dict[str, Any] = {}
5847
+ for key, value in flat_dict.items():
5848
+ parts = key.split(".")
5849
+ current = result
5850
+ for i, part in enumerate(parts[:-1]):
5851
+ if part not in current:
5852
+ current[part] = {}
5853
+ elif not isinstance(current[part], dict):
5854
+ # Conflict: trying to traverse through a non-dict value
5855
+ conflicting_path = ".".join(parts[: i + 1])
5856
+ raise ValueError(
5857
+ f"Conflicting keys detected: '{conflicting_path}' is both a "
5858
+ f"value and a nested path in key '{key}'"
5859
+ )
5860
+ current = current[part]
5861
+
5862
+ # Check if we're trying to set a value where a dict already exists
5863
+ if parts[-1] in current and isinstance(current[parts[-1]], dict):
5864
+ raise ValueError(
5865
+ f"Conflicting keys detected: '{key}' conflicts with nested keys"
5866
+ )
5867
+
5868
+ current[parts[-1]] = value
5869
+
5870
+ return result
5871
+
5872
+ @staticmethod
5873
+ def _apply_secret_format(
5874
+ secret_format: str, secret_data: dict[str, str]
5875
+ ) -> dict[str, str]:
5876
+ # Convert flat dict with dotted keys to nested dict for Jinja2
5877
+ nested_secret_data = TerrascriptClient._unflatten_dotted_keys_dict(secret_data)
5878
+ rendered_data = process_jinja2_template(secret_format, nested_secret_data)
5879
+
5880
+ parsed_data = json.loads(rendered_data)
5881
+
5882
+ if not isinstance(parsed_data, dict):
5883
+ raise ValueError("secret_format must be a dictionary")
5884
+
5885
+ # validate secret is a dict[str, str]
5886
+ for k, v in parsed_data.items():
5887
+ if not isinstance(k, str):
5888
+ raise ValueError(f"key '{k}' is not a string")
5889
+
5890
+ if not isinstance(v, str):
5891
+ raise ValueError(f"dictionary value '{v}' under '{k}' is not a string")
5892
+
5893
+ return parsed_data
5894
+
5726
5895
  def get_commit_sha(self, repo_info: Mapping) -> str:
5727
5896
  url = repo_info["url"]
5728
5897
  ref = repo_info["ref"]
@@ -5741,7 +5910,8 @@ class TerrascriptClient:
5741
5910
  return commit.sha
5742
5911
  case "gitlab":
5743
5912
  gitlab = self.init_gitlab()
5744
- project = gitlab.get_project(url)
5913
+ if not (project := gitlab.get_project(url)):
5914
+ raise ValueError(f"could not find gitlab project for url {url}")
5745
5915
  commits = project.commits.list(ref_name=ref, per_page=1, page=1)
5746
5916
  return commits[0].id
5747
5917
  case _:
@@ -5781,7 +5951,7 @@ class TerrascriptClient:
5781
5951
  return True
5782
5952
  return False
5783
5953
 
5784
- def populate_tf_resource_asg(self, spec) -> None:
5954
+ def populate_tf_resource_asg(self, spec: ExternalResourceSpec) -> None:
5785
5955
  account = spec.provisioner_name
5786
5956
  identifier = spec.identifier
5787
5957
  common_values = self.init_values(spec)
@@ -5868,7 +6038,7 @@ class TerrascriptClient:
5868
6038
  template_resource = aws_launch_template(identifier, **template_values)
5869
6039
  tf_resources.append(template_resource)
5870
6040
 
5871
- asg_value = {
6041
+ asg_value: dict[str, Any] = {
5872
6042
  "name": identifier,
5873
6043
  "max_size": common_values.get("max_size"),
5874
6044
  "min_size": common_values.get("min_size"),
@@ -5924,12 +6094,12 @@ class TerrascriptClient:
5924
6094
 
5925
6095
  self.add_resources(account, tf_resources)
5926
6096
 
5927
- def populate_tf_resource_route53_zone(self, spec):
6097
+ def populate_tf_resource_route53_zone(self, spec: ExternalResourceSpec) -> None:
5928
6098
  account = spec.provisioner_name
5929
6099
  identifier = spec.identifier
5930
6100
  common_values = self.init_values(spec)
5931
6101
  output_prefix = spec.output_prefix
5932
- tf_resources = []
6102
+ tf_resources: list[TFResource] = []
5933
6103
  self.init_common_outputs(tf_resources, spec)
5934
6104
 
5935
6105
  # https://www.terraform.io/docs/providers/aws/r/route53_zone.html
@@ -5973,16 +6143,20 @@ class TerrascriptClient:
5973
6143
 
5974
6144
  self.add_resources(account, tf_resources)
5975
6145
 
5976
- def populate_tf_resource_rosa_authenticator(self, spec):
6146
+ def populate_tf_resource_rosa_authenticator(
6147
+ self, spec: ExternalResourceSpec
6148
+ ) -> None:
5977
6149
  account = spec.provisioner_name
5978
6150
  identifier = spec.identifier
5979
6151
  common_values = self.init_values(spec)
5980
- tf_resources = []
6152
+ tf_resources: list[TFResource] = []
5981
6153
  self.init_common_outputs(tf_resources, spec)
5982
6154
 
5983
6155
  # Prepare consts
5984
6156
  region = common_values.get("region") or self.default_regions.get(account)
6157
+ assert region # make mypy happy
5985
6158
  bucket_name = common_values.get("cognito_callback_bucket_name")
6159
+ assert bucket_name # make mypy happy
5986
6160
  bucket_url = f"https://{bucket_name}.s3.{region}.amazonaws.com"
5987
6161
  lambda_managed_policy_arn = (
5988
6162
  "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
@@ -5992,6 +6166,7 @@ class TerrascriptClient:
5992
6166
  vpc_id = common_values.get("vpc_id")
5993
6167
  subnet_ids = common_values.get("subnet_ids")
5994
6168
  network_interface_ids = common_values.get("network_interface_ids")
6169
+ assert network_interface_ids is not None # make mypy happy
5995
6170
  certificate_arn = common_values.get("certificate_arn")
5996
6171
  domain_name = common_values.get("domain_name")
5997
6172
  openshift_ingress_load_balancer_arn = common_values.get(
@@ -6017,7 +6192,7 @@ class TerrascriptClient:
6017
6192
  lambda_iam_role_resource = aws_iam_role(
6018
6193
  "lambda_role",
6019
6194
  name=f"ocm-{identifier}-cognito-lambda-role",
6020
- assume_role_policy=json.dumps(lambda_role_policy),
6195
+ assume_role_policy=json_dumps(lambda_role_policy),
6021
6196
  managed_policy_arns=[lambda_managed_policy_arn],
6022
6197
  force_detach_policies=False,
6023
6198
  max_session_duration=3600,
@@ -6692,7 +6867,7 @@ class TerrascriptClient:
6692
6867
  )
6693
6868
  tf_resources.append(api_gateway_stage_resource)
6694
6869
 
6695
- rest_api_policy = json.dumps({
6870
+ rest_api_policy = json_dumps({
6696
6871
  "Version": "2012-10-17",
6697
6872
  "Statement": [
6698
6873
  {
@@ -6796,7 +6971,7 @@ class TerrascriptClient:
6796
6971
  },
6797
6972
  ],
6798
6973
  }
6799
- cloudwatch_assume_role_policy = json.dumps(policy, sort_keys=True)
6974
+ cloudwatch_assume_role_policy = json_dumps(policy)
6800
6975
 
6801
6976
  cloudwatch_iam_role_resource = aws_iam_role(
6802
6977
  "cloudwatch_assume_role",
@@ -6824,7 +6999,7 @@ class TerrascriptClient:
6824
6999
  ],
6825
7000
  }
6826
7001
 
6827
- cloudwatch_iam_policy_document = json.dumps(policy, sort_keys=True)
7002
+ cloudwatch_iam_policy_document = json_dumps(policy)
6828
7003
 
6829
7004
  cloudwatch_iam_policy_resource = aws_iam_policy(
6830
7005
  "cloudwatch",
@@ -6868,15 +7043,18 @@ class TerrascriptClient:
6868
7043
 
6869
7044
  self.add_resources(account, tf_resources)
6870
7045
 
6871
- def populate_tf_resource_rosa_authenticator_vpce(self, spec):
7046
+ def populate_tf_resource_rosa_authenticator_vpce(
7047
+ self, spec: ExternalResourceSpec
7048
+ ) -> None:
6872
7049
  account = spec.provisioner_name
6873
7050
  identifier = spec.identifier
6874
7051
  common_values = self.init_values(spec)
6875
- tf_resources = []
7052
+ tf_resources: list[TFResource] = []
6876
7053
  self.init_common_outputs(tf_resources, spec)
6877
7054
 
6878
7055
  vpc_id = common_values.get("vpc_id")
6879
7056
  subnet_ids = common_values.get("subnet_ids")
7057
+ assert subnet_ids is not None # make mypy happy
6880
7058
  vpce_security_group_rule_common_args = common_values.get(
6881
7059
  "vpce_security_group_rule_common_properties", None
6882
7060
  )
@@ -6935,11 +7113,11 @@ class TerrascriptClient:
6935
7113
 
6936
7114
  self.add_resources(account, tf_resources)
6937
7115
 
6938
- def populate_tf_resource_msk(self, spec):
7116
+ def populate_tf_resource_msk(self, spec: ExternalResourceSpec) -> None:
6939
7117
  account = spec.provisioner_name
6940
7118
  values = self.init_values(spec)
6941
7119
  output_prefix = spec.output_prefix
6942
- tf_resources = []
7120
+ tf_resources: list[TFResource] = []
6943
7121
  resource_id = spec.identifier
6944
7122
 
6945
7123
  del values["identifier"]
@@ -7066,7 +7244,7 @@ class TerrascriptClient:
7066
7244
 
7067
7245
  version_values = {
7068
7246
  "secret_id": "${" + secret_resource.arn + "}",
7069
- "secret_string": json.dumps(secret, sort_keys=True),
7247
+ "secret_string": json_dumps(secret),
7070
7248
  }
7071
7249
  version_resource = aws_secretsmanager_secret_version(
7072
7250
  secret_identifier, **version_values
@@ -7075,7 +7253,7 @@ class TerrascriptClient:
7075
7253
 
7076
7254
  secret_policy_values = {
7077
7255
  "secret_arn": "${" + secret_resource.arn + "}",
7078
- "policy": json.dumps({
7256
+ "policy": json_dumps({
7079
7257
  "Version": "2012-10-17",
7080
7258
  "Statement": [
7081
7259
  {