qontract-reconcile 0.10.0__py3-none-any.whl → 0.10.1.dev1203__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 (844) hide show
  1. qontract_reconcile-0.10.1.dev1203.dist-info/METADATA +500 -0
  2. qontract_reconcile-0.10.1.dev1203.dist-info/RECORD +771 -0
  3. {qontract_reconcile-0.10.0.dist-info → qontract_reconcile-0.10.1.dev1203.dist-info}/WHEEL +1 -2
  4. {qontract_reconcile-0.10.0.dist-info → qontract_reconcile-0.10.1.dev1203.dist-info}/entry_points.txt +4 -2
  5. reconcile/acs_notifiers.py +126 -0
  6. reconcile/acs_policies.py +243 -0
  7. reconcile/acs_rbac.py +596 -0
  8. reconcile/aus/advanced_upgrade_service.py +621 -8
  9. reconcile/aus/aus_label_source.py +115 -0
  10. reconcile/aus/base.py +1053 -353
  11. reconcile/{utils → aus}/cluster_version_data.py +27 -12
  12. reconcile/aus/healthchecks.py +77 -0
  13. reconcile/aus/metrics.py +158 -0
  14. reconcile/aus/models.py +245 -5
  15. reconcile/aus/node_pool_spec.py +35 -0
  16. reconcile/aus/ocm_addons_upgrade_scheduler_org.py +225 -110
  17. reconcile/aus/ocm_upgrade_scheduler.py +76 -71
  18. reconcile/aus/ocm_upgrade_scheduler_org.py +81 -23
  19. reconcile/aus/version_gate_approver.py +204 -0
  20. reconcile/aus/version_gates/__init__.py +12 -0
  21. reconcile/aus/version_gates/handler.py +33 -0
  22. reconcile/aus/version_gates/ingress_gate_handler.py +32 -0
  23. reconcile/aus/version_gates/ocp_gate_handler.py +26 -0
  24. reconcile/aus/version_gates/sts_version_gate_handler.py +100 -0
  25. reconcile/aws_account_manager/README.md +5 -0
  26. reconcile/aws_account_manager/integration.py +373 -0
  27. reconcile/aws_account_manager/merge_request_manager.py +114 -0
  28. reconcile/aws_account_manager/metrics.py +39 -0
  29. reconcile/aws_account_manager/reconciler.py +403 -0
  30. reconcile/aws_account_manager/utils.py +41 -0
  31. reconcile/aws_ami_cleanup/integration.py +273 -0
  32. reconcile/aws_ami_share.py +18 -14
  33. reconcile/aws_cloudwatch_log_retention/integration.py +253 -0
  34. reconcile/aws_iam_keys.py +1 -1
  35. reconcile/aws_iam_password_reset.py +56 -20
  36. reconcile/aws_saml_idp/integration.py +204 -0
  37. reconcile/aws_saml_roles/integration.py +322 -0
  38. reconcile/aws_support_cases_sos.py +2 -2
  39. reconcile/aws_version_sync/integration.py +430 -0
  40. reconcile/aws_version_sync/merge_request_manager/merge_request.py +156 -0
  41. reconcile/aws_version_sync/merge_request_manager/merge_request_manager.py +160 -0
  42. reconcile/aws_version_sync/utils.py +64 -0
  43. reconcile/blackbox_exporter_endpoint_monitoring.py +2 -5
  44. reconcile/change_owners/README.md +34 -0
  45. reconcile/change_owners/approver.py +7 -9
  46. reconcile/change_owners/bundle.py +134 -9
  47. reconcile/change_owners/change_log_tracking.py +236 -0
  48. reconcile/change_owners/change_owners.py +204 -194
  49. reconcile/change_owners/change_types.py +183 -265
  50. reconcile/change_owners/changes.py +488 -0
  51. reconcile/change_owners/decision.py +120 -41
  52. reconcile/change_owners/diff.py +63 -92
  53. reconcile/change_owners/implicit_ownership.py +19 -16
  54. reconcile/change_owners/self_service_roles.py +158 -35
  55. reconcile/change_owners/tester.py +20 -18
  56. reconcile/checkpoint.py +4 -6
  57. reconcile/cli.py +1523 -242
  58. reconcile/closedbox_endpoint_monitoring_base.py +10 -17
  59. reconcile/cluster_auth_rhidp/integration.py +257 -0
  60. reconcile/cluster_deployment_mapper.py +2 -5
  61. reconcile/cna/assets/asset.py +4 -7
  62. reconcile/cna/assets/null.py +2 -5
  63. reconcile/cna/integration.py +2 -3
  64. reconcile/cna/state.py +6 -9
  65. reconcile/dashdotdb_base.py +31 -10
  66. reconcile/dashdotdb_cso.py +3 -6
  67. reconcile/dashdotdb_dora.py +530 -0
  68. reconcile/dashdotdb_dvo.py +10 -13
  69. reconcile/dashdotdb_slo.py +75 -19
  70. reconcile/database_access_manager.py +753 -0
  71. reconcile/deadmanssnitch.py +207 -0
  72. reconcile/dynatrace_token_provider/dependencies.py +69 -0
  73. reconcile/dynatrace_token_provider/integration.py +656 -0
  74. reconcile/dynatrace_token_provider/metrics.py +62 -0
  75. reconcile/dynatrace_token_provider/model.py +14 -0
  76. reconcile/dynatrace_token_provider/ocm.py +140 -0
  77. reconcile/dynatrace_token_provider/validate.py +48 -0
  78. reconcile/endpoints_discovery/integration.py +348 -0
  79. reconcile/endpoints_discovery/merge_request.py +96 -0
  80. reconcile/endpoints_discovery/merge_request_manager.py +178 -0
  81. reconcile/external_resources/aws.py +204 -0
  82. reconcile/external_resources/factories.py +163 -0
  83. reconcile/external_resources/integration.py +194 -0
  84. reconcile/external_resources/integration_secrets_sync.py +47 -0
  85. reconcile/external_resources/manager.py +405 -0
  86. reconcile/external_resources/meta.py +17 -0
  87. reconcile/external_resources/metrics.py +95 -0
  88. reconcile/external_resources/model.py +350 -0
  89. reconcile/external_resources/reconciler.py +265 -0
  90. reconcile/external_resources/secrets_sync.py +465 -0
  91. reconcile/external_resources/state.py +258 -0
  92. reconcile/gabi_authorized_users.py +19 -11
  93. reconcile/gcr_mirror.py +43 -34
  94. reconcile/github_org.py +4 -6
  95. reconcile/github_owners.py +1 -1
  96. reconcile/github_repo_invites.py +2 -5
  97. reconcile/gitlab_fork_compliance.py +14 -13
  98. reconcile/gitlab_housekeeping.py +185 -91
  99. reconcile/gitlab_labeler.py +15 -14
  100. reconcile/gitlab_members.py +126 -120
  101. reconcile/gitlab_owners.py +53 -66
  102. reconcile/gitlab_permissions.py +167 -6
  103. reconcile/glitchtip/README.md +150 -0
  104. reconcile/glitchtip/integration.py +99 -51
  105. reconcile/glitchtip/reconciler.py +99 -70
  106. reconcile/glitchtip_project_alerts/__init__.py +0 -0
  107. reconcile/glitchtip_project_alerts/integration.py +333 -0
  108. reconcile/glitchtip_project_dsn/integration.py +43 -43
  109. reconcile/gql_definitions/acs/__init__.py +0 -0
  110. reconcile/gql_definitions/acs/acs_instances.py +83 -0
  111. reconcile/gql_definitions/acs/acs_policies.py +239 -0
  112. reconcile/gql_definitions/acs/acs_rbac.py +111 -0
  113. reconcile/gql_definitions/advanced_upgrade_service/aus_clusters.py +46 -8
  114. reconcile/gql_definitions/advanced_upgrade_service/aus_organization.py +38 -8
  115. reconcile/gql_definitions/app_interface_metrics_exporter/__init__.py +0 -0
  116. reconcile/gql_definitions/app_interface_metrics_exporter/onboarding_status.py +61 -0
  117. reconcile/gql_definitions/aws_account_manager/__init__.py +0 -0
  118. reconcile/gql_definitions/aws_account_manager/aws_accounts.py +177 -0
  119. reconcile/gql_definitions/aws_ami_cleanup/__init__.py +0 -0
  120. reconcile/gql_definitions/aws_ami_cleanup/aws_accounts.py +161 -0
  121. reconcile/gql_definitions/aws_saml_idp/__init__.py +0 -0
  122. reconcile/gql_definitions/aws_saml_idp/aws_accounts.py +117 -0
  123. reconcile/gql_definitions/aws_saml_roles/__init__.py +0 -0
  124. reconcile/gql_definitions/aws_saml_roles/aws_accounts.py +117 -0
  125. reconcile/gql_definitions/aws_saml_roles/roles.py +97 -0
  126. reconcile/gql_definitions/aws_version_sync/__init__.py +0 -0
  127. reconcile/gql_definitions/aws_version_sync/clusters.py +83 -0
  128. reconcile/gql_definitions/aws_version_sync/namespaces.py +143 -0
  129. reconcile/gql_definitions/change_owners/queries/change_types.py +16 -29
  130. reconcile/gql_definitions/change_owners/queries/self_service_roles.py +45 -11
  131. reconcile/gql_definitions/cluster_auth_rhidp/__init__.py +0 -0
  132. reconcile/gql_definitions/cluster_auth_rhidp/clusters.py +128 -0
  133. reconcile/gql_definitions/cna/queries/cna_provisioners.py +6 -8
  134. reconcile/gql_definitions/cna/queries/cna_resources.py +3 -5
  135. reconcile/gql_definitions/common/alerting_services_settings.py +2 -2
  136. reconcile/gql_definitions/common/app_code_component_repos.py +9 -5
  137. reconcile/gql_definitions/{glitchtip/glitchtip_settings.py → common/app_interface_custom_messages.py} +14 -16
  138. reconcile/gql_definitions/common/app_interface_dms_settings.py +86 -0
  139. reconcile/gql_definitions/common/app_interface_repo_settings.py +2 -2
  140. reconcile/gql_definitions/common/app_interface_state_settings.py +3 -5
  141. reconcile/gql_definitions/common/app_interface_vault_settings.py +3 -5
  142. reconcile/gql_definitions/common/app_quay_repos_escalation_policies.py +120 -0
  143. reconcile/gql_definitions/common/apps.py +72 -0
  144. reconcile/gql_definitions/common/aws_vpc_requests.py +109 -0
  145. reconcile/gql_definitions/common/aws_vpcs.py +84 -0
  146. reconcile/gql_definitions/common/clusters.py +120 -254
  147. reconcile/gql_definitions/common/clusters_minimal.py +11 -35
  148. reconcile/gql_definitions/common/clusters_with_dms.py +72 -0
  149. reconcile/gql_definitions/common/clusters_with_peering.py +70 -98
  150. reconcile/gql_definitions/common/github_orgs.py +2 -2
  151. reconcile/gql_definitions/common/jira_settings.py +68 -0
  152. reconcile/gql_definitions/common/jiralert_settings.py +68 -0
  153. reconcile/gql_definitions/common/namespaces.py +74 -32
  154. reconcile/gql_definitions/common/namespaces_minimal.py +4 -10
  155. reconcile/gql_definitions/common/ocm_env_telemeter.py +95 -0
  156. reconcile/gql_definitions/common/ocm_environments.py +4 -2
  157. reconcile/gql_definitions/common/pagerduty_instances.py +5 -5
  158. reconcile/gql_definitions/common/pgp_reencryption_settings.py +5 -11
  159. reconcile/gql_definitions/common/pipeline_providers.py +45 -90
  160. reconcile/gql_definitions/common/quay_instances.py +64 -0
  161. reconcile/gql_definitions/common/quay_orgs.py +68 -0
  162. reconcile/gql_definitions/common/reserved_networks.py +94 -0
  163. reconcile/gql_definitions/common/saas_files.py +133 -95
  164. reconcile/gql_definitions/common/saas_target_namespaces.py +41 -26
  165. reconcile/gql_definitions/common/saasherder_settings.py +2 -2
  166. reconcile/gql_definitions/common/slack_workspaces.py +62 -0
  167. reconcile/gql_definitions/common/smtp_client_settings.py +2 -2
  168. reconcile/gql_definitions/common/state_aws_account.py +77 -0
  169. reconcile/gql_definitions/common/users.py +3 -2
  170. reconcile/gql_definitions/cost_report/__init__.py +0 -0
  171. reconcile/gql_definitions/cost_report/app_names.py +68 -0
  172. reconcile/gql_definitions/cost_report/cost_namespaces.py +86 -0
  173. reconcile/gql_definitions/cost_report/settings.py +77 -0
  174. reconcile/gql_definitions/dashdotdb_slo/slo_documents_query.py +42 -12
  175. reconcile/gql_definitions/dynatrace_token_provider/__init__.py +0 -0
  176. reconcile/gql_definitions/dynatrace_token_provider/dynatrace_bootstrap_tokens.py +79 -0
  177. reconcile/gql_definitions/dynatrace_token_provider/token_specs.py +84 -0
  178. reconcile/gql_definitions/endpoints_discovery/__init__.py +0 -0
  179. reconcile/gql_definitions/endpoints_discovery/namespaces.py +127 -0
  180. reconcile/gql_definitions/external_resources/__init__.py +0 -0
  181. reconcile/gql_definitions/external_resources/aws_accounts.py +73 -0
  182. reconcile/gql_definitions/external_resources/external_resources_modules.py +78 -0
  183. reconcile/gql_definitions/external_resources/external_resources_namespaces.py +1111 -0
  184. reconcile/gql_definitions/external_resources/external_resources_settings.py +98 -0
  185. reconcile/gql_definitions/fragments/aus_organization.py +34 -39
  186. reconcile/gql_definitions/fragments/aws_account_common.py +62 -0
  187. reconcile/gql_definitions/fragments/aws_account_managed.py +57 -0
  188. reconcile/gql_definitions/fragments/aws_account_sso.py +35 -0
  189. reconcile/gql_definitions/fragments/aws_infra_management_account.py +2 -2
  190. reconcile/gql_definitions/fragments/aws_vpc.py +47 -0
  191. reconcile/gql_definitions/fragments/aws_vpc_request.py +65 -0
  192. reconcile/gql_definitions/fragments/aws_vpc_request_subnet.py +29 -0
  193. reconcile/gql_definitions/fragments/deplopy_resources.py +7 -7
  194. reconcile/gql_definitions/fragments/disable.py +28 -0
  195. reconcile/gql_definitions/fragments/jumphost_common_fields.py +2 -2
  196. reconcile/gql_definitions/fragments/membership_source.py +47 -0
  197. reconcile/gql_definitions/fragments/minimal_ocm_organization.py +29 -0
  198. reconcile/gql_definitions/fragments/oc_connection_cluster.py +4 -9
  199. reconcile/gql_definitions/fragments/ocm_environment.py +5 -5
  200. reconcile/gql_definitions/fragments/pipeline_provider_retention.py +30 -0
  201. reconcile/gql_definitions/fragments/prometheus_instance.py +48 -0
  202. reconcile/gql_definitions/fragments/resource_limits_requirements.py +29 -0
  203. reconcile/gql_definitions/fragments/{resource_requirements.py → resource_requests_requirements.py} +3 -3
  204. reconcile/gql_definitions/fragments/resource_values.py +2 -2
  205. reconcile/gql_definitions/fragments/saas_target_namespace.py +55 -12
  206. reconcile/gql_definitions/fragments/serviceaccount_token.py +38 -0
  207. reconcile/gql_definitions/fragments/terraform_state.py +36 -0
  208. reconcile/gql_definitions/fragments/upgrade_policy.py +5 -3
  209. reconcile/gql_definitions/fragments/user.py +3 -2
  210. reconcile/gql_definitions/fragments/vault_secret.py +2 -2
  211. reconcile/gql_definitions/gitlab_members/gitlab_instances.py +6 -2
  212. reconcile/gql_definitions/gitlab_members/permissions.py +3 -5
  213. reconcile/gql_definitions/glitchtip/glitchtip_instance.py +16 -2
  214. reconcile/gql_definitions/glitchtip/glitchtip_project.py +22 -23
  215. reconcile/gql_definitions/glitchtip_project_alerts/__init__.py +0 -0
  216. reconcile/gql_definitions/glitchtip_project_alerts/glitchtip_project.py +173 -0
  217. reconcile/gql_definitions/integrations/integrations.py +62 -45
  218. reconcile/gql_definitions/introspection.json +51176 -0
  219. reconcile/gql_definitions/jenkins_configs/jenkins_configs.py +13 -5
  220. reconcile/gql_definitions/jenkins_configs/jenkins_instances.py +79 -0
  221. reconcile/gql_definitions/jira/__init__.py +0 -0
  222. reconcile/gql_definitions/jira/jira_servers.py +80 -0
  223. reconcile/gql_definitions/jira_permissions_validator/__init__.py +0 -0
  224. reconcile/gql_definitions/jira_permissions_validator/jira_boards_for_permissions_validator.py +131 -0
  225. reconcile/gql_definitions/jumphosts/jumphosts.py +3 -5
  226. reconcile/gql_definitions/ldap_groups/__init__.py +0 -0
  227. reconcile/gql_definitions/ldap_groups/roles.py +111 -0
  228. reconcile/gql_definitions/ldap_groups/settings.py +79 -0
  229. reconcile/gql_definitions/maintenance/__init__.py +0 -0
  230. reconcile/gql_definitions/maintenance/maintenances.py +101 -0
  231. reconcile/gql_definitions/membershipsources/__init__.py +0 -0
  232. reconcile/gql_definitions/membershipsources/roles.py +112 -0
  233. reconcile/gql_definitions/ocm_labels/__init__.py +0 -0
  234. reconcile/gql_definitions/ocm_labels/clusters.py +112 -0
  235. reconcile/gql_definitions/ocm_labels/organizations.py +78 -0
  236. reconcile/gql_definitions/ocm_subscription_labels/__init__.py +0 -0
  237. reconcile/gql_definitions/openshift_cluster_bots/__init__.py +0 -0
  238. reconcile/gql_definitions/openshift_cluster_bots/clusters.py +126 -0
  239. reconcile/gql_definitions/openshift_groups/managed_groups.py +2 -2
  240. reconcile/gql_definitions/openshift_groups/managed_roles.py +3 -2
  241. reconcile/gql_definitions/openshift_serviceaccount_tokens/__init__.py +0 -0
  242. reconcile/gql_definitions/openshift_serviceaccount_tokens/tokens.py +132 -0
  243. reconcile/gql_definitions/quay_membership/quay_membership.py +3 -5
  244. reconcile/gql_definitions/rhidp/__init__.py +0 -0
  245. reconcile/gql_definitions/rhidp/organizations.py +96 -0
  246. reconcile/gql_definitions/service_dependencies/jenkins_instance_fragment.py +2 -2
  247. reconcile/gql_definitions/service_dependencies/service_dependencies.py +9 -31
  248. reconcile/gql_definitions/sharding/aws_accounts.py +2 -2
  249. reconcile/gql_definitions/sharding/ocm_organization.py +63 -0
  250. reconcile/gql_definitions/skupper_network/site_controller_template.py +2 -2
  251. reconcile/gql_definitions/skupper_network/skupper_networks.py +12 -38
  252. reconcile/gql_definitions/slack_usergroups/clusters.py +2 -2
  253. reconcile/gql_definitions/slack_usergroups/permissions.py +8 -15
  254. reconcile/gql_definitions/slack_usergroups/users.py +3 -2
  255. reconcile/gql_definitions/slo_documents/__init__.py +0 -0
  256. reconcile/gql_definitions/slo_documents/slo_documents.py +142 -0
  257. reconcile/gql_definitions/status_board/__init__.py +0 -0
  258. reconcile/gql_definitions/status_board/status_board.py +163 -0
  259. reconcile/gql_definitions/statuspage/statuspages.py +56 -7
  260. reconcile/gql_definitions/templating/__init__.py +0 -0
  261. reconcile/gql_definitions/templating/template_collection.py +130 -0
  262. reconcile/gql_definitions/templating/templates.py +108 -0
  263. reconcile/gql_definitions/terraform_cloudflare_dns/app_interface_cloudflare_dns_settings.py +4 -8
  264. reconcile/gql_definitions/terraform_cloudflare_dns/terraform_cloudflare_zones.py +8 -8
  265. reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_accounts.py +6 -8
  266. reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_resources.py +45 -56
  267. reconcile/gql_definitions/terraform_cloudflare_users/app_interface_setting_cloudflare_and_vault.py +4 -8
  268. reconcile/gql_definitions/terraform_cloudflare_users/terraform_cloudflare_roles.py +4 -8
  269. reconcile/gql_definitions/terraform_init/__init__.py +0 -0
  270. reconcile/gql_definitions/terraform_init/aws_accounts.py +93 -0
  271. reconcile/gql_definitions/terraform_repo/__init__.py +0 -0
  272. reconcile/gql_definitions/terraform_repo/terraform_repo.py +141 -0
  273. reconcile/gql_definitions/terraform_resources/database_access_manager.py +158 -0
  274. reconcile/gql_definitions/terraform_resources/terraform_resources_namespaces.py +153 -162
  275. reconcile/gql_definitions/terraform_tgw_attachments/__init__.py +0 -0
  276. reconcile/gql_definitions/terraform_tgw_attachments/aws_accounts.py +119 -0
  277. reconcile/gql_definitions/unleash_feature_toggles/__init__.py +0 -0
  278. reconcile/gql_definitions/unleash_feature_toggles/feature_toggles.py +113 -0
  279. reconcile/gql_definitions/vault_instances/vault_instances.py +17 -50
  280. reconcile/gql_definitions/vault_policies/vault_policies.py +2 -2
  281. reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator.py +49 -12
  282. reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator_peered_cluster_fragment.py +7 -2
  283. reconcile/integrations_manager.py +25 -13
  284. reconcile/jenkins/types.py +5 -1
  285. reconcile/jenkins_base.py +36 -0
  286. reconcile/jenkins_job_builder.py +10 -48
  287. reconcile/jenkins_job_builds_cleaner.py +40 -25
  288. reconcile/jenkins_job_cleaner.py +1 -3
  289. reconcile/jenkins_roles.py +22 -26
  290. reconcile/jenkins_webhooks.py +9 -6
  291. reconcile/jenkins_worker_fleets.py +11 -6
  292. reconcile/jira_permissions_validator.py +340 -0
  293. reconcile/jira_watcher.py +3 -5
  294. reconcile/ldap_groups/__init__.py +0 -0
  295. reconcile/ldap_groups/integration.py +279 -0
  296. reconcile/ldap_users.py +3 -0
  297. reconcile/ocm/types.py +39 -59
  298. reconcile/ocm_additional_routers.py +0 -1
  299. reconcile/ocm_addons_upgrade_tests_trigger.py +10 -15
  300. reconcile/ocm_aws_infrastructure_access.py +30 -32
  301. reconcile/ocm_clusters.py +217 -130
  302. reconcile/ocm_external_configuration_labels.py +15 -0
  303. reconcile/ocm_github_idp.py +1 -1
  304. reconcile/ocm_groups.py +25 -5
  305. reconcile/ocm_internal_notifications/__init__.py +0 -0
  306. reconcile/ocm_internal_notifications/integration.py +119 -0
  307. reconcile/ocm_labels/__init__.py +0 -0
  308. reconcile/ocm_labels/integration.py +409 -0
  309. reconcile/ocm_machine_pools.py +517 -108
  310. reconcile/ocm_upgrade_scheduler_org_updater.py +15 -11
  311. reconcile/openshift_base.py +609 -207
  312. reconcile/openshift_cluster_bots.py +344 -0
  313. reconcile/openshift_clusterrolebindings.py +15 -15
  314. reconcile/openshift_groups.py +42 -45
  315. reconcile/openshift_limitranges.py +1 -0
  316. reconcile/openshift_namespace_labels.py +22 -28
  317. reconcile/openshift_namespaces.py +22 -22
  318. reconcile/openshift_network_policies.py +4 -8
  319. reconcile/openshift_prometheus_rules.py +43 -0
  320. reconcile/openshift_resourcequotas.py +2 -16
  321. reconcile/openshift_resources.py +12 -10
  322. reconcile/openshift_resources_base.py +304 -328
  323. reconcile/openshift_rolebindings.py +18 -20
  324. reconcile/openshift_saas_deploy.py +105 -21
  325. reconcile/openshift_saas_deploy_change_tester.py +30 -35
  326. reconcile/openshift_saas_deploy_trigger_base.py +39 -36
  327. reconcile/openshift_saas_deploy_trigger_cleaner.py +41 -27
  328. reconcile/openshift_saas_deploy_trigger_configs.py +1 -2
  329. reconcile/openshift_saas_deploy_trigger_images.py +1 -2
  330. reconcile/openshift_saas_deploy_trigger_moving_commits.py +1 -2
  331. reconcile/openshift_saas_deploy_trigger_upstream_jobs.py +1 -2
  332. reconcile/openshift_serviceaccount_tokens.py +138 -74
  333. reconcile/openshift_tekton_resources.py +89 -24
  334. reconcile/openshift_upgrade_watcher.py +110 -62
  335. reconcile/openshift_users.py +16 -15
  336. reconcile/openshift_vault_secrets.py +11 -6
  337. reconcile/oum/__init__.py +0 -0
  338. reconcile/oum/base.py +387 -0
  339. reconcile/oum/labelset.py +55 -0
  340. reconcile/oum/metrics.py +71 -0
  341. reconcile/oum/models.py +69 -0
  342. reconcile/oum/providers.py +59 -0
  343. reconcile/oum/standalone.py +196 -0
  344. reconcile/prometheus_rules_tester/integration.py +31 -23
  345. reconcile/quay_base.py +4 -1
  346. reconcile/quay_membership.py +1 -2
  347. reconcile/quay_mirror.py +111 -61
  348. reconcile/quay_mirror_org.py +34 -21
  349. reconcile/quay_permissions.py +7 -3
  350. reconcile/quay_repos.py +24 -32
  351. reconcile/queries.py +263 -198
  352. reconcile/query_validator.py +3 -5
  353. reconcile/resource_scraper.py +3 -4
  354. reconcile/{template_tester.py → resource_template_tester.py} +3 -3
  355. reconcile/rhidp/__init__.py +0 -0
  356. reconcile/rhidp/common.py +214 -0
  357. reconcile/rhidp/metrics.py +20 -0
  358. reconcile/rhidp/ocm_oidc_idp/__init__.py +0 -0
  359. reconcile/rhidp/ocm_oidc_idp/base.py +221 -0
  360. reconcile/rhidp/ocm_oidc_idp/integration.py +56 -0
  361. reconcile/rhidp/ocm_oidc_idp/metrics.py +22 -0
  362. reconcile/rhidp/sso_client/__init__.py +0 -0
  363. reconcile/rhidp/sso_client/base.py +266 -0
  364. reconcile/rhidp/sso_client/integration.py +60 -0
  365. reconcile/rhidp/sso_client/metrics.py +39 -0
  366. reconcile/run_integration.py +293 -0
  367. reconcile/saas_auto_promotions_manager/integration.py +69 -24
  368. reconcile/saas_auto_promotions_manager/merge_request_manager/batcher.py +208 -0
  369. reconcile/saas_auto_promotions_manager/merge_request_manager/desired_state.py +28 -0
  370. reconcile/saas_auto_promotions_manager/merge_request_manager/merge_request.py +3 -4
  371. reconcile/saas_auto_promotions_manager/merge_request_manager/merge_request_manager_v2.py +172 -0
  372. reconcile/saas_auto_promotions_manager/merge_request_manager/metrics.py +42 -0
  373. reconcile/saas_auto_promotions_manager/merge_request_manager/mr_parser.py +226 -0
  374. reconcile/saas_auto_promotions_manager/merge_request_manager/open_merge_requests.py +23 -0
  375. reconcile/saas_auto_promotions_manager/merge_request_manager/renderer.py +108 -32
  376. reconcile/saas_auto_promotions_manager/meta.py +4 -0
  377. reconcile/saas_auto_promotions_manager/publisher.py +32 -4
  378. reconcile/saas_auto_promotions_manager/s3_exporter.py +77 -0
  379. reconcile/saas_auto_promotions_manager/subscriber.py +110 -23
  380. reconcile/saas_auto_promotions_manager/utils/saas_files_inventory.py +48 -41
  381. reconcile/saas_file_validator.py +16 -6
  382. reconcile/sendgrid_teammates.py +27 -12
  383. reconcile/service_dependencies.py +0 -3
  384. reconcile/signalfx_endpoint_monitoring.py +2 -5
  385. reconcile/skupper_network/integration.py +10 -11
  386. reconcile/skupper_network/models.py +3 -5
  387. reconcile/skupper_network/reconciler.py +28 -35
  388. reconcile/skupper_network/site_controller.py +8 -8
  389. reconcile/slack_base.py +4 -7
  390. reconcile/slack_usergroups.py +249 -171
  391. reconcile/sql_query.py +324 -171
  392. reconcile/status.py +0 -1
  393. reconcile/status_board.py +275 -0
  394. reconcile/statuspage/__init__.py +0 -5
  395. reconcile/statuspage/atlassian.py +219 -80
  396. reconcile/statuspage/integration.py +9 -97
  397. reconcile/statuspage/integrations/__init__.py +0 -0
  398. reconcile/statuspage/integrations/components.py +77 -0
  399. reconcile/statuspage/integrations/maintenances.py +111 -0
  400. reconcile/statuspage/page.py +107 -72
  401. reconcile/statuspage/state.py +6 -11
  402. reconcile/statuspage/status.py +8 -12
  403. reconcile/templates/rosa-classic-cluster-creation.sh.j2 +60 -0
  404. reconcile/templates/rosa-hcp-cluster-creation.sh.j2 +61 -0
  405. reconcile/templating/__init__.py +0 -0
  406. reconcile/templating/lib/__init__.py +0 -0
  407. reconcile/templating/lib/merge_request_manager.py +180 -0
  408. reconcile/templating/lib/model.py +20 -0
  409. reconcile/templating/lib/rendering.py +191 -0
  410. reconcile/templating/renderer.py +410 -0
  411. reconcile/templating/validator.py +153 -0
  412. reconcile/terraform_aws_route53.py +13 -10
  413. reconcile/terraform_cloudflare_dns.py +92 -122
  414. reconcile/terraform_cloudflare_resources.py +15 -13
  415. reconcile/terraform_cloudflare_users.py +27 -27
  416. reconcile/terraform_init/__init__.py +0 -0
  417. reconcile/terraform_init/integration.py +165 -0
  418. reconcile/terraform_init/merge_request.py +57 -0
  419. reconcile/terraform_init/merge_request_manager.py +102 -0
  420. reconcile/terraform_repo.py +403 -0
  421. reconcile/terraform_resources.py +266 -168
  422. reconcile/terraform_tgw_attachments.py +417 -167
  423. reconcile/terraform_users.py +40 -17
  424. reconcile/terraform_vpc_peerings.py +310 -142
  425. reconcile/terraform_vpc_resources/__init__.py +0 -0
  426. reconcile/terraform_vpc_resources/integration.py +220 -0
  427. reconcile/terraform_vpc_resources/merge_request.py +57 -0
  428. reconcile/terraform_vpc_resources/merge_request_manager.py +107 -0
  429. reconcile/typed_queries/alerting_services_settings.py +1 -2
  430. reconcile/typed_queries/app_interface_custom_messages.py +24 -0
  431. reconcile/typed_queries/app_interface_deadmanssnitch_settings.py +17 -0
  432. reconcile/typed_queries/app_interface_metrics_exporter/__init__.py +0 -0
  433. reconcile/typed_queries/app_interface_metrics_exporter/onboarding_status.py +13 -0
  434. reconcile/typed_queries/app_interface_repo_url.py +1 -2
  435. reconcile/typed_queries/app_interface_state_settings.py +1 -3
  436. reconcile/typed_queries/app_interface_vault_settings.py +1 -2
  437. reconcile/typed_queries/app_quay_repos_escalation_policies.py +14 -0
  438. reconcile/typed_queries/apps.py +11 -0
  439. reconcile/typed_queries/aws_vpc_requests.py +9 -0
  440. reconcile/typed_queries/aws_vpcs.py +12 -0
  441. reconcile/typed_queries/cloudflare.py +10 -0
  442. reconcile/typed_queries/clusters.py +7 -5
  443. reconcile/typed_queries/clusters_minimal.py +6 -5
  444. reconcile/typed_queries/clusters_with_dms.py +16 -0
  445. reconcile/typed_queries/cost_report/__init__.py +0 -0
  446. reconcile/typed_queries/cost_report/app_names.py +22 -0
  447. reconcile/typed_queries/cost_report/cost_namespaces.py +43 -0
  448. reconcile/typed_queries/cost_report/settings.py +15 -0
  449. reconcile/typed_queries/dynatrace.py +10 -0
  450. reconcile/typed_queries/dynatrace_environments.py +14 -0
  451. reconcile/typed_queries/dynatrace_token_provider_token_specs.py +14 -0
  452. reconcile/typed_queries/external_resources.py +46 -0
  453. reconcile/typed_queries/get_state_aws_account.py +20 -0
  454. reconcile/typed_queries/glitchtip.py +10 -0
  455. reconcile/typed_queries/jenkins.py +25 -0
  456. reconcile/typed_queries/jira.py +7 -0
  457. reconcile/typed_queries/jira_settings.py +16 -0
  458. reconcile/typed_queries/jiralert_settings.py +22 -0
  459. reconcile/typed_queries/ocm.py +8 -0
  460. reconcile/typed_queries/pagerduty_instances.py +2 -7
  461. reconcile/typed_queries/quay.py +23 -0
  462. reconcile/typed_queries/repos.py +20 -8
  463. reconcile/typed_queries/reserved_networks.py +12 -0
  464. reconcile/typed_queries/saas_files.py +221 -167
  465. reconcile/typed_queries/slack.py +7 -0
  466. reconcile/typed_queries/slo_documents.py +12 -0
  467. reconcile/typed_queries/status_board.py +58 -0
  468. reconcile/typed_queries/tekton_pipeline_providers.py +1 -2
  469. reconcile/typed_queries/terraform_namespaces.py +1 -2
  470. reconcile/typed_queries/terraform_tgw_attachments/__init__.py +0 -0
  471. reconcile/typed_queries/terraform_tgw_attachments/aws_accounts.py +16 -0
  472. reconcile/typed_queries/unleash.py +10 -0
  473. reconcile/typed_queries/users.py +11 -0
  474. reconcile/typed_queries/vault.py +10 -0
  475. reconcile/unleash_feature_toggles/__init__.py +0 -0
  476. reconcile/unleash_feature_toggles/integration.py +287 -0
  477. reconcile/utils/acs/__init__.py +0 -0
  478. reconcile/utils/acs/base.py +81 -0
  479. reconcile/utils/acs/notifiers.py +143 -0
  480. reconcile/utils/acs/policies.py +163 -0
  481. reconcile/utils/acs/rbac.py +277 -0
  482. reconcile/utils/aggregated_list.py +11 -9
  483. reconcile/utils/amtool.py +6 -4
  484. reconcile/utils/aws_api.py +279 -66
  485. reconcile/utils/aws_api_typed/__init__.py +0 -0
  486. reconcile/utils/aws_api_typed/account.py +23 -0
  487. reconcile/utils/aws_api_typed/api.py +273 -0
  488. reconcile/utils/aws_api_typed/dynamodb.py +16 -0
  489. reconcile/utils/aws_api_typed/iam.py +67 -0
  490. reconcile/utils/aws_api_typed/organization.py +152 -0
  491. reconcile/utils/aws_api_typed/s3.py +26 -0
  492. reconcile/utils/aws_api_typed/service_quotas.py +79 -0
  493. reconcile/utils/aws_api_typed/sts.py +36 -0
  494. reconcile/utils/aws_api_typed/support.py +79 -0
  495. reconcile/utils/aws_helper.py +42 -3
  496. reconcile/utils/batches.py +11 -0
  497. reconcile/utils/binary.py +7 -9
  498. reconcile/utils/cloud_resource_best_practice/__init__.py +0 -0
  499. reconcile/utils/cloud_resource_best_practice/aws_rds.py +66 -0
  500. reconcile/utils/clusterhealth/__init__.py +0 -0
  501. reconcile/utils/clusterhealth/providerbase.py +39 -0
  502. reconcile/utils/clusterhealth/telemeter.py +39 -0
  503. reconcile/utils/config.py +3 -4
  504. reconcile/utils/deadmanssnitch_api.py +86 -0
  505. reconcile/utils/differ.py +205 -0
  506. reconcile/utils/disabled_integrations.py +4 -6
  507. reconcile/utils/dynatrace/__init__.py +0 -0
  508. reconcile/utils/dynatrace/client.py +93 -0
  509. reconcile/utils/early_exit_cache.py +289 -0
  510. reconcile/utils/elasticsearch_exceptions.py +5 -0
  511. reconcile/utils/environ.py +2 -2
  512. reconcile/utils/exceptions.py +4 -0
  513. reconcile/utils/expiration.py +4 -8
  514. reconcile/utils/extended_early_exit.py +210 -0
  515. reconcile/utils/external_resource_spec.py +34 -12
  516. reconcile/utils/external_resources.py +48 -20
  517. reconcile/utils/filtering.py +16 -0
  518. reconcile/utils/git.py +49 -16
  519. reconcile/utils/github_api.py +10 -9
  520. reconcile/utils/gitlab_api.py +333 -190
  521. reconcile/utils/glitchtip/client.py +97 -100
  522. reconcile/utils/glitchtip/models.py +89 -11
  523. reconcile/utils/gql.py +157 -58
  524. reconcile/utils/grouping.py +17 -0
  525. reconcile/utils/helm.py +89 -18
  526. reconcile/utils/helpers.py +51 -0
  527. reconcile/utils/imap_client.py +5 -6
  528. reconcile/utils/internal_groups/__init__.py +0 -0
  529. reconcile/utils/internal_groups/client.py +160 -0
  530. reconcile/utils/internal_groups/models.py +71 -0
  531. reconcile/utils/jenkins_api.py +10 -34
  532. reconcile/utils/jinja2/__init__.py +0 -0
  533. reconcile/utils/{jinja2_ext.py → jinja2/extensions.py} +6 -4
  534. reconcile/utils/jinja2/filters.py +142 -0
  535. reconcile/utils/jinja2/utils.py +278 -0
  536. reconcile/utils/jira_client.py +165 -8
  537. reconcile/utils/jjb_client.py +47 -35
  538. reconcile/utils/jobcontroller/__init__.py +0 -0
  539. reconcile/utils/jobcontroller/controller.py +413 -0
  540. reconcile/utils/jobcontroller/models.py +195 -0
  541. reconcile/utils/jsonpath.py +4 -5
  542. reconcile/utils/jump_host.py +13 -12
  543. reconcile/utils/keycloak.py +106 -0
  544. reconcile/utils/ldap_client.py +35 -6
  545. reconcile/utils/lean_terraform_client.py +115 -6
  546. reconcile/utils/membershipsources/__init__.py +0 -0
  547. reconcile/utils/membershipsources/app_interface_resolver.py +60 -0
  548. reconcile/utils/membershipsources/models.py +91 -0
  549. reconcile/utils/membershipsources/resolver.py +110 -0
  550. reconcile/utils/merge_request_manager/__init__.py +0 -0
  551. reconcile/utils/merge_request_manager/merge_request_manager.py +99 -0
  552. reconcile/utils/merge_request_manager/parser.py +67 -0
  553. reconcile/utils/metrics.py +511 -1
  554. reconcile/utils/models.py +123 -0
  555. reconcile/utils/mr/README.md +198 -0
  556. reconcile/utils/mr/__init__.py +14 -10
  557. reconcile/utils/mr/app_interface_reporter.py +2 -2
  558. reconcile/utils/mr/aws_access.py +4 -4
  559. reconcile/utils/mr/base.py +51 -31
  560. reconcile/utils/mr/clusters_updates.py +10 -7
  561. reconcile/utils/mr/glitchtip_access_reporter.py +2 -4
  562. reconcile/utils/mr/labels.py +14 -1
  563. reconcile/utils/mr/notificator.py +1 -3
  564. reconcile/utils/mr/ocm_update_recommended_version.py +1 -2
  565. reconcile/utils/mr/ocm_upgrade_scheduler_org_updates.py +7 -3
  566. reconcile/utils/mr/promote_qontract.py +203 -0
  567. reconcile/utils/mr/user_maintenance.py +24 -4
  568. reconcile/utils/oauth2_backend_application_session.py +132 -0
  569. reconcile/utils/oc.py +194 -170
  570. reconcile/utils/oc_connection_parameters.py +40 -51
  571. reconcile/utils/oc_filters.py +11 -13
  572. reconcile/utils/oc_map.py +14 -35
  573. reconcile/utils/ocm/__init__.py +30 -1
  574. reconcile/utils/ocm/addons.py +228 -0
  575. reconcile/utils/ocm/base.py +618 -5
  576. reconcile/utils/ocm/cluster_groups.py +5 -56
  577. reconcile/utils/ocm/clusters.py +111 -99
  578. reconcile/utils/ocm/identity_providers.py +66 -0
  579. reconcile/utils/ocm/label_sources.py +75 -0
  580. reconcile/utils/ocm/labels.py +139 -54
  581. reconcile/utils/ocm/manifests.py +39 -0
  582. reconcile/utils/ocm/ocm.py +182 -928
  583. reconcile/utils/ocm/products.py +758 -0
  584. reconcile/utils/ocm/search_filters.py +20 -28
  585. reconcile/utils/ocm/service_log.py +32 -79
  586. reconcile/utils/ocm/sre_capability_labels.py +51 -0
  587. reconcile/utils/ocm/status_board.py +66 -0
  588. reconcile/utils/ocm/subscriptions.py +49 -59
  589. reconcile/utils/ocm/syncsets.py +39 -0
  590. reconcile/utils/ocm/upgrades.py +181 -0
  591. reconcile/utils/ocm_base_client.py +71 -36
  592. reconcile/utils/openshift_resource.py +113 -67
  593. reconcile/utils/output.py +18 -11
  594. reconcile/utils/pagerduty_api.py +16 -10
  595. reconcile/utils/parse_dhms_duration.py +13 -1
  596. reconcile/utils/prometheus.py +123 -0
  597. reconcile/utils/promotion_state.py +56 -19
  598. reconcile/utils/promtool.py +5 -8
  599. reconcile/utils/quay_api.py +13 -25
  600. reconcile/utils/raw_github_api.py +3 -5
  601. reconcile/utils/repo_owners.py +2 -8
  602. reconcile/utils/rest_api_base.py +126 -0
  603. reconcile/utils/rosa/__init__.py +0 -0
  604. reconcile/utils/rosa/rosa_cli.py +310 -0
  605. reconcile/utils/rosa/session.py +201 -0
  606. reconcile/utils/ruamel.py +16 -0
  607. reconcile/utils/runtime/__init__.py +0 -1
  608. reconcile/utils/runtime/desired_state_diff.py +9 -20
  609. reconcile/utils/runtime/environment.py +33 -8
  610. reconcile/utils/runtime/integration.py +28 -12
  611. reconcile/utils/runtime/meta.py +1 -3
  612. reconcile/utils/runtime/runner.py +8 -11
  613. reconcile/utils/runtime/sharding.py +93 -36
  614. reconcile/utils/saasherder/__init__.py +1 -1
  615. reconcile/utils/saasherder/interfaces.py +143 -138
  616. reconcile/utils/saasherder/models.py +201 -43
  617. reconcile/utils/saasherder/saasherder.py +508 -378
  618. reconcile/utils/secret_reader.py +22 -27
  619. reconcile/utils/semver_helper.py +15 -1
  620. reconcile/utils/slack_api.py +124 -36
  621. reconcile/utils/smtp_client.py +1 -2
  622. reconcile/utils/sqs_gateway.py +10 -6
  623. reconcile/utils/state.py +276 -127
  624. reconcile/utils/terraform/config_client.py +6 -7
  625. reconcile/utils/terraform_client.py +284 -125
  626. reconcile/utils/terrascript/cloudflare_client.py +38 -17
  627. reconcile/utils/terrascript/cloudflare_resources.py +67 -18
  628. reconcile/utils/terrascript/models.py +2 -3
  629. reconcile/utils/terrascript/resources.py +1 -2
  630. reconcile/utils/terrascript_aws_client.py +1292 -540
  631. reconcile/utils/three_way_diff_strategy.py +157 -0
  632. reconcile/utils/unleash/__init__.py +11 -0
  633. reconcile/utils/{unleash.py → unleash/client.py} +35 -29
  634. reconcile/utils/unleash/server.py +145 -0
  635. reconcile/utils/vault.py +42 -32
  636. reconcile/utils/vaultsecretref.py +2 -4
  637. reconcile/utils/vcs.py +250 -0
  638. reconcile/vault_replication.py +38 -31
  639. reconcile/vpc_peerings_validator.py +82 -13
  640. tools/app_interface_metrics_exporter.py +70 -0
  641. tools/app_interface_reporter.py +44 -157
  642. tools/cli_commands/container_images_report.py +154 -0
  643. tools/cli_commands/cost_report/__init__.py +0 -0
  644. tools/cli_commands/cost_report/aws.py +137 -0
  645. tools/cli_commands/cost_report/cost_management_api.py +155 -0
  646. tools/cli_commands/cost_report/model.py +49 -0
  647. tools/cli_commands/cost_report/openshift.py +166 -0
  648. tools/cli_commands/cost_report/openshift_cost_optimization.py +187 -0
  649. tools/cli_commands/cost_report/response.py +124 -0
  650. tools/cli_commands/cost_report/util.py +72 -0
  651. tools/cli_commands/cost_report/view.py +524 -0
  652. tools/cli_commands/erv2.py +620 -0
  653. tools/cli_commands/gpg_encrypt.py +5 -8
  654. tools/cli_commands/systems_and_tools.py +489 -0
  655. tools/glitchtip_access_revalidation.py +1 -1
  656. tools/qontract_cli.py +2301 -673
  657. tools/saas_metrics_exporter/__init__.py +0 -0
  658. tools/saas_metrics_exporter/commit_distance/__init__.py +0 -0
  659. tools/saas_metrics_exporter/commit_distance/channel.py +63 -0
  660. tools/saas_metrics_exporter/commit_distance/commit_distance.py +103 -0
  661. tools/saas_metrics_exporter/commit_distance/metrics.py +19 -0
  662. tools/saas_metrics_exporter/main.py +99 -0
  663. tools/saas_promotion_state/__init__.py +0 -0
  664. tools/saas_promotion_state/saas_promotion_state.py +105 -0
  665. tools/sd_app_sre_alert_report.py +145 -0
  666. tools/template_validation.py +107 -0
  667. e2e_tests/cli.py +0 -83
  668. e2e_tests/create_namespace.py +0 -43
  669. e2e_tests/dedicated_admin_rolebindings.py +0 -44
  670. e2e_tests/dedicated_admin_test_base.py +0 -39
  671. e2e_tests/default_network_policies.py +0 -47
  672. e2e_tests/default_project_labels.py +0 -52
  673. e2e_tests/network_policy_test_base.py +0 -17
  674. e2e_tests/test_base.py +0 -56
  675. qontract_reconcile-0.10.0.dist-info/LICENSE +0 -201
  676. qontract_reconcile-0.10.0.dist-info/METADATA +0 -63
  677. qontract_reconcile-0.10.0.dist-info/RECORD +0 -586
  678. qontract_reconcile-0.10.0.dist-info/top_level.txt +0 -4
  679. reconcile/ecr_mirror.py +0 -152
  680. reconcile/github_scanner.py +0 -74
  681. reconcile/gitlab_integrations.py +0 -63
  682. reconcile/gql_definitions/ocm_oidc_idp/clusters.py +0 -195
  683. reconcile/gql_definitions/ocp_release_mirror/ocp_release_mirror.py +0 -287
  684. reconcile/integrations_validator.py +0 -18
  685. reconcile/jenkins_plugins.py +0 -129
  686. reconcile/kafka_clusters.py +0 -208
  687. reconcile/ocm_cluster_admin.py +0 -42
  688. reconcile/ocm_oidc_idp.py +0 -198
  689. reconcile/ocp_release_mirror.py +0 -373
  690. reconcile/prometheus_rules_tester_old.py +0 -436
  691. reconcile/saas_auto_promotions_manager/merge_request_manager/merge_request_manager.py +0 -279
  692. reconcile/saas_auto_promotions_manager/utils/vcs.py +0 -141
  693. reconcile/sentry_config.py +0 -613
  694. reconcile/sentry_helper.py +0 -69
  695. reconcile/test/conftest.py +0 -187
  696. reconcile/test/fixtures.py +0 -24
  697. reconcile/test/saas_auto_promotions_manager/conftest.py +0 -69
  698. reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/conftest.py +0 -110
  699. reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/data_keys.py +0 -10
  700. reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/test_housekeeping.py +0 -200
  701. reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/test_merge_request_manager.py +0 -151
  702. reconcile/test/saas_auto_promotions_manager/merge_request_manager/renderer/conftest.py +0 -63
  703. reconcile/test/saas_auto_promotions_manager/merge_request_manager/renderer/data_keys.py +0 -4
  704. reconcile/test/saas_auto_promotions_manager/merge_request_manager/renderer/test_content_multiple_namespaces.py +0 -46
  705. reconcile/test/saas_auto_promotions_manager/merge_request_manager/renderer/test_content_single_namespace.py +0 -94
  706. reconcile/test/saas_auto_promotions_manager/merge_request_manager/renderer/test_content_single_target.py +0 -44
  707. reconcile/test/saas_auto_promotions_manager/subscriber/conftest.py +0 -74
  708. reconcile/test/saas_auto_promotions_manager/subscriber/data_keys.py +0 -11
  709. reconcile/test/saas_auto_promotions_manager/subscriber/test_content_hash.py +0 -155
  710. reconcile/test/saas_auto_promotions_manager/subscriber/test_diff.py +0 -173
  711. reconcile/test/saas_auto_promotions_manager/subscriber/test_multiple_channels_config_hash.py +0 -226
  712. reconcile/test/saas_auto_promotions_manager/subscriber/test_multiple_channels_moving_ref.py +0 -224
  713. reconcile/test/saas_auto_promotions_manager/subscriber/test_single_channel_with_single_publisher.py +0 -350
  714. reconcile/test/saas_auto_promotions_manager/test_integration_test.py +0 -129
  715. reconcile/test/saas_auto_promotions_manager/utils/saas_files_inventory/test_multiple_publishers_for_single_channel.py +0 -70
  716. reconcile/test/saas_auto_promotions_manager/utils/saas_files_inventory/test_saas_files_use_target_config_hash.py +0 -63
  717. reconcile/test/saas_auto_promotions_manager/utils/saas_files_inventory/test_saas_files_with_auto_promote.py +0 -74
  718. reconcile/test/saas_auto_promotions_manager/utils/saas_files_inventory/test_saas_files_without_auto_promote.py +0 -65
  719. reconcile/test/test_aggregated_list.py +0 -237
  720. reconcile/test/test_amtool.py +0 -37
  721. reconcile/test/test_auto_promoter.py +0 -295
  722. reconcile/test/test_aws_ami_share.py +0 -68
  723. reconcile/test/test_aws_iam_keys.py +0 -70
  724. reconcile/test/test_aws_iam_password_reset.py +0 -35
  725. reconcile/test/test_aws_support_cases_sos.py +0 -23
  726. reconcile/test/test_checkpoint.py +0 -178
  727. reconcile/test/test_cli.py +0 -41
  728. reconcile/test/test_closedbox_endpoint_monitoring.py +0 -207
  729. reconcile/test/test_gabi_authorized_users.py +0 -72
  730. reconcile/test/test_github_org.py +0 -154
  731. reconcile/test/test_github_repo_invites.py +0 -123
  732. reconcile/test/test_gitlab_housekeeping.py +0 -88
  733. reconcile/test/test_gitlab_labeler.py +0 -129
  734. reconcile/test/test_gitlab_members.py +0 -283
  735. reconcile/test/test_instrumented_wrappers.py +0 -18
  736. reconcile/test/test_integrations_manager.py +0 -995
  737. reconcile/test/test_jenkins_worker_fleets.py +0 -55
  738. reconcile/test/test_jump_host.py +0 -117
  739. reconcile/test/test_ldap_users.py +0 -123
  740. reconcile/test/test_make.py +0 -28
  741. reconcile/test/test_ocm_additional_routers.py +0 -134
  742. reconcile/test/test_ocm_addons_upgrade_scheduler_org.py +0 -149
  743. reconcile/test/test_ocm_clusters.py +0 -598
  744. reconcile/test/test_ocm_clusters_manifest_updates.py +0 -89
  745. reconcile/test/test_ocm_oidc_idp.py +0 -315
  746. reconcile/test/test_ocm_update_recommended_version.py +0 -145
  747. reconcile/test/test_ocm_upgrade_scheduler.py +0 -614
  748. reconcile/test/test_ocm_upgrade_scheduler_org_updater.py +0 -129
  749. reconcile/test/test_openshift_base.py +0 -730
  750. reconcile/test/test_openshift_namespace_labels.py +0 -345
  751. reconcile/test/test_openshift_namespaces.py +0 -256
  752. reconcile/test/test_openshift_resource.py +0 -415
  753. reconcile/test/test_openshift_resources_base.py +0 -440
  754. reconcile/test/test_openshift_saas_deploy_change_tester.py +0 -310
  755. reconcile/test/test_openshift_tekton_resources.py +0 -253
  756. reconcile/test/test_openshift_upgrade_watcher.py +0 -146
  757. reconcile/test/test_prometheus_rules_tester.py +0 -151
  758. reconcile/test/test_prometheus_rules_tester_old.py +0 -77
  759. reconcile/test/test_quay_membership.py +0 -86
  760. reconcile/test/test_quay_mirror.py +0 -109
  761. reconcile/test/test_quay_mirror_org.py +0 -70
  762. reconcile/test/test_quay_repos.py +0 -59
  763. reconcile/test/test_queries.py +0 -53
  764. reconcile/test/test_repo_owners.py +0 -47
  765. reconcile/test/test_requests_sender.py +0 -139
  766. reconcile/test/test_saasherder.py +0 -1074
  767. reconcile/test/test_saasherder_allowed_secret_paths.py +0 -127
  768. reconcile/test/test_secret_reader.py +0 -153
  769. reconcile/test/test_slack_base.py +0 -185
  770. reconcile/test/test_slack_usergroups.py +0 -744
  771. reconcile/test/test_sql_query.py +0 -19
  772. reconcile/test/test_terraform_cloudflare_dns.py +0 -117
  773. reconcile/test/test_terraform_cloudflare_resources.py +0 -106
  774. reconcile/test/test_terraform_cloudflare_users.py +0 -749
  775. reconcile/test/test_terraform_resources.py +0 -257
  776. reconcile/test/test_terraform_tgw_attachments.py +0 -631
  777. reconcile/test/test_terraform_users.py +0 -57
  778. reconcile/test/test_terraform_vpc_peerings.py +0 -499
  779. reconcile/test/test_terraform_vpc_peerings_build_desired_state.py +0 -1061
  780. reconcile/test/test_unleash.py +0 -138
  781. reconcile/test/test_utils_aws_api.py +0 -240
  782. reconcile/test/test_utils_aws_helper.py +0 -80
  783. reconcile/test/test_utils_cluster_version_data.py +0 -177
  784. reconcile/test/test_utils_data_structures.py +0 -13
  785. reconcile/test/test_utils_disabled_integrations.py +0 -86
  786. reconcile/test/test_utils_expiration.py +0 -109
  787. reconcile/test/test_utils_external_resource_spec.py +0 -383
  788. reconcile/test/test_utils_external_resources.py +0 -247
  789. reconcile/test/test_utils_github_api.py +0 -73
  790. reconcile/test/test_utils_gitlab_api.py +0 -20
  791. reconcile/test/test_utils_gpg.py +0 -69
  792. reconcile/test/test_utils_gql.py +0 -81
  793. reconcile/test/test_utils_helm.py +0 -306
  794. reconcile/test/test_utils_helpers.py +0 -55
  795. reconcile/test/test_utils_imap_client.py +0 -65
  796. reconcile/test/test_utils_jjb_client.py +0 -52
  797. reconcile/test/test_utils_jsonpath.py +0 -286
  798. reconcile/test/test_utils_ldap_client.py +0 -51
  799. reconcile/test/test_utils_mr.py +0 -226
  800. reconcile/test/test_utils_mr_clusters_updates.py +0 -77
  801. reconcile/test/test_utils_oc.py +0 -984
  802. reconcile/test/test_utils_ocm.py +0 -110
  803. reconcile/test/test_utils_pagerduty_api.py +0 -251
  804. reconcile/test/test_utils_parse_dhms_duration.py +0 -34
  805. reconcile/test/test_utils_password_validator.py +0 -155
  806. reconcile/test/test_utils_quay_api.py +0 -86
  807. reconcile/test/test_utils_semver_helper.py +0 -19
  808. reconcile/test/test_utils_sharding.py +0 -56
  809. reconcile/test/test_utils_slack_api.py +0 -439
  810. reconcile/test/test_utils_smtp_client.py +0 -73
  811. reconcile/test/test_utils_state.py +0 -256
  812. reconcile/test/test_utils_terraform.py +0 -13
  813. reconcile/test/test_utils_terraform_client.py +0 -585
  814. reconcile/test/test_utils_terraform_config_client.py +0 -219
  815. reconcile/test/test_utils_terrascript_aws_client.py +0 -277
  816. reconcile/test/test_utils_terrascript_cloudflare_client.py +0 -597
  817. reconcile/test/test_utils_terrascript_cloudflare_resources.py +0 -26
  818. reconcile/test/test_vault_replication.py +0 -515
  819. reconcile/test/test_vault_utils.py +0 -47
  820. reconcile/test/test_version_bump.py +0 -18
  821. reconcile/test/test_vpc_peerings_validator.py +0 -103
  822. reconcile/test/test_wrong_region.py +0 -78
  823. reconcile/typed_queries/glitchtip_settings.py +0 -18
  824. reconcile/typed_queries/ocp_release_mirror.py +0 -11
  825. reconcile/unleash_watcher.py +0 -120
  826. reconcile/utils/git_secrets.py +0 -63
  827. reconcile/utils/mr/auto_promoter.py +0 -218
  828. reconcile/utils/sentry_client.py +0 -383
  829. release/test_version.py +0 -50
  830. release/version.py +0 -100
  831. tools/test/test_qontract_cli.py +0 -60
  832. tools/test/test_sre_checkpoints.py +0 -79
  833. /e2e_tests/__init__.py → /reconcile/aus/upgrades.py +0 -0
  834. /reconcile/{gql_definitions/ocp_release_mirror → aws_account_manager}/__init__.py +0 -0
  835. /reconcile/{test → aws_ami_cleanup}/__init__.py +0 -0
  836. /reconcile/{test/saas_auto_promotions_manager → aws_cloudwatch_log_retention}/__init__.py +0 -0
  837. /reconcile/{test/saas_auto_promotions_manager/merge_request_manager → aws_saml_idp}/__init__.py +0 -0
  838. /reconcile/{test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager → aws_saml_roles}/__init__.py +0 -0
  839. /reconcile/{test/saas_auto_promotions_manager/merge_request_manager/renderer → aws_version_sync}/__init__.py +0 -0
  840. /reconcile/{test/saas_auto_promotions_manager/subscriber → aws_version_sync/merge_request_manager}/__init__.py +0 -0
  841. /reconcile/{test/saas_auto_promotions_manager/utils → cluster_auth_rhidp}/__init__.py +0 -0
  842. /reconcile/{test/saas_auto_promotions_manager/utils/saas_files_inventory → dynatrace_token_provider}/__init__.py +0 -0
  843. {release → reconcile/endpoints_discovery}/__init__.py +0 -0
  844. {tools/test → reconcile/external_resources}/__init__.py +0 -0
@@ -1,6 +1,5 @@
1
1
  import base64
2
2
  import enum
3
- import imghdr
4
3
  import json
5
4
  import logging
6
5
  import os
@@ -8,12 +7,9 @@ import random
8
7
  import re
9
8
  import string
10
9
  import tempfile
11
- from collections.abc import (
12
- Iterable,
13
- Mapping,
14
- MutableMapping,
15
- )
16
- from dataclasses import dataclass
10
+ from collections import Counter
11
+ from collections.abc import Iterable, Mapping, MutableMapping
12
+ from dataclasses import dataclass, field
17
13
  from ipaddress import (
18
14
  ip_address,
19
15
  ip_network,
@@ -22,11 +18,11 @@ from json import JSONDecodeError
22
18
  from threading import Lock
23
19
  from typing import (
24
20
  Any,
25
- Optional,
26
21
  cast,
27
22
  )
28
23
 
29
24
  import anymarkup
25
+ import filetype
30
26
  import requests
31
27
  from botocore.errorfactory import ClientError
32
28
  from github import Github
@@ -35,7 +31,9 @@ from sretoolbox.utils import threaded
35
31
  # temporary to create aws_ecrpublic_repository
36
32
  from terrascript import (
37
33
  Backend,
34
+ Block,
38
35
  Data,
36
+ Module,
39
37
  Output,
40
38
  Provider,
41
39
  Resource,
@@ -90,11 +88,11 @@ from terrascript.resource import (
90
88
  aws_iam_role,
91
89
  aws_iam_role_policy,
92
90
  aws_iam_role_policy_attachment,
91
+ aws_iam_saml_provider,
93
92
  aws_iam_service_linked_role,
94
93
  aws_iam_user,
95
94
  aws_iam_user_group_membership,
96
95
  aws_iam_user_login_profile,
97
- aws_iam_user_policy,
98
96
  aws_iam_user_policy_attachment,
99
97
  aws_kinesis_stream,
100
98
  aws_kms_alias,
@@ -108,6 +106,8 @@ from terrascript.resource import (
108
106
  aws_lb_listener_rule,
109
107
  aws_lb_target_group,
110
108
  aws_lb_target_group_attachment,
109
+ aws_msk_cluster,
110
+ aws_msk_configuration,
111
111
  aws_ram_principal_association,
112
112
  aws_ram_resource_association,
113
113
  aws_ram_resource_share,
@@ -138,17 +138,27 @@ from terrascript.resource import (
138
138
  random_id,
139
139
  )
140
140
 
141
- import reconcile.openshift_resources_base as orb
142
141
  import reconcile.utils.aws_helper as awsh
143
142
  from reconcile import queries
143
+ from reconcile.cli import TERRAFORM_VERSION
144
144
  from reconcile.github_org import get_default_config
145
+ from reconcile.gql_definitions.fragments.aws_vpc_request import (
146
+ VPCRequest,
147
+ )
148
+ from reconcile.gql_definitions.terraform_resources.terraform_resources_namespaces import (
149
+ NamespaceTerraformResourceLifecycleV1,
150
+ )
145
151
  from reconcile.utils import gql
146
152
  from reconcile.utils.aws_api import (
147
153
  AmiTag,
148
154
  AWSApi,
149
155
  )
156
+ from reconcile.utils.cloud_resource_best_practice.aws_rds import (
157
+ verify_rds_best_practices,
158
+ )
150
159
  from reconcile.utils.disabled_integrations import integration_is_enabled
151
160
  from reconcile.utils.elasticsearch_exceptions import (
161
+ ElasticSearchResourceColdStorageError,
152
162
  ElasticSearchResourceMissingSubnetIdError,
153
163
  ElasticSearchResourceNameInvalidError,
154
164
  ElasticSearchResourceZoneAwareSubnetInvalidError,
@@ -168,12 +178,13 @@ from reconcile.utils.external_resources import (
168
178
  from reconcile.utils.git import is_file_in_git_repo
169
179
  from reconcile.utils.gitlab_api import GitLabApi
170
180
  from reconcile.utils.jenkins_api import JenkinsApi
181
+ from reconcile.utils.jinja2.utils import process_extracurlyjinja2_template
171
182
  from reconcile.utils.ocm import OCMMap
172
183
  from reconcile.utils.password_validator import (
173
184
  PasswordPolicy,
174
185
  PasswordValidator,
175
186
  )
176
- from reconcile.utils.secret_reader import SecretReader
187
+ from reconcile.utils.secret_reader import SecretReader, SecretReaderBase
177
188
  from reconcile.utils.terraform import safe_resource_id
178
189
 
179
190
  GH_BASE_URL = os.environ.get("GITHUB_API", "https://api.github.com")
@@ -217,7 +228,9 @@ VARIABLE_KEYS = [
217
228
  "image",
218
229
  "assume_role",
219
230
  "inline_policy",
231
+ "role_policy",
220
232
  "assume_condition",
233
+ "assume_action",
221
234
  "api_proxy_uri",
222
235
  "cognito_callback_bucket_name",
223
236
  "openshift_ingress_load_balancer_arn",
@@ -232,6 +245,7 @@ VARIABLE_KEYS = [
232
245
  "subscriptions",
233
246
  "records",
234
247
  "extra_tags",
248
+ "lifecycle",
235
249
  ]
236
250
 
237
251
  EMAIL_REGEX = re.compile(r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$")
@@ -250,6 +264,28 @@ SUPPORTED_ALB_LISTENER_RULE_CONDITION_TYPE_MAPPING = {
250
264
  "source-ip": "source_ip",
251
265
  }
252
266
 
267
+ DEFAULT_TAGS = {
268
+ "tags": {
269
+ "app": "app-sre-infra",
270
+ },
271
+ }
272
+
273
+
274
+ class OutputResourceNameNotUniqueException(Exception):
275
+ def __init__(self, namespace, duplicates):
276
+ self.namespace, self.duplicates = namespace, duplicates
277
+ super().__init__(
278
+ str.format(
279
+ "Found duplicate values {} for 'output_resource_name' in namespace {}. Please ensure 'output_resource_name' is unique in a given namespace.",
280
+ self.duplicates,
281
+ self.namespace,
282
+ )
283
+ )
284
+
285
+
286
+ class RDSParameterGroupValidationError(Exception):
287
+ pass
288
+
253
289
 
254
290
  class StateInaccessibleException(Exception):
255
291
  pass
@@ -264,6 +300,13 @@ class UnapprovedSecretPathError(Exception):
264
300
  pass
265
301
 
266
302
 
303
+ class Moved(Block):
304
+ """Terraform `moved` block, available since Terraform 1.1"""
305
+
306
+ def __init__(self, fro: str, to: str):
307
+ super().__init__(fro=fro, to=to)
308
+
309
+
267
310
  class aws_ecrpublic_repository(Resource):
268
311
  pass
269
312
 
@@ -272,10 +315,18 @@ class aws_s3_bucket_acl(Resource):
272
315
  pass
273
316
 
274
317
 
318
+ class aws_s3_bucket_logging(Resource):
319
+ pass
320
+
321
+
275
322
  class aws_cloudfront_log_delivery_canonical_user_id(Data):
276
323
  pass
277
324
 
278
325
 
326
+ class cloudinit_config(Data):
327
+ pass
328
+
329
+
279
330
  # temporary until we upgrade to a terrascript release
280
331
  # that supports this provider
281
332
  # https://github.com/mjuenema/python-terrascript/pull/166
@@ -302,6 +353,18 @@ class aws_api_gateway_rest_api_policy(Resource):
302
353
  pass
303
354
 
304
355
 
356
+ # temporary until we upgrade to a terrascript release
357
+ # that supports this resource
358
+ class aws_msk_scram_secret_association(Resource):
359
+ pass
360
+
361
+
362
+ # temporary until we upgrade to a terrascript release
363
+ # that supports this resource
364
+ class aws_secretsmanager_secret_policy(Resource):
365
+ pass
366
+
367
+
305
368
  class ElasticSearchLogGroupType(enum.Enum):
306
369
  INDEX_SLOW_LOGS = "INDEX_SLOW_LOGS"
307
370
  SEARCH_SLOW_LOGS = "SEARCH_SLOW_LOGS"
@@ -316,6 +379,22 @@ class ElasticSearchLogGroupInfo:
316
379
  log_group_identifier: str
317
380
 
318
381
 
382
+ @dataclass
383
+ class Exclusion:
384
+ all: bool = False
385
+ provisioners: set[str] = field(default_factory=set)
386
+
387
+
388
+ class ProviderExcludedError(Exception):
389
+ def __init__(self, spec: ExternalResourceSpec) -> None:
390
+ super().__init__(
391
+ self,
392
+ "The provider is not managed by terraform_resources in this provisioner. "
393
+ "Set the `managed_by_erv2: true` attribute in the external resource spec to fix it."
394
+ f"Provisioner: {spec.provisioner['name']}, Provider: {spec.provider}, Identifier: {spec.resource['identifier']}",
395
+ )
396
+
397
+
319
398
  class TerrascriptClient: # pylint: disable=too-many-public-methods
320
399
  """
321
400
  At a high-level, this class is responsible for generating Terraform configuration in
@@ -337,17 +416,23 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
337
416
  integration_prefix: str,
338
417
  thread_pool_size: int,
339
418
  accounts: Iterable[dict[str, Any]],
340
- settings: Optional[Mapping[str, Any]] = None,
341
- prefetch_resources_by_schemas: Optional[list[str]] = None,
419
+ settings: Mapping[str, Any] | None = None,
420
+ prefetch_resources_by_schemas: list[str] | None = None,
421
+ secret_reader: SecretReaderBase | None = None,
342
422
  ) -> None:
343
423
  self.integration = integration
344
424
  self.integration_prefix = integration_prefix
345
- self.settings = settings
346
425
  self.thread_pool_size = thread_pool_size
347
426
  filtered_accounts = self.filter_disabled_accounts(accounts)
348
- self.secret_reader = SecretReader(settings=settings)
427
+ if secret_reader:
428
+ self.secret_reader = secret_reader
429
+ else:
430
+ self.secret_reader = SecretReader(settings=settings)
431
+ self.configs: dict[str, dict] = {}
349
432
  self.populate_configs(filtered_accounts)
350
- self.versions = {a["name"]: a["providerVersion"] for a in filtered_accounts}
433
+ self.versions: dict[str, str] = {
434
+ a["name"]: a["providerVersion"] for a in filtered_accounts
435
+ }
351
436
  tss = {}
352
437
  locks = {}
353
438
  self.supported_regions = {}
@@ -361,32 +446,47 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
361
446
  ts += provider.aws(
362
447
  access_key=config["aws_access_key_id"],
363
448
  secret_key=config["aws_secret_access_key"],
364
- version=self.versions.get(name),
365
449
  region=region,
366
450
  alias=region,
451
+ skip_region_validation=True,
452
+ default_tags=DEFAULT_TAGS,
367
453
  )
368
454
 
369
455
  # Add default region, which will be in resourcesDefaultRegion
370
456
  ts += provider.aws(
371
457
  access_key=config["aws_access_key_id"],
372
458
  secret_key=config["aws_secret_access_key"],
373
- version=self.versions.get(name),
374
459
  region=config["resourcesDefaultRegion"],
460
+ skip_region_validation=True,
461
+ default_tags=DEFAULT_TAGS,
375
462
  )
376
463
 
377
- # the time provider can be removed if all AWS accounts
378
- # upgrade to a provider version with this bug fix
379
- # https://github.com/hashicorp/terraform-provider-aws/pull/20926
380
- ts += time(version="0.9.1")
381
-
382
- ts += provider.random(version="3.4.3")
383
-
384
- ts += provider.template(version="2.2.0")
385
-
386
464
  ts += Terraform(
387
465
  backend=TerrascriptClient.state_bucket_for_account(
388
466
  self.integration, name, config
389
- )
467
+ ),
468
+ required_providers={
469
+ "aws": {
470
+ "source": "hashicorp/aws",
471
+ "version": self.versions.get(name),
472
+ },
473
+ # the time provider can be removed if all AWS accounts
474
+ # upgrade to a provider version with this bug fix
475
+ # https://github.com/hashicorp/terraform-provider-aws/pull/20926
476
+ "time": {
477
+ "source": "hashicorp/time",
478
+ "version": "0.9.1",
479
+ },
480
+ "random": {
481
+ "source": "hashicorp/random",
482
+ "version": "3.4.3",
483
+ },
484
+ "cloudinit": {
485
+ "source": "hashicorp/cloudinit",
486
+ "version": "2.3.3",
487
+ },
488
+ },
489
+ required_version=TERRAFORM_VERSION[0],
390
490
  )
391
491
  tss[name] = ts
392
492
  locks[name] = Lock()
@@ -402,8 +502,9 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
402
502
 
403
503
  self.accounts = {a["name"]: a for a in filtered_accounts}
404
504
  self.uids = {a["name"]: a["uid"] for a in filtered_accounts}
505
+ # default_regions info is needed in populate_tf_resource_rds, even in disabled accounts
405
506
  self.default_regions = {
406
- a["name"]: a["resourcesDefaultRegion"] for a in filtered_accounts
507
+ a["name"]: a["resourcesDefaultRegion"] for a in accounts
407
508
  }
408
509
  self.partitions = {
409
510
  a["name"]: a.get("partition") or "aws" for a in filtered_accounts
@@ -414,9 +515,9 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
414
515
  self.rosa_authenticator_pre_signup_zip_lock = Lock()
415
516
  self.lambda_zip: dict[str, str] = {}
416
517
  self.lambda_lock = Lock()
417
- self.github: Optional[Github] = None
518
+ self.github: Github | None = None
418
519
  self.github_lock = Lock()
419
- self.gitlab: Optional[GitLabApi] = None
520
+ self.gitlab: GitLabApi | None = None
420
521
  self.gitlab_lock = Lock()
421
522
  self.jenkins_map: dict[str, JenkinsApi] = {}
422
523
  self.jenkins_lock = Lock()
@@ -445,7 +546,7 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
445
546
 
446
547
  # defaults from account
447
548
  bucket_backend_value = config.get("bucket")
448
- key_backend_value = config.get("{}_key".format(integration))
549
+ key_backend_value = config.get(f"{integration}_key")
449
550
  region_backend_value = config.get("region")
450
551
  terraform_state = config["terraformState"]
451
552
  if terraform_state:
@@ -567,7 +668,7 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
567
668
  with self.gitlab_lock:
568
669
  if not self.gitlab:
569
670
  instance = queries.get_gitlab_instance()
570
- self.gitlab = GitLabApi(instance, settings=self.settings)
671
+ self.gitlab = GitLabApi(instance, secret_reader=self.secret_reader)
571
672
  return self.gitlab
572
673
 
573
674
  def init_jenkins(self, instance: dict) -> JenkinsApi:
@@ -576,10 +677,10 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
576
677
  with self.jenkins_lock:
577
678
  # this may have already happened, so we check again
578
679
  if not self.jenkins_map.get(instance_name):
579
- self.jenkins_map[
580
- instance_name
581
- ] = JenkinsApi.init_jenkins_from_secret(
582
- SecretReader(self.settings), instance["token"]
680
+ self.jenkins_map[instance_name] = (
681
+ JenkinsApi.init_jenkins_from_secret(
682
+ self.secret_reader, instance["token"]
683
+ )
583
684
  )
584
685
  return self.jenkins_map[instance_name]
585
686
 
@@ -600,7 +701,6 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
600
701
  self.thread_pool_size,
601
702
  secret_reader=self.secret_reader,
602
703
  )
603
- self.configs: dict[str, dict] = {}
604
704
  for account_name, config in results:
605
705
  account = awsh.get_account(accounts, account_name)
606
706
  config["supportedDeploymentRegions"] = account["supportedDeploymentRegions"]
@@ -635,6 +735,9 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
635
735
  group_name = aws_group["name"]
636
736
  group_policies = aws_group["policies"]
637
737
  account = aws_group["account"]
738
+ if account["sso"] is True:
739
+ # AWS accounts with SSO enabled do not need IAM groups
740
+ continue
638
741
  account_name = account["name"]
639
742
  if account_name not in groups:
640
743
  groups[account_name] = {}
@@ -691,7 +794,7 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
691
794
  self,
692
795
  roles,
693
796
  skip_reencrypt_accounts: list[str],
694
- appsre_pgp_key: Optional[str],
797
+ appsre_pgp_key: str | None,
695
798
  ):
696
799
  error = False
697
800
  for role in roles:
@@ -705,6 +808,9 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
705
808
  for aws_group in aws_groups:
706
809
  group_name = aws_group["name"]
707
810
  account = aws_group["account"]
811
+ if account["sso"] is True:
812
+ # AWS accounts with SSO enabled do not need IAM users
813
+ continue
708
814
  account_name = account["name"]
709
815
  account_console_url = account["consoleUrl"]
710
816
 
@@ -716,11 +822,9 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
716
822
 
717
823
  # we want to include the console url in the outputs
718
824
  # to be used later to generate the email invitations
719
- output_name_0_13 = "{}_console-urls__{}".format(
720
- self.integration_prefix, account_name
721
- )
825
+ output_name = f"{self.integration_prefix}_console-urls__{account_name}"
722
826
  output_value = account_console_url
723
- tf_output = Output(output_name_0_13, value=output_value)
827
+ tf_output = Output(output_name, value=output_value)
724
828
  self.add_resource(account_name, tf_output)
725
829
 
726
830
  for user in users:
@@ -777,25 +881,23 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
777
881
  # we want the outputs to be formed into a mail invitation
778
882
  # for each new user. we form an output of the form
779
883
  # 'qrtf.enc-passwords[user_name] = <encrypted password>
780
- output_name_0_13 = "{}_enc-passwords__{}".format(
781
- self.integration_prefix, user_name
884
+ output_name = (
885
+ f"{self.integration_prefix}_enc-passwords__{user_name}"
782
886
  )
783
887
  output_value = (
784
888
  "${" + tf_iam_user_login_profile.encrypted_password + "}"
785
889
  )
786
- tf_output = Output(output_name_0_13, value=output_value)
890
+ tf_output = Output(output_name, value=output_value, sensitive=True)
787
891
  self.add_resource(account_name, tf_output)
788
892
 
789
893
  for user_policy in user_policies:
894
+ if user_policy["account"]["sso"] is True:
895
+ # AWS accounts with SSO enabled do not need user policies
896
+ continue
790
897
  policy_name = user_policy["name"]
791
898
  account_name = user_policy["account"]["name"]
792
- account_uid = user_policy["account"]["uid"]
793
899
  for user in users:
794
- # replace known keys with values
795
900
  user_name = self._get_aws_username(user)
796
- policy = user_policy["policy"]
797
- policy = policy.replace("${aws:username}", user_name)
798
- policy = policy.replace("${aws:accountid}", account_uid)
799
901
 
800
902
  # Ref: terraform aws_iam_policy
801
903
  tf_iam_user = self.get_tf_iam_user(user_name)
@@ -803,7 +905,7 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
803
905
  tf_aws_iam_policy = aws_iam_policy(
804
906
  identifier,
805
907
  name=identifier,
806
- policy=policy,
908
+ policy=user_policy["policy"],
807
909
  )
808
910
  self.add_resource(account_name, tf_aws_iam_policy)
809
911
  # Ref: terraform aws_iam_user_policy_attachment
@@ -811,9 +913,10 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
811
913
  identifier,
812
914
  user=user_name,
813
915
  policy_arn=f"${{{tf_aws_iam_policy.arn}}}",
814
- depends_on=self.get_dependencies(
815
- [tf_iam_user, tf_aws_iam_policy]
816
- ),
916
+ depends_on=self.get_dependencies([
917
+ tf_iam_user,
918
+ tf_aws_iam_policy,
919
+ ]),
817
920
  )
818
921
  self.add_resource(account_name, tf_iam_user_policy_attachment)
819
922
 
@@ -823,7 +926,7 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
823
926
  self,
824
927
  roles,
825
928
  skip_reencrypt_accounts: list[str],
826
- appsre_pgp_key: Optional[str] = None,
929
+ appsre_pgp_key: str | None = None,
827
930
  ):
828
931
  self.populate_iam_groups(roles)
829
932
  err = self.populate_iam_users(
@@ -834,27 +937,59 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
834
937
  return err
835
938
 
836
939
  @staticmethod
837
- def get_alias_name_from_assume_role(assume_role):
838
- uid = awsh.get_account_uid_from_arn(assume_role)
839
- return f"account-{uid}"
940
+ def get_provider_alias(account: Mapping[str, str]) -> str:
941
+ if not account.get("assume_role"):
942
+ return f"account-{account['name']}-{account['assume_region']}"
943
+ uid = awsh.get_account_uid_from_arn(account["assume_role"])
944
+ role_name = awsh.get_id_from_arn(account["assume_role"])
945
+ return f"account-{uid}-{role_name}"
840
946
 
841
- def populate_additional_providers(self, accounts):
947
+ @staticmethod
948
+ def get_resource_lifecycle(
949
+ common_values: dict[str, Any],
950
+ ) -> dict[str, Any] | None:
951
+ if lifecycle := common_values.get("lifecycle"):
952
+ lifecycle = NamespaceTerraformResourceLifecycleV1(**lifecycle)
953
+ if lifecycle.create_before_destroy is None:
954
+ lifecycle.create_before_destroy = False
955
+ if lifecycle.prevent_destroy is None:
956
+ lifecycle.prevent_destroy = False
957
+ if lifecycle.ignore_changes is None:
958
+ lifecycle.ignore_changes = []
959
+ if "all" in lifecycle.ignore_changes:
960
+ lifecycle.ignore_changes = "all"
961
+ return lifecycle.dict(by_alias=True)
962
+ return None
963
+
964
+ def populate_additional_providers(self, infra_account_name: str, accounts):
842
965
  for account in accounts:
843
966
  account_name = account["name"]
844
- assume_role = account["assume_role"]
845
- alias = self.get_alias_name_from_assume_role(assume_role)
846
- ts = self.tss[account_name]
967
+ assume_role = account.get("assume_role")
968
+ region = account["assume_region"]
969
+ alias = self.get_provider_alias(account)
970
+ ts = self.tss[infra_account_name]
847
971
  config = self.configs[account_name]
848
972
  existing_provider_aliases = {p.get("alias") for p in ts["provider"]["aws"]}
849
973
  if alias not in existing_provider_aliases:
850
- ts += provider.aws(
851
- access_key=config["aws_access_key_id"],
852
- secret_key=config["aws_secret_access_key"],
853
- version=self.versions.get(account_name),
854
- region=account["assume_region"],
855
- alias=alias,
856
- assume_role={"role_arn": assume_role},
857
- )
974
+ if assume_role:
975
+ ts += provider.aws(
976
+ access_key=config["aws_access_key_id"],
977
+ secret_key=config["aws_secret_access_key"],
978
+ region=region,
979
+ alias=alias,
980
+ assume_role={"role_arn": assume_role},
981
+ skip_region_validation=True,
982
+ default_tags=DEFAULT_TAGS,
983
+ )
984
+ else:
985
+ ts += provider.aws(
986
+ access_key=config["aws_access_key_id"],
987
+ secret_key=config["aws_secret_access_key"],
988
+ region=region,
989
+ alias=alias,
990
+ skip_region_validation=True,
991
+ default_tags=DEFAULT_TAGS,
992
+ )
858
993
 
859
994
  def populate_route53(
860
995
  self, desired_state: Iterable[dict[str, Any]], default_ttl: int = 300
@@ -913,7 +1048,7 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
913
1048
  record["health_check_id"] = f"${{{healthcheck_resource.id}}}"
914
1049
 
915
1050
  # Get value from Vault if _records_from_vault was set
916
- records_from_vault: Optional[Iterable[dict[str, str]]] = record.pop(
1051
+ records_from_vault: Iterable[dict[str, str]] | None = record.pop(
917
1052
  "records_from_vault", None
918
1053
  )
919
1054
  if records_from_vault:
@@ -922,19 +1057,17 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
922
1057
  )
923
1058
  vault_values: list[str] = []
924
1059
  for rec in records_from_vault:
925
- if not rec["path"] in allowed_vault_secret_paths:
1060
+ if rec["path"] not in allowed_vault_secret_paths:
926
1061
  raise UnapprovedSecretPathError(
927
1062
  "'{}' is not in the list of approved Vault secret paths. Add this path to 'allowed_vault_secret_paths'.".format(
928
1063
  rec["path"]
929
1064
  )
930
1065
  )
931
- value = self.secret_reader.read(
932
- {
933
- "path": rec["path"],
934
- "field": rec["field"],
935
- "version": rec["version"],
936
- }
937
- )
1066
+ value = self.secret_reader.read({
1067
+ "path": rec["path"],
1068
+ "field": rec["field"],
1069
+ "version": rec["version"],
1070
+ })
938
1071
  # 'key' is only set when the secret data is in JSON format and a
939
1072
  # specific item from the object needs to be selected, otherwise the
940
1073
  # value is used as-is.
@@ -950,7 +1083,7 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
950
1083
  except KeyError:
951
1084
  msg = f"Key '{rec['key']}' was not found in the contents of secret '{rec['path']}'"
952
1085
  logging.error(msg)
953
- raise KeyError(msg)
1086
+ raise KeyError(msg) from None
954
1087
  vault_values.append(value)
955
1088
  record["records"] = vault_values
956
1089
 
@@ -963,14 +1096,19 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
963
1096
  if item["deleted"]:
964
1097
  continue
965
1098
 
1099
+ infra_account_name = item["infra_account_name"]
1100
+
966
1101
  connection_provider = item["connection_provider"]
967
1102
  connection_name = item["connection_name"]
968
1103
  requester = item["requester"]
969
1104
  accepter = item["accepter"]
970
1105
 
971
1106
  req_account = requester["account"]
972
- req_account_name = req_account["name"]
973
- req_alias = self.get_alias_name_from_assume_role(req_account["assume_role"])
1107
+ req_alias = self.get_provider_alias(req_account)
1108
+
1109
+ acc_account = accepter["account"]
1110
+ acc_account_name = acc_account["name"]
1111
+ acc_alias = self.get_provider_alias(acc_account)
974
1112
 
975
1113
  # Requester's side of the connection - the cluster's account
976
1114
  identifier = f"{requester['vpc_id']}-{accepter['vpc_id']}"
@@ -981,7 +1119,7 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
981
1119
  "vpc_id": requester["vpc_id"],
982
1120
  "peer_vpc_id": accepter["vpc_id"],
983
1121
  "peer_region": accepter["region"],
984
- "peer_owner_id": req_account["uid"],
1122
+ "peer_owner_id": acc_account["uid"],
985
1123
  "auto_accept": False,
986
1124
  "tags": {
987
1125
  "managed_by_integration": self.integration,
@@ -993,7 +1131,7 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
993
1131
  if req_peer_owner_id:
994
1132
  values["peer_owner_id"] = req_peer_owner_id
995
1133
  tf_resource = aws_vpc_peering_connection(identifier, **values)
996
- self.add_resource(req_account_name, tf_resource)
1134
+ self.add_resource(infra_account_name, tf_resource)
997
1135
 
998
1136
  # add routes to existing route tables
999
1137
  route_table_ids = requester.get("route_table_ids")
@@ -1009,11 +1147,38 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
1009
1147
  }
1010
1148
  route_identifier = f"{identifier}-{route_table_id}"
1011
1149
  tf_resource = aws_route(route_identifier, **values)
1012
- self.add_resource(req_account_name, tf_resource)
1013
-
1014
- acc_account = accepter["account"]
1015
- acc_account_name = acc_account["name"]
1016
- acc_alias = self.get_alias_name_from_assume_role(acc_account["assume_role"])
1150
+ self.add_resource(infra_account_name, tf_resource)
1151
+
1152
+ # add security group rules for private hosted controlplane API VPC endpoint service
1153
+ if requester.get("api_security_group_id"):
1154
+ hcp_api_ingress_rule = aws_security_group_rule(
1155
+ f"requester-api-access-from-peering-{connection_name}",
1156
+ provider="aws." + req_alias,
1157
+ type="ingress",
1158
+ security_group_id=requester.get("api_security_group_id"),
1159
+ cidr_blocks=[accepter["cidr_block"]],
1160
+ from_port=443,
1161
+ to_port=443,
1162
+ protocol="tcp",
1163
+ description=f"HCP API access from peering connection {connection_name}",
1164
+ )
1165
+ self.add_resource(infra_account_name, hcp_api_ingress_rule)
1166
+
1167
+ if accepter.get("api_security_group_id"):
1168
+ self.add_resource(
1169
+ infra_account_name,
1170
+ aws_security_group_rule(
1171
+ f"accepter-api-access-from-peering-{connection_name}",
1172
+ provider="aws." + acc_alias,
1173
+ type="ingress",
1174
+ security_group_id=accepter.get("api_security_group_id"),
1175
+ cidr_blocks=[requester["cidr_block"]],
1176
+ from_port=443,
1177
+ to_port=443,
1178
+ protocol="tcp",
1179
+ description=f"HCP API access from peering connection {connection_name}",
1180
+ ),
1181
+ )
1017
1182
 
1018
1183
  # Accepter's side of the connection.
1019
1184
  values = {
@@ -1027,13 +1192,13 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
1027
1192
  "Name": connection_name,
1028
1193
  },
1029
1194
  }
1030
- if connection_provider in ["account-vpc", "account-vpc-mesh"]:
1195
+ if connection_provider in {"account-vpc", "account-vpc-mesh"}:
1031
1196
  if self._multiregion_account(acc_account_name):
1032
1197
  values["provider"] = "aws." + accepter["region"]
1033
1198
  else:
1034
1199
  values["provider"] = "aws." + acc_alias
1035
1200
  tf_resource = aws_vpc_peering_connection_accepter(identifier, **values)
1036
- self.add_resource(acc_account_name, tf_resource)
1201
+ self.add_resource(infra_account_name, tf_resource)
1037
1202
 
1038
1203
  # add routes to existing route tables
1039
1204
  route_table_ids = accepter.get("route_table_ids")
@@ -1046,32 +1211,121 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
1046
1211
  + identifier
1047
1212
  + ".id}",
1048
1213
  }
1049
- if connection_provider in ["account-vpc", "account-vpc-mesh"]:
1214
+ if connection_provider in {"account-vpc", "account-vpc-mesh"}:
1050
1215
  if self._multiregion_account(acc_account_name):
1051
1216
  values["provider"] = "aws." + accepter["region"]
1052
1217
  else:
1053
1218
  values["provider"] = "aws." + acc_alias
1054
1219
  route_identifier = f"{identifier}-{route_table_id}"
1055
1220
  tf_resource = aws_route(route_identifier, **values)
1056
- self.add_resource(acc_account_name, tf_resource)
1221
+ self.add_resource(infra_account_name, tf_resource)
1222
+
1223
+ def populate_vpc_requests(
1224
+ self, vpc_requests: Iterable[VPCRequest], vpc_module_version: str
1225
+ ) -> None:
1226
+ for request in vpc_requests:
1227
+ # skiping deleted requests
1228
+ if request.delete:
1229
+ continue
1230
+ # The default values here come from infra repo's module configuration
1231
+ vpc_module_values = {
1232
+ "source": "terraform-aws-modules/vpc/aws",
1233
+ "version": vpc_module_version,
1234
+ "name": request.identifier,
1235
+ "cidr": request.cidr_block.network_address,
1236
+ "private_subnet_tags": {"kubernetes.io/role/internal-elb": "1"},
1237
+ "public_subnet_tags": {"kubernetes.io/role/elb": "1"},
1238
+ "create_database_subnet_group": False,
1239
+ "enable_dns_hostnames": True,
1240
+ "tags": {
1241
+ "managed_by_integration": self.integration,
1242
+ },
1243
+ }
1244
+
1245
+ if request.subnets and request.subnets.public:
1246
+ vpc_module_values["public_subnets"] = request.subnets.public
1247
+ if request.subnets and request.subnets.private:
1248
+ vpc_module_values["private_subnets"] = request.subnets.private
1249
+ if request.subnets and request.subnets.availability_zones:
1250
+ vpc_module_values["azs"] = request.subnets.availability_zones
1251
+
1252
+ # We only want to enable nat_gateway if we have public and private subnets
1253
+ if request.subnets and request.subnets.public and request.subnets.private:
1254
+ vpc_module_values["enable_nat_gateway"] = True
1255
+
1256
+ aws_account = request.account.name
1257
+ vpc_module = Module(request.identifier, **vpc_module_values)
1258
+ self.add_resource(aws_account, vpc_module)
1259
+
1260
+ # vpc_endpoint
1261
+ vpc_endpoint_values = {
1262
+ "source": "terraform-aws-modules/vpc/aws//modules/vpc-endpoints",
1263
+ "version": vpc_module_version,
1264
+ "vpc_id": f"${{{vpc_module.vpc_id}}}",
1265
+ "endpoints": {
1266
+ "s3": {
1267
+ "service": "s3",
1268
+ "serivce_type": "Gateway",
1269
+ "tags": {
1270
+ "managed_by_integration": self.integration,
1271
+ "Name": f"{request.identifier}--vpce-s3",
1272
+ "route_table_ids": vpc_module.vpc.private_route_table_ids,
1273
+ },
1274
+ }
1275
+ },
1276
+ }
1277
+
1278
+ vpc_endpoint = Module(
1279
+ f"{request.identifier}-endpoint", **vpc_endpoint_values
1280
+ )
1281
+ self.add_resource(aws_account, vpc_endpoint)
1282
+
1283
+ # The outputs for module are only working with this sintaxe
1284
+ vpc_id_output = Output(
1285
+ f"{request.identifier}-vpc_id", value=f"${{{vpc_module.vpc_id}}}"
1286
+ )
1287
+ self.add_resource(aws_account, vpc_id_output)
1288
+
1289
+ vpc_cidr_block_output = Output(
1290
+ f"{request.identifier}-vpc_cidr_block",
1291
+ value=f"${{{vpc_module.vpc_cidr_block}}}",
1292
+ )
1293
+ self.add_resource(aws_account, vpc_cidr_block_output)
1294
+
1295
+ if request.subnets and request.subnets.private:
1296
+ private_subnets_output = Output(
1297
+ f"{request.identifier}-private_subnets",
1298
+ value=f"${{module.{request.identifier}.private_subnets}}",
1299
+ )
1300
+ self.add_resource(aws_account, private_subnets_output)
1301
+
1302
+ if request.subnets and request.subnets.public:
1303
+ public_subnets_output = Output(
1304
+ f"{request.identifier}-public_subnets",
1305
+ value=f"${{module.{request.identifier}.public_subnets}}",
1306
+ )
1307
+ self.add_resource(aws_account, public_subnets_output)
1057
1308
 
1058
1309
  def populate_tgw_attachments(self, desired_state):
1059
1310
  for item in desired_state:
1060
- if item["deleted"]:
1311
+ if item.deleted:
1061
1312
  continue
1062
1313
 
1063
- connection_name = item["connection_name"]
1064
- requester = item["requester"]
1065
- accepter = item["accepter"]
1314
+ infra_account_name = item.infra_acount_name
1315
+
1316
+ connection_name = item.connection_name
1317
+ requester = item.requester
1318
+ accepter = item.accepter
1066
1319
 
1067
1320
  # Requester's side of the connection - the AWS account
1068
- req_account = requester["account"]
1069
- req_account_name = req_account["name"]
1321
+ req_account = requester.account
1322
+ req_account_name = req_account.name
1070
1323
  # Accepter's side of the connection - the cluster's account
1071
- acc_account = accepter["account"]
1072
- acc_account_name = acc_account["name"]
1073
- acc_alias = self.get_alias_name_from_assume_role(acc_account["assume_role"])
1074
- acc_uid = awsh.get_account_uid_from_arn(acc_account["assume_role"])
1324
+ acc_account = accepter.account
1325
+ acc_alias = self.get_provider_alias(acc_account.dict(by_alias=True))
1326
+ acc_uid = acc_account.uid
1327
+ if acc_account.assume_role:
1328
+ acc_uid = awsh.get_account_uid_from_arn(acc_account.assume_role)
1075
1329
 
1076
1330
  tags = {"managed_by_integration": self.integration, "Name": connection_name}
1077
1331
  # add resource share
@@ -1081,9 +1335,9 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
1081
1335
  "tags": tags,
1082
1336
  }
1083
1337
  if self._multiregion_account(req_account_name):
1084
- values["provider"] = "aws." + requester["region"]
1338
+ values["provider"] = "aws." + requester.region
1085
1339
  tf_resource_share = aws_ram_resource_share(connection_name, **values)
1086
- self.add_resource(req_account_name, tf_resource_share)
1340
+ self.add_resource(infra_account_name, tf_resource_share)
1087
1341
 
1088
1342
  # share with accepter aws account
1089
1343
  values = {
@@ -1091,11 +1345,11 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
1091
1345
  "resource_share_arn": "${" + tf_resource_share.arn + "}",
1092
1346
  }
1093
1347
  if self._multiregion_account(req_account_name):
1094
- values["provider"] = "aws." + requester["region"]
1348
+ values["provider"] = "aws." + requester.region
1095
1349
  tf_resource_association = aws_ram_principal_association(
1096
1350
  connection_name, **values
1097
1351
  )
1098
- self.add_resource(req_account_name, tf_resource_association)
1352
+ self.add_resource(infra_account_name, tf_resource_association)
1099
1353
 
1100
1354
  # accept resource share from accepter aws account
1101
1355
  values = {
@@ -1109,32 +1363,32 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
1109
1363
  tf_resource_share_accepter = aws_ram_resource_share_accepter(
1110
1364
  connection_name, **values
1111
1365
  )
1112
- self.add_resource(acc_account_name, tf_resource_share_accepter)
1366
+ self.add_resource(infra_account_name, tf_resource_share_accepter)
1113
1367
 
1114
1368
  # until now it was standard sharing
1115
1369
  # from this line onwards we will be adding content
1116
1370
  # specific for the TGW attachments integration
1117
1371
 
1118
1372
  # tgw share association
1119
- identifier = f"{requester['tgw_id']}-{accepter['vpc_id']}"
1373
+ identifier = f"{requester.tgw_id}-{accepter.vpc_id}"
1120
1374
  values = {
1121
- "resource_arn": requester["tgw_arn"],
1375
+ "resource_arn": requester.tgw_arn,
1122
1376
  "resource_share_arn": "${" + tf_resource_share.arn + "}",
1123
1377
  }
1124
1378
  if self._multiregion_account(req_account_name):
1125
- values["provider"] = "aws." + requester["region"]
1379
+ values["provider"] = "aws." + requester.region
1126
1380
  tf_resource_association = aws_ram_resource_association(identifier, **values)
1127
- self.add_resource(req_account_name, tf_resource_association)
1381
+ self.add_resource(infra_account_name, tf_resource_association)
1128
1382
 
1129
1383
  # now that the tgw is shared to the cluster's aws account
1130
1384
  # we can create a vpc attachment to the tgw
1131
- subnets_id_az = accepter["subnets_id_az"]
1385
+ subnets_id_az = accepter.subnets_id_az
1132
1386
  subnets = self.get_az_unique_subnet_ids(subnets_id_az)
1133
1387
  values = {
1134
1388
  "provider": "aws." + acc_alias,
1135
1389
  "subnet_ids": subnets,
1136
- "transit_gateway_id": requester["tgw_id"],
1137
- "vpc_id": accepter["vpc_id"],
1390
+ "transit_gateway_id": requester.tgw_id,
1391
+ "vpc_id": accepter.vpc_id,
1138
1392
  "depends_on": [
1139
1393
  "aws_ram_principal_association." + connection_name,
1140
1394
  "aws_ram_resource_association." + identifier,
@@ -1145,7 +1399,7 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
1145
1399
  identifier, **values
1146
1400
  )
1147
1401
  # we send the attachment from the cluster's aws account
1148
- self.add_resource(acc_account_name, tf_resource_attachment)
1402
+ self.add_resource(infra_account_name, tf_resource_attachment)
1149
1403
 
1150
1404
  # and accept the attachment in the non cluster's aws account
1151
1405
  values = {
@@ -1153,30 +1407,45 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
1153
1407
  "tags": tags,
1154
1408
  }
1155
1409
  if self._multiregion_account(req_account_name):
1156
- values["provider"] = "aws." + requester["region"]
1410
+ values["provider"] = "aws." + requester.region
1157
1411
  tf_resource_attachment_accepter = (
1158
1412
  aws_ec2_transit_gateway_vpc_attachment_accepter(identifier, **values)
1159
1413
  )
1160
- self.add_resource(req_account_name, tf_resource_attachment_accepter)
1414
+ self.add_resource(infra_account_name, tf_resource_attachment_accepter)
1161
1415
 
1162
1416
  # add routes to existing route tables
1163
- route_table_ids = accepter.get("route_table_ids")
1164
- req_cidr_block = requester.get("cidr_block")
1165
- if route_table_ids and req_cidr_block:
1417
+ route_table_ids = accepter.route_table_ids
1418
+ req_cidr_block = requester.cidr_block
1419
+ req_cidr_blocks = requester.cidr_blocks or []
1420
+ if req_cidr_block:
1421
+ req_cidr_blocks.append(req_cidr_block)
1422
+ if route_table_ids and (req_cidr_block or req_cidr_blocks):
1166
1423
  for route_table_id in route_table_ids:
1167
- values = {
1168
- "provider": "aws." + acc_alias,
1169
- "route_table_id": route_table_id,
1170
- "destination_cidr_block": req_cidr_block,
1171
- "transit_gateway_id": requester["tgw_id"],
1172
- }
1173
- route_identifier = f"{identifier}-{route_table_id}"
1174
- tf_resource = aws_route(route_identifier, **values)
1175
- self.add_resource(acc_account_name, tf_resource)
1424
+ for cidr_block in req_cidr_blocks:
1425
+ values = {
1426
+ "provider": "aws." + acc_alias,
1427
+ "route_table_id": route_table_id,
1428
+ "destination_cidr_block": cidr_block,
1429
+ "transit_gateway_id": requester.tgw_id,
1430
+ }
1431
+ # use the cidr block in the resource name to allow re-ordering
1432
+ cidr_id = cidr_block.replace(".", "-").replace("/", "_")
1433
+ route_identifier = (
1434
+ f"{identifier}-{route_table_id}-dest-{cidr_id}"
1435
+ )
1436
+ tf_resource = aws_route(route_identifier, **values)
1437
+ self.add_resource(infra_account_name, tf_resource)
1438
+ if req_cidr_block:
1439
+ req_cidr_id = req_cidr_block.replace(".", "-").replace("/", "_")
1440
+ moved = Moved(
1441
+ fro=f"aws_route.{identifier}-{route_table_id}",
1442
+ to=f"aws_route.{identifier}-{route_table_id}-dest-{req_cidr_id}",
1443
+ )
1444
+ self.add_moved(infra_account_name, moved)
1176
1445
 
1177
1446
  # add routes to peered transit gateways in the requester's
1178
1447
  # account to achieve global routing from all regions
1179
- requester_routes = requester.get("routes")
1448
+ requester_routes = requester.routes
1180
1449
  if requester_routes:
1181
1450
  for route in requester_routes:
1182
1451
  route_region = route["region"]
@@ -1197,11 +1466,25 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
1197
1466
  tf_resource = aws_ec2_transit_gateway_route(
1198
1467
  route_identifier, **values
1199
1468
  )
1200
- self.add_resource(req_account_name, tf_resource)
1469
+ self.add_resource(infra_account_name, tf_resource)
1470
+
1471
+ if accepter.api_security_group_id:
1472
+ hcp_api_ingress_rule = aws_security_group_rule(
1473
+ f"api-access-from-{requester.tgw_id}",
1474
+ provider="aws." + acc_alias,
1475
+ type="ingress",
1476
+ security_group_id=accepter.api_security_group_id,
1477
+ cidr_blocks=[requester.cidr_block],
1478
+ from_port=443,
1479
+ to_port=443,
1480
+ protocol="tcp",
1481
+ description=f"HCP API access from TGW attachment {requester.tgw_id}",
1482
+ )
1483
+ self.add_resource(infra_account_name, hcp_api_ingress_rule)
1201
1484
 
1202
1485
  # add rules to security groups of VPCs which are attached
1203
1486
  # to the transit gateway to allow traffic through the routes
1204
- requester_rules = requester.get("rules")
1487
+ requester_rules = requester.rules
1205
1488
  if requester_rules:
1206
1489
  for rule in requester_rules:
1207
1490
  rule_region = rule["region"]
@@ -1223,25 +1506,25 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
1223
1506
  values["provider"] = "aws." + rule_region
1224
1507
  rule_identifier = f"{identifier}-{rule['vpc_id']}"
1225
1508
  tf_resource = aws_security_group_rule(rule_identifier, **values)
1226
- self.add_resource(req_account_name, tf_resource)
1509
+ self.add_resource(infra_account_name, tf_resource)
1227
1510
 
1228
- for zone in requester.get("hostedzones") or []:
1511
+ for zone in requester.hostedzones or []:
1229
1512
  id = f"{identifier}-{zone}"
1230
1513
  values = {
1231
- "vpc_id": accepter["vpc_id"],
1232
- "vpc_region": accepter["region"],
1514
+ "vpc_id": accepter.vpc_id,
1515
+ "vpc_region": accepter.region,
1233
1516
  "zone_id": zone,
1234
1517
  }
1235
1518
  authorization = aws_route53_vpc_association_authorization(id, **values)
1236
- self.add_resource(req_account_name, authorization)
1519
+ self.add_resource(infra_account_name, authorization)
1237
1520
  values = {
1238
1521
  "provider": "aws." + acc_alias,
1239
1522
  "vpc_id": f"${{aws_route53_vpc_association_authorization.{id}.vpc_id}}",
1240
- "vpc_region": accepter["region"],
1523
+ "vpc_region": accepter.region,
1241
1524
  "zone_id": f"${{aws_route53_vpc_association_authorization.{id}.zone_id}}",
1242
1525
  }
1243
1526
  association = aws_route53_zone_association(id, **values)
1244
- self.add_resource(acc_account_name, association)
1527
+ self.add_resource(infra_account_name, association)
1245
1528
 
1246
1529
  @staticmethod
1247
1530
  def get_az_unique_subnet_ids(subnets_id_az):
@@ -1257,7 +1540,7 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
1257
1540
 
1258
1541
  return results
1259
1542
 
1260
- def populate_resources(self, ocm_map: Optional[OCMMap] = None) -> None:
1543
+ def populate_resources(self, ocm_map: OCMMap | None = None) -> None:
1261
1544
  """
1262
1545
  Populates the terraform configuration from resource specs.
1263
1546
  :param ocm_map:
@@ -1266,10 +1549,49 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
1266
1549
  for spec in specs:
1267
1550
  self.populate_tf_resources(spec, ocm_map=ocm_map)
1268
1551
 
1552
+ def _is_provisioner_excluded(
1553
+ self,
1554
+ spec: ExternalResourceSpec,
1555
+ provider_exclusions: Mapping[str, Exclusion],
1556
+ ) -> bool:
1557
+ e = provider_exclusions.get(spec.provider)
1558
+ if not e:
1559
+ return False
1560
+ return e.all or spec.provisioner_name in e.provisioners
1561
+
1562
+ def _filter_specs_managed_by_erv2(
1563
+ self,
1564
+ specs: Iterable[ExternalResourceSpec],
1565
+ provider_exclusions: Mapping[str, Exclusion],
1566
+ ) -> list[ExternalResourceSpec]:
1567
+ filtered_specs = [
1568
+ spec for spec in specs if not spec.resource.get("managed_by_erv2")
1569
+ ]
1570
+
1571
+ for spec in filtered_specs:
1572
+ if self._is_provisioner_excluded(spec, provider_exclusions):
1573
+ raise ProviderExcludedError(spec)
1574
+
1575
+ return filtered_specs
1576
+
1577
+ def _get_provider_exclusions_query_dict(
1578
+ self, provider_exclusions: Iterable[Mapping[str, Any]]
1579
+ ) -> dict[str, Exclusion]:
1580
+ return {
1581
+ item["provider"]: Exclusion(
1582
+ all=item.get("excludeAllProvisioners") or False,
1583
+ provisioners={
1584
+ p["name"] for p in (item.get("excludeProvisioners") or [])
1585
+ },
1586
+ )
1587
+ for item in provider_exclusions
1588
+ }
1589
+
1269
1590
  def init_populate_specs(
1270
1591
  self,
1271
1592
  namespaces: Iterable[Mapping[str, Any]],
1272
- account_names: Optional[Iterable[str]],
1593
+ account_names: Iterable[str] | None,
1594
+ provider_exclusions: Iterable[Mapping[str, Any]] | None = None,
1273
1595
  ) -> None:
1274
1596
  """
1275
1597
  Initiates resource specs from the definitions in app-interface
@@ -1280,16 +1602,37 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
1280
1602
  self.account_resource_specs: dict[str, list[ExternalResourceSpec]] = {}
1281
1603
  self.resource_spec_inventory: ExternalResourceSpecInventory = {}
1282
1604
 
1605
+ # Ensure provider exclusions are fetched
1606
+ if provider_exclusions is None:
1607
+ provider_exclusions = (
1608
+ queries.get_tf_resources_provider_exclusions_by_provisioner() or []
1609
+ )
1610
+
1611
+ provider_exclusions_query_dict = self._get_provider_exclusions_query_dict(
1612
+ provider_exclusions
1613
+ )
1614
+
1283
1615
  for namespace_info in namespaces:
1284
- specs = get_external_resource_specs(
1285
- namespace_info, provision_provider=PROVIDER_AWS
1616
+ all_specs = get_external_resource_specs(
1617
+ namespace_info,
1618
+ provision_provider=PROVIDER_AWS,
1619
+ )
1620
+ specs = self._filter_specs_managed_by_erv2(
1621
+ all_specs, provider_exclusions_query_dict
1286
1622
  )
1623
+ name_counter = Counter(spec.output_resource_name for spec in specs)
1624
+ duplicates = [name for name, count in name_counter.items() if count > 1]
1625
+ if duplicates:
1626
+ raise OutputResourceNameNotUniqueException(
1627
+ namespace_info.get("name"), duplicates
1628
+ )
1287
1629
  for spec in specs:
1288
1630
  if account_names and spec.provisioner_name not in account_names:
1289
1631
  continue
1290
1632
  self.account_resource_specs.setdefault(
1291
1633
  spec.provisioner_name, []
1292
1634
  ).append(spec)
1635
+
1293
1636
  self.resource_spec_inventory[spec.id_object()] = spec
1294
1637
 
1295
1638
  def populate_tf_resources(self, spec, ocm_map=None):
@@ -1346,6 +1689,8 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
1346
1689
  self.populate_tf_resource_rosa_authenticator(spec)
1347
1690
  elif provider == "rosa-authenticator-vpce":
1348
1691
  self.populate_tf_resource_rosa_authenticator_vpce(spec)
1692
+ elif provider == "msk":
1693
+ self.populate_tf_resource_msk(spec)
1349
1694
  else:
1350
1695
  raise UnknownProviderError(provider)
1351
1696
 
@@ -1360,12 +1705,13 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
1360
1705
 
1361
1706
  # we want to allow an empty name, so we
1362
1707
  # only validate names which are not empty
1363
- if values.get("name") and not self.validate_db_name(values["name"]):
1708
+ db_name = self._get_db_name_from_values(values)
1709
+ if db_name and not self.validate_db_name(db_name):
1364
1710
  raise FetchResourceError(
1365
1711
  f"[{account}] RDS name must contain 1 to 63 letters, "
1366
1712
  + "numbers, or underscores. RDS name must begin with a "
1367
1713
  + "letter. Subsequent characters can be letters, "
1368
- + f"underscores, or digits (0-9): {values['name']}"
1714
+ + f"underscores, or digits (0-9): {db_name}"
1369
1715
  )
1370
1716
 
1371
1717
  if not values.get("apply_immediately"):
@@ -1393,6 +1739,16 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
1393
1739
  elif provider != provider_region:
1394
1740
  raise ValueError("region does not match availability zone")
1395
1741
 
1742
+ def validate_parameter_group(parameter_group: aws_db_parameter_group) -> None:
1743
+ parameter_group_name = parameter_group.get("name")
1744
+ for parameter in parameter_group.get("parameter", []):
1745
+ if parameter.get("name") == "rds.logical_replication":
1746
+ apply_method = parameter.get("apply_method")
1747
+ if apply_method != "pending-reboot":
1748
+ raise RDSParameterGroupValidationError(
1749
+ f"{parameter_group_name=} rds.logical_replication {apply_method=} must be set to 'pending-reboot'"
1750
+ )
1751
+
1396
1752
  def populate_parameter_group(name: str) -> aws_db_parameter_group:
1397
1753
  pg_values = self.get_values(name)
1398
1754
  # Parameter group name is not required by terraform.
@@ -1406,7 +1762,9 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
1406
1762
  pg_values["parameter"] = pg_values.pop("parameters")
1407
1763
  if self._multiregion_account(account) and len(provider) > 0:
1408
1764
  pg_values["provider"] = provider
1409
- return aws_db_parameter_group(pg_identifier, **pg_values)
1765
+ parameter_group = aws_db_parameter_group(pg_identifier, **pg_values)
1766
+ validate_parameter_group(parameter_group=parameter_group)
1767
+ return parameter_group
1410
1768
 
1411
1769
  # 'deps' should contain a list of terraform resource names
1412
1770
  # (not full objects) that must be created
@@ -1507,9 +1865,9 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
1507
1865
  ca_cert = values.pop("ca_cert", None)
1508
1866
  if ca_cert:
1509
1867
  # db.ca_cert
1510
- output_name_0_13 = output_prefix + "__db_ca_cert"
1868
+ output_name = output_prefix + "__db_ca_cert"
1511
1869
  output_value = self.secret_reader.read(ca_cert)
1512
- tf_resources.append(Output(output_name_0_13, value=output_value))
1870
+ tf_resources.append(Output(output_name, value=output_value, sensitive=True))
1513
1871
 
1514
1872
  region = self._region_from_availability_zone(az) or self.default_regions.get(
1515
1873
  account
@@ -1532,6 +1890,7 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
1532
1890
  replica_region = self.default_regions.get(account)
1533
1891
 
1534
1892
  source_values = self.init_values(source_info)
1893
+ db_name = self._get_db_name_from_values(source_values)
1535
1894
  if replica_region == region:
1536
1895
  # replica is in the same region as source
1537
1896
  values["replicate_source_db"] = replica_source
@@ -1631,7 +1990,7 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
1631
1990
  if sns_topic_name.startswith("arn:"):
1632
1991
  raise ValueError("destination should not be an arn")
1633
1992
  e_n["sns_topic"] = "${aws_sns_topic" + "." + sns_topic_name + ".arn}"
1634
- e_n["source_ids"] = ["${aws_db_instance" + "." + identifier + ".id}"]
1993
+ e_n["source_ids"] = [identifier]
1635
1994
  source_type = e_n.get("source_type", "all")
1636
1995
  e_n_identifier = (
1637
1996
  f"{sns_topic_name}_{source_type}_aws_db_event_subscription"
@@ -1642,33 +2001,34 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
1642
2001
  # rds instance
1643
2002
  # Ref: https://www.terraform.io/docs/providers/aws/r/db_instance.html
1644
2003
  tf_resource = aws_db_instance(identifier, **values)
2004
+ verify_rds_best_practices(tf_resource, spec.resource["data_classification"])
1645
2005
  tf_resources.append(tf_resource)
1646
2006
 
1647
2007
  # rds outputs
1648
2008
  # we want the outputs to be formed into an OpenShift Secret
1649
2009
  # with the following fields
1650
2010
  # db.host
1651
- output_name_0_13 = output_prefix + "__db_host"
2011
+ output_name = output_prefix + "__db_host"
1652
2012
  output_value = "${" + tf_resource.address + "}"
1653
- tf_resources.append(Output(output_name_0_13, value=output_value))
2013
+ tf_resources.append(Output(output_name, value=output_value))
1654
2014
  # db.port
1655
- output_name_0_13 = output_prefix + "__db_port"
2015
+ output_name = output_prefix + "__db_port"
1656
2016
  output_value = "${" + str(tf_resource.port) + "}"
1657
- tf_resources.append(Output(output_name_0_13, value=output_value))
2017
+ tf_resources.append(Output(output_name, value=output_value))
1658
2018
  # db.name
1659
- output_name_0_13 = output_prefix + "__db_name"
1660
- output_value = output_resource_db_name or values.get("name", "")
1661
- tf_resources.append(Output(output_name_0_13, value=output_value))
2019
+ output_name = output_prefix + "__db_name"
2020
+ output_value = output_resource_db_name or db_name
2021
+ tf_resources.append(Output(output_name, value=output_value))
1662
2022
  # only set db user/password if not a replica or creation from snapshot
1663
2023
  if self._db_needs_auth(values):
1664
2024
  # db.user
1665
- output_name_0_13 = output_prefix + "__db_user"
2025
+ output_name = output_prefix + "__db_user"
1666
2026
  output_value = values["username"]
1667
- tf_resources.append(Output(output_name_0_13, value=output_value))
2027
+ tf_resources.append(Output(output_name, value=output_value, sensitive=True))
1668
2028
  # db.password
1669
- output_name_0_13 = output_prefix + "__db_password"
2029
+ output_name = output_prefix + "__db_password"
1670
2030
  output_value = values["password"]
1671
- tf_resources.append(Output(output_name_0_13, value=output_value))
2031
+ tf_resources.append(Output(output_name, value=output_value, sensitive=True))
1672
2032
  # only add reset_password key to the terraform state
1673
2033
  # if reset_password_current_value is defined.
1674
2034
  # this means that if the reset_password field is removed
@@ -1676,9 +2036,9 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
1676
2036
  # removed from the state and from the output resource,
1677
2037
  # leading to a recycle of the pods using this resource.
1678
2038
  if reset_password_current_value:
1679
- output_name_0_13 = output_prefix + "__reset_password"
2039
+ output_name = output_prefix + "__reset_password"
1680
2040
  output_value = reset_password_current_value
1681
- tf_resources.append(Output(output_name_0_13, value=output_value))
2041
+ tf_resources.append(Output(output_name, value=output_value))
1682
2042
 
1683
2043
  self.add_resources(account, tf_resources)
1684
2044
 
@@ -1686,14 +2046,11 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
1686
2046
  if name not in self.configs:
1687
2047
  return False
1688
2048
 
1689
- if self.configs[name]["supportedDeploymentRegions"] is not None:
1690
- return True
1691
-
1692
- return False
2049
+ return self.configs[name]["supportedDeploymentRegions"] is not None
1693
2050
 
1694
2051
  def _find_resource_spec(
1695
2052
  self, account: str, source: str, provider: str
1696
- ) -> Optional[ExternalResourceSpec]:
2053
+ ) -> ExternalResourceSpec | None:
1697
2054
  if account not in self.account_resource_specs:
1698
2055
  return None
1699
2056
 
@@ -1702,6 +2059,9 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
1702
2059
  return spec
1703
2060
  return None
1704
2061
 
2062
+ def _get_db_name_from_values(self, values: dict) -> str:
2063
+ return values.get("name") or values.get("db_name") or ""
2064
+
1705
2065
  @staticmethod
1706
2066
  def _region_from_availability_zone(az):
1707
2067
  # Find the region by removing the last character from the
@@ -1714,12 +2074,10 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
1714
2074
 
1715
2075
  @staticmethod
1716
2076
  def _db_needs_auth(config):
1717
- if (
2077
+ return bool(
1718
2078
  "replicate_source_db" not in config
1719
2079
  and config.get("replica_source", None) is None
1720
- ):
1721
- return True
1722
- return False
2080
+ )
1723
2081
 
1724
2082
  @staticmethod
1725
2083
  def validate_db_name(name):
@@ -1764,9 +2122,9 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
1764
2122
  common_values.get("server_side_encryption_configuration")
1765
2123
  or DEFAULT_S3_SSE_CONFIGURATION
1766
2124
  )
1767
- values[
1768
- "server_side_encryption_configuration"
1769
- ] = server_side_encryption_configuration
2125
+ values["server_side_encryption_configuration"] = (
2126
+ server_side_encryption_configuration
2127
+ )
1770
2128
  # Support static website hosting [rosa-authenticator]
1771
2129
  website = common_values.get("website")
1772
2130
  if website:
@@ -1819,6 +2177,27 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
1819
2177
  if cors_rules:
1820
2178
  # common_values['cors_rules'] is a list of cors_rules
1821
2179
  values["cors_rule"] = cors_rules
2180
+ # S3 Bucket Logging
2181
+ s3_bucket_logging = common_values.get("s3_bucket_logging")
2182
+ if s3_bucket_logging:
2183
+ target_bucket_name = s3_bucket_logging.get("target_bucket_name")
2184
+ logging_identifier = f"{identifier}-logging"
2185
+
2186
+ # Logging config will be set out of this resource
2187
+ # the following `ignore_change` allows to avoid conflicts
2188
+ values.setdefault("lifecycle", {}).setdefault("ignore_changes", []).append(
2189
+ "logging"
2190
+ )
2191
+
2192
+ logging_values = {
2193
+ "bucket": "${aws_s3_bucket." + identifier + ".id}",
2194
+ "target_bucket": "${aws_s3_bucket." + target_bucket_name + ".id}",
2195
+ "target_prefix": s3_bucket_logging.get("target_prefix", ""),
2196
+ }
2197
+ logging_tf_resource = aws_s3_bucket_logging(
2198
+ logging_identifier, **logging_values
2199
+ )
2200
+ tf_resources.append(logging_tf_resource)
1822
2201
  deps = []
1823
2202
  replication_configs = common_values.get("replication_configurations")
1824
2203
  if replication_configs:
@@ -1889,9 +2268,10 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
1889
2268
  # Terraform resource reference:
1890
2269
  # https://www.terraform.io/docs/providers/aws/r/iam_policy_attachment.html
1891
2270
  rc_values.clear()
1892
- rc_values["depends_on"] = self.get_dependencies(
1893
- [role_resource, policy_resource]
1894
- )
2271
+ rc_values["depends_on"] = self.get_dependencies([
2272
+ role_resource,
2273
+ policy_resource,
2274
+ ])
1895
2275
  rc_values["role"] = "${aws_iam_role." + id + ".name}"
1896
2276
  rc_values["policy_arn"] = "${aws_iam_policy." + id + ".arn}"
1897
2277
  tf_resource = aws_iam_role_policy_attachment(id, **rc_values)
@@ -1923,14 +2303,14 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
1923
2303
  values["provider"] = "aws." + region
1924
2304
  bucket_tf_resource = aws_s3_bucket(identifier, **values)
1925
2305
  tf_resources.append(bucket_tf_resource)
1926
- output_name_0_13 = output_prefix + "__bucket"
2306
+ output_name = output_prefix + "__bucket"
1927
2307
  output_value = bucket_tf_resource.bucket
1928
- tf_resources.append(Output(output_name_0_13, value=output_value))
1929
- output_name_0_13 = output_prefix + "__aws_region"
1930
- tf_resources.append(Output(output_name_0_13, value=region))
1931
- endpoint = "s3.{}.amazonaws.com".format(region)
1932
- output_name_0_13 = output_prefix + "__endpoint"
1933
- tf_resources.append(Output(output_name_0_13, value=endpoint))
2308
+ tf_resources.append(Output(output_name, value=output_value))
2309
+ output_name = output_prefix + "__aws_region"
2310
+ tf_resources.append(Output(output_name, value=region))
2311
+ endpoint = f"s3.{region}.amazonaws.com"
2312
+ output_name = output_prefix + "__endpoint"
2313
+ tf_resources.append(Output(output_name, value=endpoint))
1934
2314
 
1935
2315
  sqs_identifier = common_values.get("sqs_identifier", None)
1936
2316
  if sqs_identifier is not None:
@@ -2043,6 +2423,8 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
2043
2423
  "policy": bucket_policy,
2044
2424
  "depends_on": self.get_dependencies([bucket_tf_resource]),
2045
2425
  }
2426
+ if self._multiregion_account(account):
2427
+ values["provider"] = "aws." + region
2046
2428
  bucket_policy_tf_resource = aws_s3_bucket_policy(identifier, **values)
2047
2429
  tf_resources.append(bucket_policy_tf_resource)
2048
2430
 
@@ -2065,10 +2447,9 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
2065
2447
 
2066
2448
  # iam user policy for bucket
2067
2449
  values = {}
2068
- values["user"] = identifier
2069
2450
  values["name"] = identifier
2070
2451
 
2071
- action = ["s3:*Object"]
2452
+ action = ["s3:*Object*"]
2072
2453
  if common_values.get("acl", "private") == "public-read":
2073
2454
  action.append("s3:PutObjectAcl")
2074
2455
  allow_object_tagging = common_values.get("allow_object_tagging", False)
@@ -2095,12 +2476,6 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
2095
2476
  values["policy"] = json.dumps(policy, sort_keys=True)
2096
2477
  values["depends_on"] = self.get_dependencies([user_tf_resource])
2097
2478
 
2098
- # This is temporary, we are going to remove this after the
2099
- # aws_iam_user_policy_attachment is deployed
2100
- tf_aws_iam_user_policy = aws_iam_user_policy(identifier, **values)
2101
- tf_resources.append(tf_aws_iam_user_policy)
2102
-
2103
- values.pop("user")
2104
2479
  tf_aws_iam_policy = aws_iam_policy(identifier, **values)
2105
2480
  tf_resources.append(tf_aws_iam_policy)
2106
2481
 
@@ -2184,23 +2559,23 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
2184
2559
  # we want the outputs to be formed into an OpenShift Secret
2185
2560
  # with the following fields
2186
2561
  # db.endpoint
2187
- output_name_0_13 = output_prefix + "__db_endpoint"
2562
+ output_name = output_prefix + "__db_endpoint"
2188
2563
  # https://docs.aws.amazon.com/AmazonElastiCache/
2189
2564
  # latest/red-ug/Endpoints.html
2190
2565
  if pg_cluster_enabled:
2191
2566
  output_value = "${" + tf_resource.configuration_endpoint_address + "}"
2192
2567
  else:
2193
2568
  output_value = "${" + tf_resource.primary_endpoint_address + "}"
2194
- tf_resources.append(Output(output_name_0_13, value=output_value))
2569
+ tf_resources.append(Output(output_name, value=output_value))
2195
2570
  # db.port
2196
- output_name_0_13 = output_prefix + "__db_port"
2571
+ output_name = output_prefix + "__db_port"
2197
2572
  output_value = "${" + str(tf_resource.port) + "}"
2198
- tf_resources.append(Output(output_name_0_13, value=output_value))
2573
+ tf_resources.append(Output(output_name, value=output_value))
2199
2574
  # db.auth_token
2200
2575
  if values.get("transit_encryption_enabled", False):
2201
- output_name_0_13 = output_prefix + "__db_auth_token"
2576
+ output_name = output_prefix + "__db_auth_token"
2202
2577
  output_value = values["auth_token"]
2203
- tf_resources.append(Output(output_name_0_13, value=output_value))
2578
+ tf_resources.append(Output(output_name, value=output_value, sensitive=True))
2204
2579
 
2205
2580
  self.add_resources(account, tf_resources)
2206
2581
 
@@ -2245,19 +2620,8 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
2245
2620
  for k, v in data.items():
2246
2621
  to_replace = "${" + k + "}"
2247
2622
  user_policy = user_policy.replace(to_replace, v)
2248
- output_name_0_13 = output_prefix + "__{}".format(k)
2249
- tf_resources.append(Output(output_name_0_13, value=v))
2250
-
2251
- # This is temporary, we are going to remove this after the
2252
- # aws_iam_user_policy_attachment is deployed
2253
- tf_aws_iam_user_policy = aws_iam_user_policy(
2254
- identifier,
2255
- name=identifier,
2256
- user=identifier,
2257
- policy=user_policy,
2258
- depends_on=self.get_dependencies([user_tf_resource]),
2259
- )
2260
- tf_resources.append(tf_aws_iam_user_policy)
2623
+ output_name = output_prefix + f"__{k}"
2624
+ tf_resources.append(Output(output_name, value=v))
2261
2625
 
2262
2626
  tf_aws_iam_policy = aws_iam_policy(
2263
2627
  identifier,
@@ -2286,15 +2650,15 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
2286
2650
  # OCM AWS infrastructure access.
2287
2651
  assume_role = aws_infrastructure_access.get("assume_role")
2288
2652
  if assume_role:
2289
- output_name_0_13 = output_prefix + "__role_arn"
2290
- tf_resources.append(Output(output_name_0_13, value=assume_role))
2653
+ output_name = output_prefix + "__role_arn"
2654
+ tf_resources.append(Output(output_name, value=assume_role))
2291
2655
  elif ocm_map:
2292
2656
  cluster = aws_infrastructure_access["cluster"]["name"]
2293
2657
  ocm = ocm_map.get(cluster)
2294
2658
  role_grants = ocm.get_aws_infrastructure_access_role_grants(cluster)
2295
2659
  for user_arn, _, state, switch_role_link in role_grants:
2296
2660
  # find correct user by identifier
2297
- user_id = awsh.get_user_id_from_arn(user_arn)
2661
+ user_id = awsh.get_id_from_arn(user_arn)
2298
2662
  # output will only be added once
2299
2663
  # terraform-resources created the user
2300
2664
  # and ocm-aws-infrastructure-access granted it the role
@@ -2302,10 +2666,8 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
2302
2666
  switch_role_arn = awsh.get_role_arn_from_role_link(
2303
2667
  switch_role_link
2304
2668
  )
2305
- output_name_0_13 = output_prefix + "__role_arn"
2306
- tf_resources.append(
2307
- Output(output_name_0_13, value=switch_role_arn)
2308
- )
2669
+ output_name = output_prefix + "__role_arn"
2670
+ tf_resources.append(Output(output_name, value=switch_role_arn))
2309
2671
  else:
2310
2672
  raise KeyError(
2311
2673
  f"[{account}/{identifier}] "
@@ -2348,9 +2710,9 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
2348
2710
  )
2349
2711
  )
2350
2712
 
2351
- output_name_0_13 = output_prefix + "__secrets_prefix"
2713
+ output_name = output_prefix + "__secrets_prefix"
2352
2714
  output_value = secrets_prefix
2353
- tf_resources.append(Output(output_name_0_13, value=output_value))
2715
+ tf_resources.append(Output(output_name, value=output_value))
2354
2716
 
2355
2717
  self.add_resources(account, tf_resources)
2356
2718
 
@@ -2365,12 +2727,13 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
2365
2727
 
2366
2728
  assume_role = common_values["assume_role"]
2367
2729
  assume_role = {k: v for k, v in assume_role.items() if v is not None}
2730
+ assume_action = common_values.get("assume_action") or "AssumeRole"
2368
2731
  # assume role policy
2369
2732
  assume_role_policy = {
2370
2733
  "Version": "2012-10-17",
2371
2734
  "Statement": [
2372
2735
  {
2373
- "Action": "sts:AssumeRole",
2736
+ "Action": f"sts:{assume_action}",
2374
2737
  "Effect": "Allow",
2375
2738
  "Principal": assume_role,
2376
2739
  }
@@ -2391,16 +2754,96 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
2391
2754
  if inline_policy:
2392
2755
  values["inline_policy"] = {"name": identifier, "policy": inline_policy}
2393
2756
 
2757
+ if lifecycle := self.get_resource_lifecycle(common_values):
2758
+ values["lifecycle"] = lifecycle
2759
+
2394
2760
  role_tf_resource = aws_iam_role(identifier, **values)
2395
2761
  tf_resources.append(role_tf_resource)
2396
2762
 
2763
+ if role_policy := common_values.get("role_policy"):
2764
+ tf_aws_iam_policy = aws_iam_policy(
2765
+ identifier,
2766
+ name=identifier,
2767
+ policy=role_policy,
2768
+ depends_on=self.get_dependencies([role_tf_resource]),
2769
+ tags=common_values["tags"],
2770
+ )
2771
+ tf_resources.append(tf_aws_iam_policy)
2772
+
2773
+ tf_aws_iam_policy_attachment = aws_iam_role_policy_attachment(
2774
+ identifier,
2775
+ role=role_tf_resource.name,
2776
+ policy_arn=f"${{{tf_aws_iam_policy.arn}}}",
2777
+ depends_on=self.get_dependencies([role_tf_resource, tf_aws_iam_policy]),
2778
+ )
2779
+ tf_resources.append(tf_aws_iam_policy_attachment)
2780
+
2781
+ for policy in common_values.get("policies") or []:
2782
+ tf_iam_role_policy_attachment = aws_iam_role_policy_attachment(
2783
+ identifier + "-" + policy,
2784
+ role=role_tf_resource.name,
2785
+ policy_arn=f"arn:{self._get_partition(account)}:iam::aws:policy/{policy}",
2786
+ depends_on=self.get_dependencies([role_tf_resource]),
2787
+ )
2788
+ tf_resources.append(tf_iam_role_policy_attachment)
2789
+
2397
2790
  # output role arn
2398
- output_name_0_13 = output_prefix + "__role_arn"
2791
+ output_name = output_prefix + "__role_arn"
2399
2792
  output_value = "${" + role_tf_resource.arn + "}"
2400
- tf_resources.append(Output(output_name_0_13, value=output_value))
2793
+ tf_resources.append(Output(output_name, value=output_value))
2401
2794
 
2402
2795
  self.add_resources(account, tf_resources)
2403
2796
 
2797
+ def populate_iam_policy(self, account: str, name: str, policy: dict[str, Any]):
2798
+ tf_aws_iam_policy = aws_iam_policy(
2799
+ f"{account}-{name}", name=name, policy=json.dumps(policy)
2800
+ )
2801
+ self.add_resource(account, tf_aws_iam_policy)
2802
+
2803
+ def populate_saml_iam_role(
2804
+ self,
2805
+ account: str,
2806
+ name: str,
2807
+ saml_provider_name: str,
2808
+ aws_managed_policies: list[str],
2809
+ customer_managed_policies: list[str] | None = None,
2810
+ max_session_duration_hours: int = 1,
2811
+ ) -> None:
2812
+ """Manage the an IAM role needed for SAML authentication."""
2813
+ managed_policy_arns = [
2814
+ f"arn:{self._get_partition(account)}:iam::aws:policy/{policy}"
2815
+ for policy in aws_managed_policies
2816
+ ]
2817
+ managed_policy_arns += [
2818
+ f"arn:{self._get_partition(account)}:iam::{self.uids[account]}:policy/{policy}"
2819
+ for policy in customer_managed_policies or []
2820
+ ]
2821
+ assume_role_policy = {
2822
+ "Version": "2012-10-17",
2823
+ "Statement": [
2824
+ {
2825
+ "Effect": "Allow",
2826
+ "Principal": {
2827
+ "Federated": f"arn:{self._get_partition(account)}:iam::{self.uids[account]}:saml-provider/{saml_provider_name}"
2828
+ },
2829
+ "Action": "sts:AssumeRoleWithSAML",
2830
+ "Condition": {
2831
+ "StringEquals": {
2832
+ "SAML:aud": "https://signin.aws.amazon.com/saml"
2833
+ }
2834
+ },
2835
+ }
2836
+ ],
2837
+ }
2838
+ role_tf_resource = aws_iam_role(
2839
+ f"{account}-{name}",
2840
+ name=name,
2841
+ assume_role_policy=json.dumps(assume_role_policy),
2842
+ managed_policy_arns=managed_policy_arns,
2843
+ max_session_duration=max_session_duration_hours * 3600,
2844
+ )
2845
+ self.add_resource(account, role_tf_resource)
2846
+
2404
2847
  def populate_tf_resource_sqs(self, spec):
2405
2848
  account = spec.provisioner_name
2406
2849
  identifier = spec.identifier
@@ -2414,9 +2857,9 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
2414
2857
  specs = common_values.get("specs")
2415
2858
  all_queues_per_spec = []
2416
2859
  kms_keys = set()
2417
- for spec in specs:
2418
- defaults = self.get_values(spec["defaults"])
2419
- queues = spec.pop("queues", [])
2860
+ for _spec in specs:
2861
+ defaults = self.get_values(_spec["defaults"])
2862
+ queues = _spec.pop("queues", [])
2420
2863
  all_queues = []
2421
2864
  for queue_kv in queues:
2422
2865
  queue_key = queue_kv["key"]
@@ -2473,13 +2916,11 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
2473
2916
  kms_keys.add(values["kms_master_key_id"])
2474
2917
  queue_tf_resource = aws_sqs_queue(queue, **values)
2475
2918
  tf_resources.append(queue_tf_resource)
2476
- output_name_0_13 = output_prefix + "__aws_region"
2477
- tf_resources.append(Output(output_name_0_13, value=region))
2478
- output_name_0_13 = "{}__{}".format(output_prefix, queue_key)
2479
- output_value = "https://sqs.{}.amazonaws.com/{}/{}".format(
2480
- region, uid, queue_name
2481
- )
2482
- tf_resources.append(Output(output_name_0_13, value=output_value))
2919
+ output_name = output_prefix + "__aws_region"
2920
+ tf_resources.append(Output(output_name, value=region))
2921
+ output_name = f"{output_prefix}__{queue_key}"
2922
+ output_value = f"https://sqs.{region}.amazonaws.com/{uid}/{queue_name}"
2923
+ tf_resources.append(Output(output_name, value=output_value))
2483
2924
  all_queues_per_spec.append(all_queues)
2484
2925
 
2485
2926
  # iam resources
@@ -2499,10 +2940,8 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
2499
2940
  )
2500
2941
 
2501
2942
  # iam policy for queue
2502
- policy_index = 0
2503
- for all_queues in all_queues_per_spec:
2943
+ for policy_index, all_queues in enumerate(all_queues_per_spec):
2504
2944
  policy_identifier = f"{identifier}-{policy_index}"
2505
- policy_index += 1
2506
2945
  if len(all_queues_per_spec) == 1:
2507
2946
  policy_identifier = identifier
2508
2947
  values = {}
@@ -2536,9 +2975,10 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
2536
2975
  values = {}
2537
2976
  values["user"] = identifier
2538
2977
  values["policy_arn"] = "${" + policy_tf_resource.arn + "}"
2539
- values["depends_on"] = self.get_dependencies(
2540
- [user_tf_resource, policy_tf_resource]
2541
- )
2978
+ values["depends_on"] = self.get_dependencies([
2979
+ user_tf_resource,
2980
+ policy_tf_resource,
2981
+ ])
2542
2982
  tf_resource = aws_iam_user_policy_attachment(policy_identifier, **values)
2543
2983
  tf_resources.append(tf_resource)
2544
2984
 
@@ -2554,10 +2994,7 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
2554
2994
 
2555
2995
  values = {}
2556
2996
  fifo_topic = common_values.get("fifo_topic", False)
2557
- if fifo_topic:
2558
- topic_name = identifier + (".fifo")
2559
- else:
2560
- topic_name = identifier
2997
+ topic_name = identifier + ".fifo" if fifo_topic else identifier
2561
2998
 
2562
2999
  values["name"] = topic_name
2563
3000
  values["policy"] = policy
@@ -2568,9 +3005,9 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
2568
3005
  tf_resource = aws_sns_topic(identifier, **values)
2569
3006
  tf_resources.append(tf_resource)
2570
3007
 
2571
- if "subscriptions" in common_values.keys():
3008
+ if "subscriptions" in common_values:
2572
3009
  subscriptions = common_values.get("subscriptions")
2573
- for sub in subscriptions:
3010
+ for index, sub in enumerate(subscriptions):
2574
3011
  sub_values = {}
2575
3012
  sub_values["topic_arn"] = "${aws_sns_topic" + "." + identifier + ".arn}"
2576
3013
  protocol = sub["protocol"]
@@ -2580,17 +3017,21 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
2580
3017
  raise ValueError(msg)
2581
3018
  sub_values["protocol"] = protocol
2582
3019
  sub_values["endpoint"] = endpoint
2583
- sub_identifier = f"{identifier}_{protocol}_aws_sns_topic_subscription"
3020
+ # append suffix only in case there are multiple subscriptions
3021
+ suffix = f"_{index + 1}" if len(subscriptions) > 1 else ""
3022
+ sub_identifier = (
3023
+ f"{identifier}_{protocol}_aws_sns_topic_subscription{suffix}"
3024
+ )
2584
3025
  sub_tf_resource = aws_sns_topic_subscription(
2585
3026
  sub_identifier, **sub_values
2586
3027
  )
2587
3028
  tf_resources.append(sub_tf_resource)
2588
3029
 
2589
- output_name_0_13 = output_prefix + "__aws_region"
2590
- tf_resources.append(Output(output_name_0_13, value=region))
2591
- output_name_0_13 = output_prefix + "__endpoint"
3030
+ output_name = output_prefix + "__aws_region"
3031
+ tf_resources.append(Output(output_name, value=region))
3032
+ output_name = output_prefix + "__endpoint"
2592
3033
  output_value = f"https://sns.{region}.amazonaws.com"
2593
- tf_resources.append(Output(output_name_0_13, value=output_value))
3034
+ tf_resources.append(Output(output_name, value=output_value))
2594
3035
  self.add_resources(account, tf_resources)
2595
3036
 
2596
3037
  def populate_tf_resource_dynamodb(self, spec):
@@ -2605,10 +3046,10 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
2605
3046
  region = common_values.get("region") or self.default_regions.get(account)
2606
3047
  specs = common_values.get("specs")
2607
3048
  all_tables = []
2608
- for spec in specs:
2609
- defaults = self.get_values(spec["defaults"])
3049
+ for _spec in specs:
3050
+ defaults = self.get_values(_spec["defaults"])
2610
3051
  attributes = defaults.pop("attributes")
2611
- tables = spec["tables"]
3052
+ tables = _spec["tables"]
2612
3053
  for table_kv in tables:
2613
3054
  table_key = table_kv["key"]
2614
3055
  table = table_kv["value"]
@@ -2626,14 +3067,14 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
2626
3067
  values["provider"] = "aws." + region
2627
3068
  table_tf_resource = aws_dynamodb_table(table, **values)
2628
3069
  tf_resources.append(table_tf_resource)
2629
- output_name_0_13 = "{}__{}".format(output_prefix, table_key)
2630
- tf_resources.append(Output(output_name_0_13, value=table))
3070
+ output_name = f"{output_prefix}__{table_key}"
3071
+ tf_resources.append(Output(output_name, value=table))
2631
3072
 
2632
- output_name_0_13 = output_prefix + "__aws_region"
2633
- tf_resources.append(Output(output_name_0_13, value=region))
2634
- output_name_0_13 = output_prefix + "__endpoint"
3073
+ output_name = output_prefix + "__aws_region"
3074
+ tf_resources.append(Output(output_name, value=region))
3075
+ output_name = output_prefix + "__endpoint"
2635
3076
  output_value = f"https://dynamodb.{region}.amazonaws.com"
2636
- tf_resources.append(Output(output_name_0_13, value=output_value))
3077
+ tf_resources.append(Output(output_name, value=output_value))
2637
3078
 
2638
3079
  # iam resources
2639
3080
  # Terraform resource reference:
@@ -2653,7 +3094,6 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
2653
3094
 
2654
3095
  # iam user policy for queue
2655
3096
  values = {}
2656
- values["user"] = identifier
2657
3097
  values["name"] = identifier
2658
3098
  policy = {
2659
3099
  "Version": "2012-10-17",
@@ -2662,8 +3102,7 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
2662
3102
  "Effect": "Allow",
2663
3103
  "Action": ["dynamodb:*"],
2664
3104
  "Resource": [
2665
- "arn:aws:dynamodb:{}:{}:table/{}".format(region, uid, t)
2666
- for t in all_tables
3105
+ f"arn:aws:dynamodb:{region}:{uid}:table/{t}" for t in all_tables
2667
3106
  ],
2668
3107
  }
2669
3108
  ],
@@ -2671,12 +3110,6 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
2671
3110
  values["policy"] = json.dumps(policy, sort_keys=True)
2672
3111
  values["depends_on"] = self.get_dependencies([user_tf_resource])
2673
3112
 
2674
- # This is temporary, we are going to remove this after the
2675
- # aws_iam_user_policy_attachment is deployed
2676
- tf_aws_iam_user_policy = aws_iam_user_policy(identifier, **values)
2677
- tf_resources.append(tf_aws_iam_user_policy)
2678
-
2679
- values.pop("user")
2680
3113
  tf_aws_iam_policy = aws_iam_policy(identifier, **values)
2681
3114
  tf_resources.append(tf_aws_iam_policy)
2682
3115
 
@@ -2719,13 +3152,13 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
2719
3152
  values["repository_name"] = values.pop("name")
2720
3153
  ecr_tf_resource = aws_ecrpublic_repository(identifier, **values)
2721
3154
  tf_resources.append(ecr_tf_resource)
2722
- output_name_0_13 = output_prefix + "__url"
3155
+ output_name = output_prefix + "__url"
2723
3156
  output_value = "${" + ecr_tf_resource.repository_url + "}"
2724
3157
  if public:
2725
3158
  output_value = "${" + ecr_tf_resource.repository_uri + "}"
2726
- tf_resources.append(Output(output_name_0_13, value=output_value))
2727
- output_name_0_13 = output_prefix + "__aws_region"
2728
- tf_resources.append(Output(output_name_0_13, value=region))
3159
+ tf_resources.append(Output(output_name, value=output_value))
3160
+ output_name = output_prefix + "__aws_region"
3161
+ tf_resources.append(Output(output_name, value=region))
2729
3162
 
2730
3163
  # iam resources
2731
3164
  # Terraform resource reference:
@@ -2746,7 +3179,6 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
2746
3179
 
2747
3180
  # iam user policy for bucket
2748
3181
  values = {}
2749
- values["user"] = identifier
2750
3182
  values["name"] = identifier
2751
3183
  policy = {
2752
3184
  "Version": "2012-10-17",
@@ -2787,12 +3219,6 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
2787
3219
  values["policy"] = json.dumps(policy, sort_keys=True)
2788
3220
  values["depends_on"] = self.get_dependencies([user_tf_resource])
2789
3221
 
2790
- # This is temporary, we are going to remove this after the
2791
- # aws_iam_user_policy_attachment is deployed
2792
- tf_aws_iam_user_policy = aws_iam_user_policy(identifier, **values)
2793
- tf_resources.append(tf_aws_iam_user_policy)
2794
-
2795
- values.pop("user")
2796
3222
  tf_aws_iam_policy = aws_iam_policy(identifier, **values)
2797
3223
  tf_resources.append(tf_aws_iam_policy)
2798
3224
 
@@ -2850,49 +3276,9 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
2850
3276
  bucket_policy_tf_resource = aws_s3_bucket_policy(identifier, **values)
2851
3277
  tf_resources.append(bucket_policy_tf_resource)
2852
3278
 
2853
- # cloud front distribution
2854
3279
  values = common_values.get("distribution_config", {})
2855
- values["tags"] = common_values["tags"]
2856
- values.setdefault("default_cache_behavior", {}).setdefault(
2857
- "target_origin_id", "default"
2858
- )
2859
- origin = {
2860
- "domain_name": "${" + bucket_tf_resource.bucket_domain_name + "}",
2861
- "origin_id": values["default_cache_behavior"]["target_origin_id"],
2862
- "s3_origin_config": {
2863
- "origin_access_identity": "origin-access-identity/cloudfront/"
2864
- + "${"
2865
- + cf_oai_tf_resource.id
2866
- + "}"
2867
- },
2868
- }
2869
- values["origin"] = [origin]
2870
- cf_distribution_tf_resource = aws_cloudfront_distribution(identifier, **values)
2871
- tf_resources.append(cf_distribution_tf_resource)
2872
-
2873
- # outputs
2874
- # cloud_front_origin_access_identity_id
2875
- output_name_0_13 = output_prefix + "__cloud_front_origin_access_identity_id"
2876
- output_value = "${" + cf_oai_tf_resource.id + "}"
2877
- tf_resources.append(Output(output_name_0_13, value=output_value))
2878
- # s3_canonical_user_id
2879
- output_name_0_13 = output_prefix + "__s3_canonical_user_id"
2880
- output_value = "${" + cf_oai_tf_resource.s3_canonical_user_id + "}"
2881
- tf_resources.append(Output(output_name_0_13, value=output_value))
2882
- # distribution_domain
2883
- output_name_0_13 = output_prefix + "__distribution_domain"
2884
- output_value = "${" + cf_distribution_tf_resource.domain_name + "}"
2885
- tf_resources.append(Output(output_name_0_13, value=output_value))
2886
- # origin_access_identity
2887
- output_name_0_13 = output_prefix + "__origin_access_identity"
2888
- output_value = (
2889
- "origin-access-identity/cloudfront/" + "${" + cf_oai_tf_resource.id + "}"
2890
- )
2891
- tf_resources.append(Output(output_name_0_13, value=output_value))
2892
-
2893
3280
  # aws_s3_bucket_acl
2894
- values = common_values.get("distribution_config", {})
2895
- if "logging_config" in values.keys():
3281
+ if "logging_config" in values:
2896
3282
  # we could set this at a global level with a standard name like "cloudfront"
2897
3283
  # but we need all aws accounts upgraded to aws provider >3.60 first
2898
3284
  tf_resources.append(
@@ -2900,7 +3286,7 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
2900
3286
  )
2901
3287
 
2902
3288
  logging_config_bucket = values["logging_config"]
2903
- values = {}
3289
+ acl_values = {}
2904
3290
  access_control_policy = {
2905
3291
  "owner": {
2906
3292
  "id": "${data.aws_canonical_user_id.current.id}",
@@ -2923,12 +3309,61 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
2923
3309
  },
2924
3310
  ],
2925
3311
  }
2926
- values["access_control_policy"] = access_control_policy
2927
- values["bucket"] = logging_config_bucket.get("bucket").split(".")[0]
3312
+ external_account_id = logging_config_bucket.pop("external_account_id", None)
3313
+ if external_account_id:
3314
+ external_account_policy = {
3315
+ "grantee": {
3316
+ "id": external_account_id,
3317
+ "type": "CanonicalUser",
3318
+ },
3319
+ "permission": "FULL_CONTROL",
3320
+ }
3321
+ access_control_policy["grant"].append(external_account_policy)
3322
+ acl_values["access_control_policy"] = access_control_policy
3323
+ acl_values["bucket"] = logging_config_bucket.get("bucket").split(".")[0]
2928
3324
 
2929
- aws_s3_bucket_acl_resource = aws_s3_bucket_acl(identifier, **values)
3325
+ aws_s3_bucket_acl_resource = aws_s3_bucket_acl(identifier, **acl_values)
2930
3326
  tf_resources.append(aws_s3_bucket_acl_resource)
2931
3327
 
3328
+ # cloud front distribution
3329
+ values["tags"] = common_values["tags"]
3330
+ values.setdefault("default_cache_behavior", {}).setdefault(
3331
+ "target_origin_id", "default"
3332
+ )
3333
+ origin = {
3334
+ "domain_name": "${" + bucket_tf_resource.bucket_domain_name + "}",
3335
+ "origin_id": values["default_cache_behavior"]["target_origin_id"],
3336
+ "s3_origin_config": {
3337
+ "origin_access_identity": "origin-access-identity/cloudfront/"
3338
+ + "${"
3339
+ + cf_oai_tf_resource.id
3340
+ + "}"
3341
+ },
3342
+ }
3343
+ values["origin"] = [origin]
3344
+ cf_distribution_tf_resource = aws_cloudfront_distribution(identifier, **values)
3345
+ tf_resources.append(cf_distribution_tf_resource)
3346
+
3347
+ # outputs
3348
+ # cloud_front_origin_access_identity_id
3349
+ output_name = output_prefix + "__cloud_front_origin_access_identity_id"
3350
+ output_value = "${" + cf_oai_tf_resource.id + "}"
3351
+ tf_resources.append(Output(output_name, value=output_value))
3352
+ # s3_canonical_user_id
3353
+ output_name = output_prefix + "__s3_canonical_user_id"
3354
+ output_value = "${" + cf_oai_tf_resource.s3_canonical_user_id + "}"
3355
+ tf_resources.append(Output(output_name, value=output_value))
3356
+ # distribution_domain
3357
+ output_name = output_prefix + "__distribution_domain"
3358
+ output_value = "${" + cf_distribution_tf_resource.domain_name + "}"
3359
+ tf_resources.append(Output(output_name, value=output_value))
3360
+ # origin_access_identity
3361
+ output_name = output_prefix + "__origin_access_identity"
3362
+ output_value = (
3363
+ "origin-access-identity/cloudfront/" + "${" + cf_oai_tf_resource.id + "}"
3364
+ )
3365
+ tf_resources.append(Output(output_name, value=output_value))
3366
+
2932
3367
  self.add_resources(account, tf_resources)
2933
3368
 
2934
3369
  def populate_tf_resource_s3_sqs(self, spec):
@@ -3076,13 +3511,13 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
3076
3511
  tf_resources.append(access_key_tf_resource)
3077
3512
  # outputs
3078
3513
  # sqs_aws_access_key_id
3079
- output_name_0_13 = output_prefix + "__sqs_aws_access_key_id"
3514
+ output_name = output_prefix + "__sqs_aws_access_key_id"
3080
3515
  output_value = "${" + access_key_tf_resource.id + "}"
3081
- tf_resources.append(Output(output_name_0_13, value=output_value))
3516
+ tf_resources.append(Output(output_name, value=output_value, sensitive=True))
3082
3517
  # sqs_aws_secret_access_key
3083
- output_name_0_13 = output_prefix + "__sqs_aws_secret_access_key"
3518
+ output_name = output_prefix + "__sqs_aws_secret_access_key"
3084
3519
  output_value = "${" + access_key_tf_resource.secret + "}"
3085
- tf_resources.append(Output(output_name_0_13, value=output_value))
3520
+ tf_resources.append(Output(output_name, value=output_value, sensitive=True))
3086
3521
 
3087
3522
  # iam policy for queue
3088
3523
  values = {}
@@ -3116,20 +3551,19 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
3116
3551
  values = {}
3117
3552
  values["user"] = sqs_identifier
3118
3553
  values["policy_arn"] = "${" + policy_tf_resource.arn + "}"
3119
- values["depends_on"] = self.get_dependencies(
3120
- [user_tf_resource, policy_tf_resource]
3121
- )
3554
+ values["depends_on"] = self.get_dependencies([
3555
+ user_tf_resource,
3556
+ policy_tf_resource,
3557
+ ])
3122
3558
  user_policy_attachment_tf_resource = aws_iam_user_policy_attachment(
3123
3559
  sqs_identifier, **values
3124
3560
  )
3125
3561
  tf_resources.append(user_policy_attachment_tf_resource)
3126
3562
 
3127
3563
  # outputs
3128
- output_name_0_13 = "{}__{}".format(output_prefix, sqs_identifier)
3129
- output_value = "https://sqs.{}.amazonaws.com/{}/{}".format(
3130
- region, uid, sqs_identifier
3131
- )
3132
- tf_resources.append(Output(output_name_0_13, value=output_value))
3564
+ output_name = f"{output_prefix}__{sqs_identifier}"
3565
+ output_value = f"https://sqs.{region}.amazonaws.com/{uid}/{sqs_identifier}"
3566
+ tf_resources.append(Output(output_name, value=output_value))
3133
3567
 
3134
3568
  self.add_resources(account, tf_resources)
3135
3569
 
@@ -3294,11 +3728,11 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
3294
3728
  )
3295
3729
  tf_resources.append(subscription_tf_resource)
3296
3730
 
3297
- output_name_0_13 = output_prefix + "__log_group_name"
3731
+ output_name = output_prefix + "__log_group_name"
3298
3732
  output_value = log_group_tf_resource.name
3299
- tf_resources.append(Output(output_name_0_13, value=output_value))
3300
- output_name_0_13 = output_prefix + "__aws_region"
3301
- tf_resources.append(Output(output_name_0_13, value=region))
3733
+ tf_resources.append(Output(output_name, value=output_value))
3734
+ output_name = output_prefix + "__aws_region"
3735
+ tf_resources.append(Output(output_name, value=region))
3302
3736
 
3303
3737
  # iam resources
3304
3738
  # Terraform resource reference:
@@ -3340,18 +3774,11 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
3340
3774
  ],
3341
3775
  }
3342
3776
  values = {
3343
- "user": identifier,
3344
3777
  "name": identifier,
3345
3778
  "policy": json.dumps(policy, sort_keys=True),
3346
3779
  "depends_on": self.get_dependencies([user_tf_resource]),
3347
3780
  }
3348
3781
 
3349
- # This is temporary, we are going to remove this after the
3350
- # aws_iam_user_policy_attachment is deployed
3351
- tf_aws_iam_user_policy = aws_iam_user_policy(identifier, **values)
3352
- tf_resources.append(tf_aws_iam_user_policy)
3353
-
3354
- values.pop("user")
3355
3782
  tf_aws_iam_policy = aws_iam_policy(identifier, **values)
3356
3783
  tf_resources.append(tf_aws_iam_policy)
3357
3784
 
@@ -3395,9 +3822,9 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
3395
3822
  tf_resources.append(tf_resource)
3396
3823
 
3397
3824
  # key_id
3398
- output_name_0_13 = output_prefix + "__key_id"
3825
+ output_name = output_prefix + "__key_id"
3399
3826
  output_value = "${" + tf_resource.key_id + "}"
3400
- tf_resources.append(Output(output_name_0_13, value=output_value))
3827
+ tf_resources.append(Output(output_name, value=output_value))
3401
3828
 
3402
3829
  alias_values = {}
3403
3830
  alias_values["name"] = "alias/" + identifier
@@ -3510,11 +3937,9 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
3510
3937
  attachment_values = {
3511
3938
  "role": role_tf_resource.name,
3512
3939
  "policy_arn": "${" + policy_tf_resource.arn + "}",
3513
- "depends_on": self.get_dependencies(
3514
- [
3515
- role_tf_resource,
3516
- ]
3517
- ),
3940
+ "depends_on": self.get_dependencies([
3941
+ role_tf_resource,
3942
+ ]),
3518
3943
  }
3519
3944
  attachment_tf_resource = aws_iam_role_policy_attachment(
3520
3945
  policy_identifier, **attachment_values
@@ -3598,9 +4023,9 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
3598
4023
  "starting_position_timestamp", None
3599
4024
  )
3600
4025
  if not starting_position_timestamp:
3601
- source_vaules[
3602
- "starting_position_timestamp"
3603
- ] = starting_position_timestamp
4026
+ source_vaules["starting_position_timestamp"] = (
4027
+ starting_position_timestamp
4028
+ )
3604
4029
 
3605
4030
  batch_size = common_values.get("batch_size", 100)
3606
4031
  source_vaules["batch_size"] = batch_size
@@ -3617,11 +4042,11 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
3617
4042
 
3618
4043
  # outputs
3619
4044
  # stream_name
3620
- output_name_0_13 = output_prefix + "__stream_name"
3621
- tf_resources.append(Output(output_name_0_13, value=identifier))
4045
+ output_name = output_prefix + "__stream_name"
4046
+ tf_resources.append(Output(output_name, value=identifier))
3622
4047
  # aws_region
3623
- output_name_0_13 = output_prefix + "__aws_region"
3624
- tf_resources.append(Output(output_name_0_13, value=region))
4048
+ output_name = output_prefix + "__aws_region"
4049
+ tf_resources.append(Output(output_name, value=region))
3625
4050
 
3626
4051
  # iam resources
3627
4052
  policy = {
@@ -3713,17 +4138,10 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
3713
4138
 
3714
4139
  # iam user policy
3715
4140
  values = {}
3716
- values["user"] = identifier
3717
4141
  values["name"] = identifier
3718
4142
  values["policy"] = json.dumps(policy, sort_keys=True)
3719
4143
  values["depends_on"] = self.get_dependencies([user_tf_resource])
3720
4144
 
3721
- # This is temporary, we are going to remove this after the
3722
- # aws_iam_user_policy_attachment is deployed
3723
- tf_aws_iam_user_policy = aws_iam_user_policy(identifier, **values)
3724
- tf_resources.append(tf_aws_iam_user_policy)
3725
-
3726
- values.pop("user")
3727
4145
  tf_aws_iam_policy = aws_iam_policy(identifier, **values)
3728
4146
  tf_resources.append(tf_aws_iam_policy)
3729
4147
 
@@ -3746,13 +4164,13 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
3746
4164
  tf_resources.append(tf_resource)
3747
4165
  # outputs
3748
4166
  # aws_access_key_id
3749
- output_name_0_13 = output_prefix + "__aws_access_key_id"
4167
+ output_name = output_prefix + "__aws_access_key_id"
3750
4168
  output_value = "${" + tf_resource.id + "}"
3751
- tf_resources.append(Output(output_name_0_13, value=output_value))
4169
+ tf_resources.append(Output(output_name, value=output_value, sensitive=True))
3752
4170
  # aws_secret_access_key
3753
- output_name_0_13 = output_prefix + "__aws_secret_access_key"
4171
+ output_name = output_prefix + "__aws_secret_access_key"
3754
4172
  output_value = "${" + tf_resource.secret + "}"
3755
- tf_resources.append(Output(output_name_0_13, value=output_value))
4173
+ tf_resources.append(Output(output_name, value=output_value, sensitive=True))
3756
4174
 
3757
4175
  return tf_resources
3758
4176
 
@@ -3763,17 +4181,30 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
3763
4181
  def add_resource(self, account, tf_resource):
3764
4182
  if account not in self.locks:
3765
4183
  logging.debug(
3766
- "integration {} is disabled for account {}. "
3767
- "can not add resource".format(self.integration, account)
4184
+ f"integration {self.integration} is disabled for account {account}. "
4185
+ "can not add resource"
3768
4186
  )
3769
4187
  return
3770
4188
  with self.locks[account]:
3771
4189
  self.tss[account].add(tf_resource)
3772
4190
 
4191
+ def add_moved(self, account: str, moved: Moved):
4192
+ if account not in self.locks:
4193
+ logging.debug(
4194
+ f"integration {self.integration} is disabled for account {account}. "
4195
+ "can not add resource"
4196
+ )
4197
+ return
4198
+ with self.locks[account]:
4199
+ self.tss[account].setdefault("moved", []).append({
4200
+ "from": moved.fro,
4201
+ "to": moved.to,
4202
+ })
4203
+
3773
4204
  def dump(
3774
4205
  self,
3775
- print_to_file: Optional[str] = None,
3776
- existing_dirs: Optional[dict[str, str]] = None,
4206
+ print_to_file: str | None = None,
4207
+ existing_dirs: dict[str, str] | None = None,
3777
4208
  ) -> dict[str, str]:
3778
4209
  """
3779
4210
  Dump the Terraform configurations (in JSON format) to the working directories.
@@ -3796,21 +4227,36 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
3796
4227
  os.remove(print_to_file)
3797
4228
 
3798
4229
  for name, ts in self.tss.items():
4230
+ content = str(ts)
3799
4231
  if print_to_file:
3800
- with open(print_to_file, "a") as f:
4232
+ with open(print_to_file, "a", encoding="locale") as f:
3801
4233
  f.write(f"##### {name} #####\n")
3802
- f.write(str(ts))
4234
+ f.write(content)
3803
4235
  f.write("\n")
3804
4236
  if existing_dirs is None:
3805
4237
  wd = tempfile.mkdtemp(prefix=TMP_DIR_PREFIX)
3806
4238
  else:
3807
4239
  wd = working_dirs[name]
3808
- with open(wd + "/config.tf.json", "w") as f:
3809
- f.write(str(ts))
4240
+ with open(wd + "/config.tf.json", "w", encoding="locale") as f:
4241
+ f.write(content)
3810
4242
  working_dirs[name] = wd
3811
4243
 
3812
4244
  return working_dirs
3813
4245
 
4246
+ def terraform_configurations(self) -> dict[str, str]:
4247
+ """
4248
+ Return the Terraform configurations (in JSON format) for each AWS account.
4249
+ Terraform config content keys are sorted for consistent hash check.
4250
+ The result is not used for final file dump.
4251
+ Doc: https://python-terrascript.readthedocs.io/en/stable/quickstart.html
4252
+
4253
+ :return: key is AWS account name and value is terraform configuration
4254
+ """
4255
+ return {
4256
+ name: json.dumps(ts, indent=2, sort_keys=True)
4257
+ for name, ts in self.tss.items()
4258
+ }
4259
+
3814
4260
  def init_values(self, spec: ExternalResourceSpec, init_tags: bool = True) -> dict:
3815
4261
  """
3816
4262
  Initialize the values of the terraform resource and merge the defaults and
@@ -3834,6 +4280,33 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
3834
4280
  if val is not None:
3835
4281
  values[key] = val
3836
4282
 
4283
+ if self.versions and not self.versions.get(
4284
+ spec.provisioner_name, ""
4285
+ ).startswith("3"):
4286
+ if spec.provider == "rds":
4287
+ if db_name := values.pop("name", None):
4288
+ values["db_name"] = db_name
4289
+ if values.get("replica_source"):
4290
+ values.pop("db_name", None)
4291
+ elif spec.provider == "elasticache":
4292
+ if description := values.pop("replication_group_description", None):
4293
+ values["description"] = description
4294
+ if num_cache_clusters := values.pop("number_cache_clusters", None):
4295
+ values["num_cache_clusters"] = num_cache_clusters
4296
+ if cluster_mode := values.pop("cluster_mode", {}):
4297
+ for k, v in cluster_mode.items():
4298
+ values[k] = v
4299
+ values.pop("availability_zones", None)
4300
+ elif spec.provider == "msk":
4301
+ if ebs_volume_size := values.get("broker_node_group_info", {}).pop(
4302
+ "ebs_volume_size", None
4303
+ ):
4304
+ values["broker_node_group_info"].setdefault(
4305
+ "storage_info", {}
4306
+ ).setdefault("ebs_storage_info", {})[
4307
+ "volume_size"
4308
+ ] = ebs_volume_size
4309
+
3837
4310
  return values
3838
4311
 
3839
4312
  @staticmethod
@@ -3863,39 +4336,39 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
3863
4336
  tf_resources: list[Resource],
3864
4337
  spec: ExternalResourceSpec,
3865
4338
  ):
3866
- output_format_0_13 = "{}__{}_{}"
4339
+ output_format = "{}__{}_{}"
3867
4340
  # cluster
3868
- output_name_0_13 = output_format_0_13.format(
4341
+ output_name = output_format.format(
3869
4342
  spec.output_prefix, self.integration_prefix, "cluster"
3870
4343
  )
3871
4344
  output_value = spec.cluster_name
3872
- tf_resources.append(Output(output_name_0_13, value=output_value))
4345
+ tf_resources.append(Output(output_name, value=output_value))
3873
4346
  # namespace
3874
- output_name_0_13 = output_format_0_13.format(
4347
+ output_name = output_format.format(
3875
4348
  spec.output_prefix, self.integration_prefix, "namespace"
3876
4349
  )
3877
4350
  output_value = spec.namespace_name
3878
- tf_resources.append(Output(output_name_0_13, value=output_value))
4351
+ tf_resources.append(Output(output_name, value=output_value))
3879
4352
  # resource
3880
- output_name_0_13 = output_format_0_13.format(
4353
+ output_name = output_format.format(
3881
4354
  spec.output_prefix, self.integration_prefix, "resource"
3882
4355
  )
3883
4356
  output_value = "Secret"
3884
- tf_resources.append(Output(output_name_0_13, value=output_value))
4357
+ tf_resources.append(Output(output_name, value=output_value))
3885
4358
  # output_resource_name
3886
- output_name_0_13 = output_format_0_13.format(
4359
+ output_name = output_format.format(
3887
4360
  spec.output_prefix, self.integration_prefix, "output_resource_name"
3888
4361
  )
3889
4362
  output_value = spec.output_resource_name
3890
- tf_resources.append(Output(output_name_0_13, value=output_value))
4363
+ tf_resources.append(Output(output_name, value=output_value))
3891
4364
  # annotations
3892
4365
  if spec.annotations():
3893
- output_name_0_13 = output_format_0_13.format(
4366
+ output_name = output_format.format(
3894
4367
  spec.output_prefix, self.integration_prefix, "annotations"
3895
4368
  )
3896
4369
  anno_json = json.dumps(spec.annotations()).encode("utf-8")
3897
4370
  output_value = base64.b64encode(anno_json).decode()
3898
- tf_resources.append(Output(output_name_0_13, value=output_value))
4371
+ tf_resources.append(Output(output_name, value=output_value))
3899
4372
 
3900
4373
  def prefetch_resources(self, schema) -> dict[str, dict[str, str]]:
3901
4374
  gqlapi = gql.get_api()
@@ -3909,7 +4382,7 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
3909
4382
  try:
3910
4383
  raw_values = gqlapi.get_resource(path)
3911
4384
  except gql.GqlGetResourceError as e:
3912
- raise FetchResourceError(str(e))
4385
+ raise FetchResourceError(str(e)) from e
3913
4386
  return raw_values
3914
4387
 
3915
4388
  def get_values(self, path: str) -> dict[str, Any]:
@@ -3919,7 +4392,7 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
3919
4392
  values.pop("$schema", None)
3920
4393
  except anymarkup.AnyMarkupError:
3921
4394
  e_msg = "Could not parse data. Skipping resource: {}"
3922
- raise FetchResourceError(e_msg.format(path))
4395
+ raise FetchResourceError(e_msg.format(path)) from None
3923
4396
  return values
3924
4397
 
3925
4398
  @staticmethod
@@ -3990,7 +4463,7 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
3990
4463
 
3991
4464
  def _get_elasticsearch_account_wide_resource_policy(
3992
4465
  self, account: str
3993
- ) -> Optional[aws_cloudwatch_log_resource_policy]:
4466
+ ) -> aws_cloudwatch_log_resource_policy | None:
3994
4467
  """
3995
4468
  https://docs.aws.amazon.com/opensearch-service/latest/developerguide/createdomain-configure-slow-logs.html
3996
4469
  CloudWatch Logs supports 10 resource policies per Region.
@@ -4065,13 +4538,11 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
4065
4538
  for t in ElasticSearchLogGroupType:
4066
4539
  log_type = t.value
4067
4540
  if log_type not in publish_log_types:
4068
- publishing_options.append(
4069
- {
4070
- "log_type": log_type,
4071
- "enabled": False,
4072
- "cloudwatch_log_group_arn": "",
4073
- }
4074
- )
4541
+ publishing_options.append({
4542
+ "log_type": log_type,
4543
+ "enabled": False,
4544
+ "cloudwatch_log_group_arn": "",
4545
+ })
4075
4546
  continue
4076
4547
 
4077
4548
  log_type_identifier = TerrascriptClient.elasticsearch_log_group_identifier(
@@ -4093,25 +4564,23 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
4093
4564
  arn = f"${{{log_group_tf_resource.arn}}}"
4094
4565
 
4095
4566
  # add arn to output
4096
- output_name_0_13 = (
4567
+ output_name = (
4097
4568
  f"{output_prefix}__cloudwatch_log_group_" f"{log_type.lower()}_arn"
4098
4569
  )
4099
4570
  output_value = arn
4100
- tf_resources.append(Output(output_name_0_13, value=output_value))
4571
+ tf_resources.append(Output(output_name, value=output_value))
4101
4572
 
4102
4573
  # add name to output
4103
- output_name_0_13 = (
4574
+ output_name = (
4104
4575
  f"{output_prefix}__cloudwatch_log_group_" f"{log_type.lower()}_name"
4105
4576
  )
4106
4577
  output_value = log_type_identifier
4107
- tf_resources.append(Output(output_name_0_13, value=output_value))
4108
- publishing_options.append(
4109
- {
4110
- "log_type": log_type,
4111
- "enabled": True,
4112
- "cloudwatch_log_group_arn": arn,
4113
- }
4114
- )
4578
+ tf_resources.append(Output(output_name, value=output_value))
4579
+ publishing_options.append({
4580
+ "log_type": log_type,
4581
+ "enabled": True,
4582
+ "cloudwatch_log_group_arn": arn,
4583
+ })
4115
4584
 
4116
4585
  return tf_resources, publishing_options
4117
4586
 
@@ -4281,6 +4750,24 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
4281
4750
  )
4282
4751
  cluster_vaules["warm_count"] = cluster_config.get("warm_count", 2)
4283
4752
 
4753
+ cold_storage_options = cluster_config.get("cold_storage_options", {})
4754
+ if cold_storage_options.get("enabled", False):
4755
+ if not dedicated_master_enabled:
4756
+ raise ElasticSearchResourceColdStorageError(
4757
+ f"[{account}] Cold storage can only be enabled when "
4758
+ + "dedicated_master_enabled is set to true for resource"
4759
+ f" {values['identifier']}"
4760
+ )
4761
+ if not warm_enabled:
4762
+ raise ElasticSearchResourceColdStorageError(
4763
+ f"[{account}] Cold storage can only be enabled when "
4764
+ + "warm_enabled is set to true for resource"
4765
+ f" {values['identifier']}"
4766
+ )
4767
+ cluster_vaules["cold_storage_options"] = {
4768
+ "enabled": True,
4769
+ }
4770
+
4284
4771
  es_values["cluster_config"] = cluster_vaules
4285
4772
 
4286
4773
  snapshot_options = values.get("snapshot_options", {})
@@ -4358,19 +4845,19 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
4358
4845
  auth_options = values.get("auth", {})
4359
4846
  # TODO: @fishi0x01 make mandatory after migration APPSRE-3409
4360
4847
  if auth_options:
4361
- es_values[
4362
- "advanced_security_options"
4363
- ] = self._build_es_advanced_security_options(auth_options)
4848
+ es_values["advanced_security_options"] = (
4849
+ self._build_es_advanced_security_options(auth_options)
4850
+ )
4364
4851
 
4365
4852
  # TODO: @fishi0x01 remove after migration APPSRE-3409
4366
4853
  # ++++++++ START: REMOVE +++++++++
4367
4854
  else:
4368
4855
  advanced_security_options = values.get("advanced_security_options", {})
4369
4856
  if advanced_security_options:
4370
- es_values[
4371
- "advanced_security_options"
4372
- ] = self._build_es_advanced_security_options_deprecated(
4373
- advanced_security_options
4857
+ es_values["advanced_security_options"] = (
4858
+ self._build_es_advanced_security_options_deprecated(
4859
+ advanced_security_options
4860
+ )
4374
4861
  )
4375
4862
  # ++++++++ END: REMOVE ++++++++++
4376
4863
 
@@ -4379,33 +4866,33 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
4379
4866
 
4380
4867
  # Setup outputs
4381
4868
  # arn
4382
- output_name_0_13 = output_prefix + "__arn"
4869
+ output_name = output_prefix + "__arn"
4383
4870
  output_value = "${" + es_tf_resource.arn + "}"
4384
- tf_resources.append(Output(output_name_0_13, value=output_value))
4871
+ tf_resources.append(Output(output_name, value=output_value))
4385
4872
  # domain_id
4386
- output_name_0_13 = output_prefix + "__domain_id"
4873
+ output_name = output_prefix + "__domain_id"
4387
4874
  output_value = "${" + es_tf_resource.domain_id + "}"
4388
- tf_resources.append(Output(output_name_0_13, value=output_value))
4875
+ tf_resources.append(Output(output_name, value=output_value))
4389
4876
  # domain_name
4390
- output_name_0_13 = output_prefix + "__domain_name"
4877
+ output_name = output_prefix + "__domain_name"
4391
4878
  output_value = es_tf_resource.domain_name
4392
- tf_resources.append(Output(output_name_0_13, value=output_value))
4879
+ tf_resources.append(Output(output_name, value=output_value))
4393
4880
  # endpoint
4394
- output_name_0_13 = output_prefix + "__endpoint"
4881
+ output_name = output_prefix + "__endpoint"
4395
4882
  output_value = "https://" + "${" + es_tf_resource.endpoint + "}"
4396
- tf_resources.append(Output(output_name_0_13, value=output_value))
4883
+ tf_resources.append(Output(output_name, value=output_value))
4397
4884
  # kibana_endpoint
4398
- output_name_0_13 = output_prefix + "__kibana_endpoint"
4885
+ output_name = output_prefix + "__kibana_endpoint"
4399
4886
  output_value = "https://" + "${" + es_tf_resource.kibana_endpoint + "}"
4400
- tf_resources.append(Output(output_name_0_13, value=output_value))
4887
+ tf_resources.append(Output(output_name, value=output_value))
4401
4888
  # vpc_id
4402
- output_name_0_13 = output_prefix + "__vpc_id"
4889
+ output_name = output_prefix + "__vpc_id"
4403
4890
  output_value = (
4404
4891
  "${aws_elasticsearch_domain." + identifier + ".vpc_options.0.vpc_id}"
4405
4892
  )
4406
- tf_resources.append(Output(output_name_0_13, value=output_value))
4893
+ tf_resources.append(Output(output_name, value=output_value))
4407
4894
  # add master user creds to output and secretsmanager if internal_user_database_enabled
4408
- security_options = es_values.get("advanced_security_options", None)
4895
+ security_options = es_values.get("advanced_security_options")
4409
4896
  if security_options and security_options.get(
4410
4897
  "internal_user_database_enabled", False
4411
4898
  ):
@@ -4454,20 +4941,20 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
4454
4941
  )
4455
4942
  tf_resources.append(iam_policy_resource)
4456
4943
 
4457
- output_name_0_13 = output_prefix + "__secret_name"
4944
+ output_name = output_prefix + "__secret_name"
4458
4945
  output_value = secret_name
4459
- tf_resources.append(Output(output_name_0_13, value=output_value))
4460
- output_name_0_13 = output_prefix + "__secret_policy_arn"
4946
+ tf_resources.append(Output(output_name, value=output_value))
4947
+ output_name = output_prefix + "__secret_policy_arn"
4461
4948
  output_value = "${" + iam_policy_resource.arn + "}"
4462
- tf_resources.append(Output(output_name_0_13, value=output_value))
4949
+ tf_resources.append(Output(output_name, value=output_value))
4463
4950
  # master_user_name
4464
- output_name_0_13 = output_prefix + "__master_user_name"
4951
+ output_name = output_prefix + "__master_user_name"
4465
4952
  output_value = master_user["master_user_name"]
4466
- tf_resources.append(Output(output_name_0_13, value=output_value))
4953
+ tf_resources.append(Output(output_name, value=output_value, sensitive=True))
4467
4954
  # master_user_password
4468
- output_name_0_13 = output_prefix + "__master_user_password"
4955
+ output_name = output_prefix + "__master_user_password"
4469
4956
  output_value = master_user["master_user_password"]
4470
- tf_resources.append(Output(output_name_0_13, value=output_value))
4957
+ tf_resources.append(Output(output_name, value=output_value, sensitive=True))
4471
4958
 
4472
4959
  self.add_resources(account, tf_resources)
4473
4960
 
@@ -4545,34 +5032,36 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
4545
5032
 
4546
5033
  # outputs
4547
5034
  # arn
4548
- output_name_0_13 = output_prefix + "__arn"
5035
+ output_name = output_prefix + "__arn"
4549
5036
  output_value = "${" + acm_tf_resource.arn + "}"
4550
- tf_resources.append(Output(output_name_0_13, value=output_value))
5037
+ tf_resources.append(Output(output_name, value=output_value))
4551
5038
  # domain name
4552
- # output_name_0_13 = output_prefix + '__domain_name'
5039
+ # output_name = output_prefix + '__domain_name'
4553
5040
  # output_value = '${' + acm_tf_resource.domain_name + '}'
4554
- # tf_resources.append(Output(output_name_0_13, value=output_value))
5041
+ # tf_resources.append(Output(output_name, value=output_value))
4555
5042
  # status
4556
- output_name_0_13 = output_prefix + "__status"
5043
+ output_name = output_prefix + "__status"
4557
5044
  output_value = "${" + acm_tf_resource.status + "}"
4558
- tf_resources.append(Output(output_name_0_13, value=output_value))
5045
+ tf_resources.append(Output(output_name, value=output_value))
4559
5046
  # domain_validation_options
4560
- output_name_0_13 = output_prefix + "__domain_validation_options"
5047
+ output_name = output_prefix + "__domain_validation_options"
4561
5048
  output_value = "${" + acm_tf_resource.domain_validation_options + "}"
4562
- tf_resources.append(Output(output_name_0_13, value=output_value))
5049
+ tf_resources.append(Output(output_name, value=output_value))
4563
5050
  if secret is not None:
4564
5051
  # key
4565
- output_name_0_13 = output_prefix + "__key"
5052
+ output_name = output_prefix + "__key"
4566
5053
  output_value = key
4567
- tf_resources.append(Output(output_name_0_13, value=output_value))
5054
+ tf_resources.append(Output(output_name, value=output_value))
4568
5055
  # certificate
4569
- output_name_0_13 = output_prefix + "__certificate"
5056
+ output_name = output_prefix + "__certificate"
4570
5057
  output_value = certificate
4571
- tf_resources.append(Output(output_name_0_13, value=output_value))
5058
+ tf_resources.append(Output(output_name, value=output_value, sensitive=True))
4572
5059
  if caCertificate is not None:
4573
- output_name_0_13 = output_prefix + "__caCertificate"
5060
+ output_name = output_prefix + "__caCertificate"
4574
5061
  output_value = caCertificate
4575
- tf_resources.append(Output(output_name_0_13, value=output_value))
5062
+ tf_resources.append(
5063
+ Output(output_name, value=output_value, sensitive=True)
5064
+ )
4576
5065
 
4577
5066
  self.add_resources(account, tf_resources)
4578
5067
 
@@ -4609,17 +5098,17 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
4609
5098
 
4610
5099
  # outputs
4611
5100
  # etag
4612
- output_name_0_13 = output_prefix + "_etag"
5101
+ output_name = output_prefix + "_etag"
4613
5102
  output_value = "${" + pk_tf_resource.etag + "}"
4614
- tf_resources.append(Output(output_name_0_13, value=output_value))
5103
+ tf_resources.append(Output(output_name, value=output_value))
4615
5104
  # id
4616
- output_name_0_13 = output_prefix + "__id"
5105
+ output_name = output_prefix + "__id"
4617
5106
  output_value = "${" + pk_tf_resource.id + "}"
4618
- tf_resources.append(Output(output_name_0_13, value=output_value))
5107
+ tf_resources.append(Output(output_name, value=output_value))
4619
5108
  # key
4620
- output_name_0_13 = output_prefix + "__key"
5109
+ output_name = output_prefix + "__key"
4621
5110
  output_value = key
4622
- tf_resources.append(Output(output_name_0_13, value=output_value))
5111
+ tf_resources.append(Output(output_name, value=output_value))
4623
5112
 
4624
5113
  self.add_resources(account, tf_resources)
4625
5114
 
@@ -4629,16 +5118,18 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
4629
5118
  account = self.accounts[account_name]
4630
5119
  cluster = namespace_info["cluster"]
4631
5120
  ocm = ocm_map.get(cluster["name"])
4632
- account[
4633
- "assume_role"
4634
- ] = ocm.get_aws_infrastructure_access_terraform_assume_role(
4635
- cluster["name"],
4636
- account["uid"],
4637
- account["terraformUsername"],
5121
+ account["assume_role"] = (
5122
+ ocm.get_aws_infrastructure_access_terraform_assume_role(
5123
+ cluster["name"],
5124
+ account["uid"],
5125
+ account["terraformUsername"],
5126
+ )
4638
5127
  )
4639
5128
  account["assume_region"] = cluster["spec"]["region"]
4640
5129
  service_name = f"{namespace_info['name']}/{openshift_service}"
4641
- with AWSApi(1, [account], settings=self.settings, init_users=False) as awsapi:
5130
+ with AWSApi(
5131
+ 1, [account], secret_reader=self.secret_reader, init_users=False
5132
+ ) as awsapi:
4642
5133
  ips = awsapi.get_alb_network_interface_ips(account, service_name)
4643
5134
  if not ips:
4644
5135
  raise ValueError(
@@ -4652,7 +5143,7 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
4652
5143
  def _get_alb_rule_condition_value(condition):
4653
5144
  condition_type = condition["type"]
4654
5145
  condition_type_key = SUPPORTED_ALB_LISTENER_RULE_CONDITION_TYPE_MAPPING.get(
4655
- condition_type, None
5146
+ condition_type
4656
5147
  )
4657
5148
  if condition_type_key is None:
4658
5149
  raise KeyError(f"unknown alb rule condition type {condition_type}")
@@ -4739,7 +5230,7 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
4739
5230
  tf_resources.append(sg_tf_resource)
4740
5231
 
4741
5232
  # https://www.terraform.io/docs/providers/aws/r/lb.html
4742
- values = {
5233
+ lb_values = {
4743
5234
  "provider": provider,
4744
5235
  "name": identifier,
4745
5236
  "internal": False,
@@ -4753,13 +5244,33 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
4753
5244
 
4754
5245
  idle_timeout = resource.get("idle_timeout")
4755
5246
  if idle_timeout:
4756
- values["idle_timeout"] = idle_timeout
5247
+ lb_values["idle_timeout"] = idle_timeout
4757
5248
 
4758
5249
  enable_http2 = resource.get("enable_http2")
4759
5250
  if enable_http2 is False:
4760
- values["enable_http2"] = False
5251
+ lb_values["enable_http2"] = False
5252
+
5253
+ if resource.get("access_logs"):
5254
+ # https://www.terraform.io/docs/providers/aws/r/lb.html#access_logs
5255
+ bucket_identifier = f"{identifier}-lb-access-logs"
5256
+ lb_access_logs_s3_bucket_values = {
5257
+ "provider": provider,
5258
+ "bucket": bucket_identifier,
5259
+ }
5260
+ lb_access_logs_s3_bucket_tf_resource = aws_s3_bucket(
5261
+ bucket_identifier, **lb_access_logs_s3_bucket_values
5262
+ )
5263
+ tf_resources.append(lb_access_logs_s3_bucket_tf_resource)
4761
5264
 
4762
- lb_tf_resource = aws_lb(identifier, **values)
5265
+ lb_values["access_logs"] = {
5266
+ "enabled": True,
5267
+ "bucket": f"${{{lb_access_logs_s3_bucket_tf_resource.id}}}",
5268
+ }
5269
+ lb_values["depends_on"].extend(
5270
+ self.get_dependencies([lb_access_logs_s3_bucket_tf_resource])
5271
+ )
5272
+
5273
+ lb_tf_resource = aws_lb(identifier, **lb_values)
4763
5274
  tf_resources.append(lb_tf_resource)
4764
5275
 
4765
5276
  default_target = None
@@ -4768,6 +5279,9 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
4768
5279
  target_name = t["name"]
4769
5280
  t_openshift_service = t.get("openshift_service")
4770
5281
  t_ips = t.get("ips")
5282
+ t_protocol = t.get("protocol") or "HTTPS"
5283
+ t_protocol_version = t.get("protocol_version") or "HTTP1"
5284
+
4771
5285
  if t_openshift_service:
4772
5286
  target_ips = self._get_alb_target_ips_by_openshift_service(
4773
5287
  identifier, t_openshift_service, account, namespace_info, ocm_map
@@ -4798,8 +5312,8 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
4798
5312
  "provider": provider,
4799
5313
  "name": f"{target_name}-${{{lbt_random_id.hex}}}",
4800
5314
  "port": 443,
4801
- "protocol": "HTTPS",
4802
- "protocol_version": "HTTP1",
5315
+ "protocol": t_protocol,
5316
+ "protocol_version": t_protocol_version,
4803
5317
  "target_type": "ip",
4804
5318
  "vpc_id": vpc_id,
4805
5319
  "health_check": {
@@ -4832,7 +5346,7 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
4832
5346
  "port": 443,
4833
5347
  "depends_on": self.get_dependencies([lbt_tf_resource]),
4834
5348
  }
4835
- if not ip_address(ip) in ip_network(vpc_cidr_block):
5349
+ if ip_address(ip) not in ip_network(vpc_cidr_block):
4836
5350
  values["availability_zone"] = "all"
4837
5351
  ip_slug = ip.replace(".", "_")
4838
5352
  lbta_identifier = f"{lbt_identifier}-{ip_slug}"
@@ -4864,12 +5378,13 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
4864
5378
  # forward
4865
5379
  if not default_target:
4866
5380
  raise KeyError("expected a single default target")
5381
+ ssl_policy = resource.get("ssl_policy") or "ELBSecurityPolicy-TLS13-1-0-2021-06"
4867
5382
  values = {
4868
5383
  "provider": provider,
4869
5384
  "load_balancer_arn": f"${{{lb_tf_resource.arn}}}",
4870
5385
  "port": 443,
4871
5386
  "protocol": "HTTPS",
4872
- "ssl_policy": "ELBSecurityPolicy-TLS-1-2-2017-01",
5387
+ "ssl_policy": ssl_policy,
4873
5388
  "certificate_arn": resource["certificate_arn"],
4874
5389
  "default_action": {
4875
5390
  "type": "forward",
@@ -4907,9 +5422,10 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
4907
5422
 
4908
5423
  target_resource = valid_targets[target_name]
4909
5424
 
4910
- action_values["forward"]["target_group"].append(
4911
- {"arn": f"${{{target_resource.arn}}}", "weight": a["weight"]}
4912
- )
5425
+ action_values["forward"]["target_group"].append({
5426
+ "arn": f"${{{target_resource.arn}}}",
5427
+ "weight": a["weight"],
5428
+ })
4913
5429
  weight_sum += a["weight"]
4914
5430
  if weight_sum != 100:
4915
5431
  raise ValueError(
@@ -4926,6 +5442,19 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
4926
5442
  "status_code": fr_data.get("status_code"),
4927
5443
  },
4928
5444
  }
5445
+ elif action_type == "redirect":
5446
+ redirect_data = action.get("redirect", {})
5447
+ action_values = {
5448
+ "type": "redirect",
5449
+ "redirect": {
5450
+ "host": redirect_data.get("host"),
5451
+ "path": redirect_data.get("path"),
5452
+ "port": redirect_data.get("port"),
5453
+ "protocol": redirect_data.get("protocol"),
5454
+ "query": redirect_data.get("query"),
5455
+ "status_code": redirect_data.get("status_code"),
5456
+ },
5457
+ }
4929
5458
  else:
4930
5459
  raise KeyError(f"unknown alb rule action type {action_type}")
4931
5460
 
@@ -4941,20 +5470,20 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
4941
5470
  "depends_on": self.get_dependencies([forward_lbl_tf_resource]),
4942
5471
  }
4943
5472
 
4944
- lblr_identifier = f"{identifier}-rule-{rule_num+1:02d}"
5473
+ lblr_identifier = f"{identifier}-rule-{rule_num + 1:02d}"
4945
5474
  lblr_tf_resource = aws_lb_listener_rule(lblr_identifier, **values)
4946
5475
 
4947
5476
  tf_resources.append(lblr_tf_resource)
4948
5477
 
4949
5478
  # outputs
4950
5479
  # dns name
4951
- output_name_0_13 = output_prefix + "__dns_name"
5480
+ output_name = output_prefix + "__dns_name"
4952
5481
  output_value = f"${{{lb_tf_resource.dns_name}}}"
4953
- tf_resources.append(Output(output_name_0_13, value=output_value))
5482
+ tf_resources.append(Output(output_name, value=output_value))
4954
5483
  # vpc cidr block
4955
- output_name_0_13 = output_prefix + "__vpc_cidr_block"
5484
+ output_name = output_prefix + "__vpc_cidr_block"
4956
5485
  output_value = vpc_cidr_block
4957
- tf_resources.append(Output(output_name_0_13, value=output_value))
5486
+ tf_resources.append(Output(output_name, value=output_value))
4958
5487
 
4959
5488
  self.add_resources(account, tf_resources)
4960
5489
 
@@ -4993,12 +5522,12 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
4993
5522
  tf_resources.append(aws_version_resource)
4994
5523
 
4995
5524
  # outputs
4996
- output_name_0_13 = output_prefix + "__arn"
5525
+ output_name = output_prefix + "__arn"
4997
5526
  output_value = "${" + aws_version_resource.arn + "}"
4998
- tf_resources.append(Output(output_name_0_13, value=output_value))
4999
- output_name_0_13 = output_prefix + "__version_id"
5527
+ tf_resources.append(Output(output_name, value=output_value))
5528
+ output_name = output_prefix + "__version_id"
5000
5529
  output_value = "${" + aws_version_resource.version_id + "}"
5001
- tf_resources.append(Output(output_name_0_13, value=output_value))
5530
+ tf_resources.append(Output(output_name, value=output_value))
5002
5531
 
5003
5532
  self.add_resources(account, tf_resources)
5004
5533
 
@@ -5021,14 +5550,14 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
5021
5550
  if "gitlab" in url:
5022
5551
  gitlab = self.init_gitlab()
5023
5552
  project = gitlab.get_project(url)
5024
- commits = project.commits.list(ref_name=ref)
5553
+ commits = project.commits.list(ref_name=ref, per_page=1, page=1)
5025
5554
  return commits[0].id
5026
5555
 
5027
5556
  return ""
5028
5557
 
5029
5558
  def get_asg_image_id(
5030
5559
  self, filters: Iterable[Mapping[str, Any]], account: str, region: str
5031
- ) -> Optional[str]:
5560
+ ) -> str | None:
5032
5561
  """
5033
5562
  AMI ID comes form AWS Api filter result.
5034
5563
  AMI needs to be shared by integration aws-ami-share.
@@ -5045,7 +5574,9 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
5045
5574
 
5046
5575
  # Get the most recent AMI id
5047
5576
  aws_account = self.accounts[account]
5048
- with AWSApi(1, [aws_account], settings=self.settings, init_users=False) as aws:
5577
+ with AWSApi(
5578
+ 1, [aws_account], secret_reader=self.secret_reader, init_users=False
5579
+ ) as aws:
5049
5580
  return aws.get_image_id(account, region, tags)
5050
5581
 
5051
5582
  def _use_previous_image_id(self, filters: Iterable[Mapping[str, Any]]) -> bool:
@@ -5127,23 +5658,19 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
5127
5658
  part = []
5128
5659
  for c in cloudinit_configs:
5129
5660
  raw = self.get_raw_values(c["content"])
5130
- content = orb.process_extracurlyjinja2_template(
5131
- body=raw["content"], vars=vars, settings=self.settings
5661
+ content = process_extracurlyjinja2_template(
5662
+ body=raw["content"], vars=vars, secret_reader=self.secret_reader
5132
5663
  )
5133
5664
  # https://www.terraform.io/docs/language/expressions/strings.html#escape-sequences
5134
5665
  content = content.replace("${", "$${")
5135
5666
  content = content.replace("%{", "%%{")
5136
- part.append(
5137
- {
5138
- "filename": c.get("filename"),
5139
- "content_type": c.get("content_type"),
5140
- "content": content,
5141
- }
5142
- )
5667
+ part.append({
5668
+ "filename": c.get("filename"),
5669
+ "content_type": c.get("content_type"),
5670
+ "content": content,
5671
+ })
5143
5672
  cloudinit_value = {"gzip": True, "base64_encode": True, "part": part}
5144
- cloudinit_data = data.template_cloudinit_config(
5145
- identifier, **cloudinit_value
5146
- )
5673
+ cloudinit_data = cloudinit_config(identifier, **cloudinit_value)
5147
5674
  tf_resources.append(cloudinit_data)
5148
5675
  template_values["user_data"] = "${" + cloudinit_data.rendered + "}"
5149
5676
  template_resource = aws_launch_template(identifier, **template_values)
@@ -5172,25 +5699,36 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
5172
5699
  "preferences": common_values.get("instance_refresh_preferences"),
5173
5700
  },
5174
5701
  }
5702
+
5703
+ override = []
5175
5704
  instance_types = common_values.get("instance_types")
5176
5705
  if instance_types:
5177
- override = [{"instance_type": i} for i in instance_types]
5178
- asg_value["mixed_instances_policy"]["launch_template"][
5179
- "override"
5180
- ] = override
5181
- asg_value["tags"] = [
5706
+ override += [{"instance_type": i} for i in instance_types]
5707
+ instance_requirements = common_values.get("instance_requirements")
5708
+ if instance_requirements:
5709
+ override += [{"instance_requirements": instance_requirements}]
5710
+ if override:
5711
+ asg_value["mixed_instances_policy"]["launch_template"]["override"] = (
5712
+ override
5713
+ )
5714
+
5715
+ tags = [
5182
5716
  {"key": k, "value": v, "propagate_at_launch": True} for k, v in tags.items()
5183
5717
  ]
5718
+ if self.versions.get(account, "").startswith("3"):
5719
+ asg_value["tags"] = tags
5720
+ else:
5721
+ asg_value["tag"] = tags
5184
5722
  asg_resource = aws_autoscaling_group(identifier, **asg_value)
5185
5723
  tf_resources.append(asg_resource)
5186
5724
 
5187
5725
  # outputs
5188
- output_name_0_13 = output_prefix + "__template_latest_version"
5726
+ output_name = output_prefix + "__template_latest_version"
5189
5727
  output_value = "${" + template_resource.latest_version + "}"
5190
- tf_resources.append(Output(output_name_0_13, value=output_value))
5191
- output_name_0_13 = output_prefix + "__image_id"
5728
+ tf_resources.append(Output(output_name, value=output_value))
5729
+ output_name = output_prefix + "__image_id"
5192
5730
  output_value = image_id
5193
- tf_resources.append(Output(output_name_0_13, value=output_value))
5731
+ tf_resources.append(Output(output_name, value=output_value))
5194
5732
 
5195
5733
  self.add_resources(account, tf_resources)
5196
5734
 
@@ -5257,7 +5795,7 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
5257
5795
  lambda_managed_policy_arn = (
5258
5796
  "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
5259
5797
  )
5260
- if region in ("us-gov-west-1", "us-gov-east-1"):
5798
+ if region in {"us-gov-west-1", "us-gov-east-1"}:
5261
5799
  lambda_managed_policy_arn = "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
5262
5800
  vpc_id = common_values.get("vpc_id")
5263
5801
  subnet_ids = common_values.get("subnet_ids")
@@ -5333,8 +5871,8 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
5333
5871
  s3_client.head_bucket(Bucket=bucket_name)
5334
5872
  except ClientError as details:
5335
5873
  raise StateInaccessibleException(
5336
- f"Bucket {bucket_name} is not accessible - {str(details)}"
5337
- )
5874
+ f"Bucket {bucket_name} is not accessible - {details!s}"
5875
+ ) from None
5338
5876
 
5339
5877
  # todo: probably remove 'RedHat' from the object/variable/filepath
5340
5878
  # names to keep the code RedHat-agnostic?
@@ -5352,8 +5890,8 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
5352
5890
  f"from s3 bucket {bucket_name} to local filepath {redhat_logo_png_filepath},"
5353
5891
  f" but no file exists locally at this path!"
5354
5892
  )
5355
- file_type = imghdr.what(redhat_logo_png_filepath)
5356
- if file_type != "png":
5893
+ file_type = filetype.guess(redhat_logo_png_filepath)
5894
+ if file_type.extension != "png":
5357
5895
  raise Exception(
5358
5896
  f"Attempted to download object {redhat_logo_png_obj_name} "
5359
5897
  f"from s3 bucket {bucket_name} to local filepath {redhat_logo_png_filepath}."
@@ -5650,7 +6188,7 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
5650
6188
  api_gateway_vpc_link_resource = aws_api_gateway_vpc_link(
5651
6189
  "vpc_link",
5652
6190
  name=f"{identifier}-vpc-link",
5653
- target_arns=[openshift_ingress_load_balancer_arn]
6191
+ target_arns=[openshift_ingress_load_balancer_arn],
5654
6192
  # future: use data source to get vpc arn by annotation
5655
6193
  )
5656
6194
  tf_resources.append(api_gateway_vpc_link_resource)
@@ -5877,26 +6415,24 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
5877
6415
  )
5878
6416
  tf_resources.append(api_gateway_stage_resource)
5879
6417
 
5880
- rest_api_policy = json.dumps(
5881
- {
5882
- "Version": "2012-10-17",
5883
- "Statement": [
5884
- {
5885
- "Effect": "Allow",
5886
- "Principal": "*",
5887
- "Action": "execute-api:Invoke",
5888
- "Resource": "${aws_api_gateway_rest_api.gw_api.execution_arn}/*",
5889
- },
5890
- {
5891
- "Effect": "Deny",
5892
- "Principal": "*",
5893
- "Action": "execute-api:Invoke",
5894
- "Resource": "${aws_api_gateway_rest_api.gw_api.execution_arn}/*",
5895
- "Condition": {"StringNotEquals": {"aws:SourceVpce": vpce_id}},
5896
- },
5897
- ],
5898
- }
5899
- )
6418
+ rest_api_policy = json.dumps({
6419
+ "Version": "2012-10-17",
6420
+ "Statement": [
6421
+ {
6422
+ "Effect": "Allow",
6423
+ "Principal": "*",
6424
+ "Action": "execute-api:Invoke",
6425
+ "Resource": "${aws_api_gateway_rest_api.gw_api.execution_arn}/*",
6426
+ },
6427
+ {
6428
+ "Effect": "Deny",
6429
+ "Principal": "*",
6430
+ "Action": "execute-api:Invoke",
6431
+ "Resource": "${aws_api_gateway_rest_api.gw_api.execution_arn}/*",
6432
+ "Condition": {"StringNotEquals": {"aws:SourceVpce": vpce_id}},
6433
+ },
6434
+ ],
6435
+ })
5900
6436
 
5901
6437
  # REST API POLICY
5902
6438
  api_gateway_rest_api_policy_resource = aws_api_gateway_rest_api_policy(
@@ -5965,6 +6501,7 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
5965
6501
  "${aws_cloudwatch_log_group.waf_log_group.arn}"
5966
6502
  ],
5967
6503
  resource_arn="${aws_wafv2_web_acl.api_waf.arn}",
6504
+ redacted_fields={"single_header": {"name": "authorization"}},
5968
6505
  )
5969
6506
  )
5970
6507
 
@@ -6120,3 +6657,218 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
6120
6657
  tf_resources.append(aws_vpc_endpoint_subnet_association_resource)
6121
6658
 
6122
6659
  self.add_resources(account, tf_resources)
6660
+
6661
+ def populate_tf_resource_msk(self, spec):
6662
+ account = spec.provisioner_name
6663
+ values = self.init_values(spec)
6664
+ output_prefix = spec.output_prefix
6665
+ tf_resources = []
6666
+ resource_id = spec.identifier
6667
+
6668
+ del values["identifier"]
6669
+ values.setdefault("cluster_name", spec.identifier)
6670
+
6671
+ # common
6672
+ self.init_common_outputs(tf_resources, spec)
6673
+
6674
+ # validations
6675
+ if (
6676
+ values["number_of_broker_nodes"]
6677
+ % len(values["broker_node_group_info"]["client_subnets"])
6678
+ != 0
6679
+ ):
6680
+ raise ValueError(
6681
+ "number_of_broker_nodes must be a multiple of the number of specified client subnets."
6682
+ )
6683
+
6684
+ scram_enabled = (
6685
+ values.get("client_authentication", {}).get("sasl", {}).get("scram", False)
6686
+ )
6687
+ scram_users = {}
6688
+ if scram_enabled:
6689
+ if not spec.resource.get("users", []):
6690
+ raise ValueError(
6691
+ "users attribute must be given when client_authentication.sasl.scram is enabled."
6692
+ )
6693
+ scram_users = {
6694
+ user["name"]: self.secret_reader.read_all(user["secret"])
6695
+ for user in spec.resource["users"]
6696
+ }
6697
+ # validate user objects
6698
+ for user, secret in scram_users.items():
6699
+ if secret.keys() != {"password", "username"}:
6700
+ raise ValueError(
6701
+ f"MSK user '{user}' secret must contain only 'username' and 'password' keys!"
6702
+ )
6703
+
6704
+ # resource - msk config
6705
+ # unique msk config resource name enables "create_before_destroy" lifecycle
6706
+ # which is required when changing version which requires a resource replacement
6707
+ msk_version_str = values["kafka_version"].replace(".", "-")
6708
+ msk_config_name = f"{resource_id}-{msk_version_str}"
6709
+ msk_config = aws_msk_configuration(
6710
+ msk_config_name,
6711
+ name=msk_config_name,
6712
+ kafka_versions=[values["kafka_version"]],
6713
+ server_properties=values["server_properties"],
6714
+ # lifecycle create_before_destroy is required to ensure that the config is created
6715
+ # before it is assigned to the cluster
6716
+ lifecycle={
6717
+ "create_before_destroy": True,
6718
+ },
6719
+ )
6720
+ tf_resources.append(msk_config)
6721
+ values.pop("server_properties", None)
6722
+
6723
+ # resource - cluster
6724
+ values["configuration_info"] = {
6725
+ "arn": "${" + msk_config.arn + "}",
6726
+ "revision": "${" + msk_config.latest_revision + "}",
6727
+ }
6728
+ msk_cluster = aws_msk_cluster(resource_id, **values)
6729
+ tf_resources.append(msk_cluster)
6730
+
6731
+ # resource - cloudwatch
6732
+ if (
6733
+ values.get("logging_info", {})
6734
+ .get("broker_logs", {})
6735
+ .get("cloudwatch_logs", {})
6736
+ .get("enabled", False)
6737
+ ):
6738
+ log_group_values = {
6739
+ "name": f"{resource_id}-msk-broker-logs",
6740
+ "tags": values["tags"],
6741
+ "retention_in_days": values["logging_info"]["broker_logs"][
6742
+ "cloudwatch_logs"
6743
+ ]["retention_in_days"],
6744
+ }
6745
+ log_group_tf_resource = aws_cloudwatch_log_group(
6746
+ resource_id, **log_group_values
6747
+ )
6748
+ tf_resources.append(log_group_tf_resource)
6749
+ del values["logging_info"]["broker_logs"]["cloudwatch_logs"][
6750
+ "retention_in_days"
6751
+ ]
6752
+ values["logging_info"]["broker_logs"]["cloudwatch_logs"]["log_group"] = (
6753
+ log_group_tf_resource.name
6754
+ )
6755
+
6756
+ # resource - secret manager for SCRAM client credentials
6757
+ if scram_enabled and scram_users:
6758
+ scram_secrets: list[
6759
+ tuple[aws_secretsmanager_secret, aws_secretsmanager_secret_version]
6760
+ ] = []
6761
+
6762
+ # kms
6763
+ kms_values = {
6764
+ "description": "KMS key for MSK SCRAM credentials",
6765
+ "tags": values["tags"],
6766
+ }
6767
+ kms_key = aws_kms_key(resource_id, **kms_values)
6768
+ tf_resources.append(kms_key)
6769
+
6770
+ kms_key_alias = aws_kms_alias(
6771
+ resource_id,
6772
+ name=f"alias/{resource_id}-msk-scram",
6773
+ target_key_id="${" + kms_key.arn + "}",
6774
+ )
6775
+ tf_resources.append(kms_key_alias)
6776
+
6777
+ for user, secret in scram_users.items():
6778
+ secret_identifier = f"AmazonMSK_{resource_id}-{user}"
6779
+
6780
+ secret_values = {
6781
+ "name": secret_identifier,
6782
+ "tags": values["tags"],
6783
+ "kms_key_id": "${" + kms_key.arn + "}",
6784
+ }
6785
+ secret_resource = aws_secretsmanager_secret(
6786
+ secret_identifier, **secret_values
6787
+ )
6788
+ tf_resources.append(secret_resource)
6789
+
6790
+ version_values = {
6791
+ "secret_id": "${" + secret_resource.arn + "}",
6792
+ "secret_string": json.dumps(secret, sort_keys=True),
6793
+ }
6794
+ version_resource = aws_secretsmanager_secret_version(
6795
+ secret_identifier, **version_values
6796
+ )
6797
+ tf_resources.append(version_resource)
6798
+
6799
+ secret_policy_values = {
6800
+ "secret_arn": "${" + secret_resource.arn + "}",
6801
+ "policy": json.dumps({
6802
+ "Version": "2012-10-17",
6803
+ "Statement": [
6804
+ {
6805
+ "Sid": "AWSKafkaResourcePolicy",
6806
+ "Effect": "Allow",
6807
+ "Principal": {"Service": "kafka.amazonaws.com"},
6808
+ "Action": "secretsmanager:getSecretValue",
6809
+ "Resource": "${" + secret_resource.arn + "}",
6810
+ }
6811
+ ],
6812
+ }),
6813
+ }
6814
+ secret_policy = aws_secretsmanager_secret_policy(
6815
+ secret_identifier, **secret_policy_values
6816
+ )
6817
+ tf_resources.append(secret_policy)
6818
+ scram_secrets.append((secret_resource, version_resource))
6819
+
6820
+ # create ONE scram secret association for each secret created above
6821
+ scram_secret_association_values = {
6822
+ "cluster_arn": "${" + msk_cluster.arn + "}",
6823
+ "secret_arn_list": ["${" + s.arn + "}" for s, _ in scram_secrets],
6824
+ "depends_on": self.get_dependencies([v for _, v in scram_secrets]),
6825
+ }
6826
+ scram_secret_association = aws_msk_scram_secret_association(
6827
+ resource_id, **scram_secret_association_values
6828
+ )
6829
+ tf_resources.append(scram_secret_association)
6830
+
6831
+ # outputs
6832
+ tf_resources.append(
6833
+ Output(
6834
+ output_prefix + "__zookeeper_connect_string",
6835
+ value="${" + msk_cluster.zookeeper_connect_string + "}",
6836
+ )
6837
+ )
6838
+ tf_resources.append(
6839
+ Output(
6840
+ output_prefix + "__zookeeper_connect_string_tls",
6841
+ value="${" + msk_cluster.zookeeper_connect_string_tls + "}",
6842
+ )
6843
+ )
6844
+ tf_resources.append(
6845
+ Output(
6846
+ output_prefix + "__bootstrap_brokers",
6847
+ value="${" + msk_cluster.bootstrap_brokers + "}",
6848
+ )
6849
+ )
6850
+ tf_resources.append(
6851
+ Output(
6852
+ output_prefix + "__bootstrap_brokers_tls",
6853
+ value="${" + msk_cluster.bootstrap_brokers_tls + "}",
6854
+ )
6855
+ )
6856
+ tf_resources.append(
6857
+ Output(
6858
+ output_prefix + "__bootstrap_brokers_sasl_iam",
6859
+ value="${" + msk_cluster.bootstrap_brokers_sasl_iam + "}",
6860
+ )
6861
+ )
6862
+ tf_resources.append(
6863
+ Output(
6864
+ output_prefix + "__bootstrap_brokers_sasl_scram",
6865
+ value="${" + msk_cluster.bootstrap_brokers_sasl_scram + "}",
6866
+ )
6867
+ )
6868
+ self.add_resources(account, tf_resources)
6869
+
6870
+ def populate_saml_idp(self, account_name: str, name: str, metadata: str) -> None:
6871
+ saml_idp = aws_iam_saml_provider(
6872
+ f"{account_name}-{name}", name=name, saml_metadata_document=metadata
6873
+ )
6874
+ self.add_resource(account_name, saml_idp)