qontract-reconcile 0.10.2.dev334__py3-none-any.whl → 0.10.2.dev439__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.

Potentially problematic release.


This version of qontract-reconcile might be problematic. Click here for more details.

Files changed (370) hide show
  1. {qontract_reconcile-0.10.2.dev334.dist-info → qontract_reconcile-0.10.2.dev439.dist-info}/METADATA +13 -12
  2. {qontract_reconcile-0.10.2.dev334.dist-info → qontract_reconcile-0.10.2.dev439.dist-info}/RECORD +366 -361
  3. reconcile/acs_rbac.py +2 -2
  4. reconcile/aus/advanced_upgrade_service.py +18 -12
  5. reconcile/aus/base.py +134 -32
  6. reconcile/aus/cluster_version_data.py +15 -5
  7. reconcile/aus/models.py +3 -1
  8. reconcile/aus/ocm_addons_upgrade_scheduler_org.py +1 -0
  9. reconcile/aus/ocm_upgrade_scheduler.py +8 -1
  10. reconcile/aus/ocm_upgrade_scheduler_org.py +20 -5
  11. reconcile/aus/version_gates/sts_version_gate_handler.py +54 -1
  12. reconcile/automated_actions/config/integration.py +16 -4
  13. reconcile/aws_account_manager/integration.py +8 -8
  14. reconcile/aws_account_manager/reconciler.py +3 -3
  15. reconcile/aws_ami_cleanup/integration.py +8 -12
  16. reconcile/aws_ami_share.py +69 -62
  17. reconcile/aws_cloudwatch_log_retention/integration.py +155 -126
  18. reconcile/aws_ecr_image_pull_secrets.py +3 -3
  19. reconcile/aws_iam_keys.py +1 -0
  20. reconcile/aws_saml_idp/integration.py +12 -4
  21. reconcile/aws_saml_roles/integration.py +32 -25
  22. reconcile/aws_version_sync/integration.py +6 -12
  23. reconcile/change_owners/bundle.py +3 -3
  24. reconcile/change_owners/change_log_tracking.py +3 -2
  25. reconcile/change_owners/change_owners.py +1 -1
  26. reconcile/change_owners/diff.py +2 -4
  27. reconcile/checkpoint.py +11 -3
  28. reconcile/cli.py +111 -18
  29. reconcile/dashdotdb_dora.py +5 -12
  30. reconcile/dashdotdb_slo.py +1 -1
  31. reconcile/database_access_manager.py +123 -117
  32. reconcile/dynatrace_token_provider/integration.py +1 -1
  33. reconcile/endpoints_discovery/integration.py +4 -1
  34. reconcile/endpoints_discovery/merge_request.py +1 -1
  35. reconcile/endpoints_discovery/merge_request_manager.py +9 -11
  36. reconcile/external_resources/factories.py +5 -12
  37. reconcile/external_resources/integration.py +1 -1
  38. reconcile/external_resources/manager.py +8 -5
  39. reconcile/external_resources/meta.py +0 -1
  40. reconcile/external_resources/metrics.py +1 -1
  41. reconcile/external_resources/model.py +20 -20
  42. reconcile/external_resources/reconciler.py +7 -4
  43. reconcile/external_resources/secrets_sync.py +8 -11
  44. reconcile/external_resources/state.py +26 -16
  45. reconcile/fleet_labeler/integration.py +1 -1
  46. reconcile/gabi_authorized_users.py +8 -5
  47. reconcile/gcp_image_mirror.py +2 -2
  48. reconcile/github_org.py +1 -1
  49. reconcile/github_owners.py +4 -0
  50. reconcile/gitlab_housekeeping.py +13 -15
  51. reconcile/gitlab_members.py +6 -12
  52. reconcile/gitlab_mr_sqs_consumer.py +2 -2
  53. reconcile/gitlab_owners.py +15 -11
  54. reconcile/gitlab_permissions.py +8 -12
  55. reconcile/glitchtip_project_alerts/integration.py +3 -1
  56. reconcile/gql_definitions/acs/acs_instances.py +10 -10
  57. reconcile/gql_definitions/acs/acs_policies.py +5 -5
  58. reconcile/gql_definitions/acs/acs_rbac.py +6 -6
  59. reconcile/gql_definitions/advanced_upgrade_service/aus_clusters.py +32 -32
  60. reconcile/gql_definitions/advanced_upgrade_service/aus_organization.py +26 -26
  61. reconcile/gql_definitions/app_interface_metrics_exporter/onboarding_status.py +6 -7
  62. reconcile/gql_definitions/app_sre_tekton_access_revalidation/roles.py +5 -5
  63. reconcile/gql_definitions/app_sre_tekton_access_revalidation/users.py +5 -5
  64. reconcile/gql_definitions/automated_actions/instance.py +51 -12
  65. reconcile/gql_definitions/aws_account_manager/aws_accounts.py +11 -11
  66. reconcile/gql_definitions/aws_ami_cleanup/aws_accounts.py +20 -10
  67. reconcile/gql_definitions/aws_cloudwatch_log_retention/aws_accounts.py +28 -68
  68. reconcile/gql_definitions/aws_saml_idp/aws_accounts.py +20 -10
  69. reconcile/gql_definitions/aws_saml_roles/aws_accounts.py +20 -10
  70. reconcile/gql_definitions/aws_saml_roles/roles.py +5 -5
  71. reconcile/gql_definitions/aws_version_sync/clusters.py +10 -10
  72. reconcile/gql_definitions/aws_version_sync/namespaces.py +5 -5
  73. reconcile/gql_definitions/change_owners/queries/change_types.py +5 -5
  74. reconcile/gql_definitions/change_owners/queries/self_service_roles.py +9 -9
  75. reconcile/gql_definitions/cluster_auth_rhidp/clusters.py +18 -18
  76. reconcile/gql_definitions/common/alerting_services_settings.py +9 -9
  77. reconcile/gql_definitions/common/app_code_component_repos.py +5 -5
  78. reconcile/gql_definitions/common/app_interface_custom_messages.py +5 -5
  79. reconcile/gql_definitions/common/app_interface_dms_settings.py +5 -5
  80. reconcile/gql_definitions/common/app_interface_repo_settings.py +5 -5
  81. reconcile/gql_definitions/common/app_interface_roles.py +120 -0
  82. reconcile/gql_definitions/common/app_interface_state_settings.py +10 -10
  83. reconcile/gql_definitions/common/app_interface_vault_settings.py +5 -5
  84. reconcile/gql_definitions/common/app_quay_repos_escalation_policies.py +5 -5
  85. reconcile/gql_definitions/common/apps.py +5 -5
  86. reconcile/gql_definitions/common/aws_vpc_requests.py +22 -9
  87. reconcile/gql_definitions/common/aws_vpcs.py +11 -11
  88. reconcile/gql_definitions/common/clusters.py +37 -35
  89. reconcile/gql_definitions/common/clusters_minimal.py +14 -14
  90. reconcile/gql_definitions/common/clusters_with_dms.py +6 -6
  91. reconcile/gql_definitions/common/clusters_with_peering.py +29 -30
  92. reconcile/gql_definitions/common/github_orgs.py +10 -10
  93. reconcile/gql_definitions/common/jira_settings.py +10 -10
  94. reconcile/gql_definitions/common/jiralert_settings.py +5 -5
  95. reconcile/gql_definitions/common/ldap_settings.py +5 -5
  96. reconcile/gql_definitions/common/namespaces.py +42 -44
  97. reconcile/gql_definitions/common/namespaces_minimal.py +15 -13
  98. reconcile/gql_definitions/common/ocm_env_telemeter.py +12 -12
  99. reconcile/gql_definitions/common/ocm_environments.py +19 -19
  100. reconcile/gql_definitions/common/pagerduty_instances.py +9 -9
  101. reconcile/gql_definitions/common/pgp_reencryption_settings.py +6 -6
  102. reconcile/gql_definitions/common/pipeline_providers.py +29 -29
  103. reconcile/gql_definitions/common/quay_instances.py +5 -5
  104. reconcile/gql_definitions/common/quay_orgs.py +5 -5
  105. reconcile/gql_definitions/common/reserved_networks.py +5 -5
  106. reconcile/gql_definitions/common/rhcs_provider_settings.py +5 -5
  107. reconcile/gql_definitions/common/saas_files.py +44 -44
  108. reconcile/gql_definitions/common/saas_target_namespaces.py +10 -10
  109. reconcile/gql_definitions/common/saasherder_settings.py +5 -5
  110. reconcile/gql_definitions/common/slack_workspaces.py +5 -5
  111. reconcile/gql_definitions/common/smtp_client_settings.py +19 -19
  112. reconcile/gql_definitions/common/state_aws_account.py +7 -8
  113. reconcile/gql_definitions/common/users.py +5 -5
  114. reconcile/gql_definitions/common/users_with_paths.py +5 -5
  115. reconcile/gql_definitions/cost_report/app_names.py +5 -5
  116. reconcile/gql_definitions/cost_report/cost_namespaces.py +5 -5
  117. reconcile/gql_definitions/cost_report/settings.py +9 -9
  118. reconcile/gql_definitions/dashdotdb_slo/slo_documents_query.py +43 -43
  119. reconcile/gql_definitions/dynatrace_token_provider/dynatrace_bootstrap_tokens.py +10 -10
  120. reconcile/gql_definitions/dynatrace_token_provider/token_specs.py +5 -5
  121. reconcile/gql_definitions/email_sender/apps.py +5 -5
  122. reconcile/gql_definitions/email_sender/emails.py +8 -8
  123. reconcile/gql_definitions/email_sender/users.py +6 -6
  124. reconcile/gql_definitions/endpoints_discovery/apps.py +10 -10
  125. reconcile/gql_definitions/external_resources/aws_accounts.py +9 -9
  126. reconcile/gql_definitions/external_resources/external_resources_modules.py +23 -23
  127. reconcile/gql_definitions/external_resources/external_resources_namespaces.py +494 -410
  128. reconcile/gql_definitions/external_resources/external_resources_settings.py +28 -26
  129. reconcile/gql_definitions/external_resources/fragments/external_resources_module_overrides.py +5 -5
  130. reconcile/gql_definitions/fleet_labeler/fleet_labels.py +40 -40
  131. reconcile/gql_definitions/fragments/aus_organization.py +5 -5
  132. reconcile/gql_definitions/fragments/aws_account_common.py +7 -5
  133. reconcile/gql_definitions/fragments/aws_account_managed.py +5 -5
  134. reconcile/gql_definitions/fragments/aws_account_sso.py +5 -5
  135. reconcile/gql_definitions/fragments/aws_infra_management_account.py +5 -5
  136. reconcile/gql_definitions/fragments/{aws_vpc_request_subnet.py → aws_organization.py} +12 -8
  137. reconcile/gql_definitions/fragments/aws_vpc.py +5 -5
  138. reconcile/gql_definitions/fragments/aws_vpc_request.py +12 -5
  139. reconcile/gql_definitions/fragments/container_image_mirror.py +5 -5
  140. reconcile/gql_definitions/fragments/deploy_resources.py +5 -5
  141. reconcile/gql_definitions/fragments/disable.py +5 -5
  142. reconcile/gql_definitions/fragments/email_service.py +5 -5
  143. reconcile/gql_definitions/fragments/email_user.py +5 -5
  144. reconcile/gql_definitions/fragments/jumphost_common_fields.py +5 -5
  145. reconcile/gql_definitions/fragments/membership_source.py +5 -5
  146. reconcile/gql_definitions/fragments/minimal_ocm_organization.py +5 -5
  147. reconcile/gql_definitions/fragments/oc_connection_cluster.py +5 -5
  148. reconcile/gql_definitions/fragments/ocm_environment.py +5 -5
  149. reconcile/gql_definitions/fragments/pipeline_provider_retention.py +5 -5
  150. reconcile/gql_definitions/fragments/prometheus_instance.py +5 -5
  151. reconcile/gql_definitions/fragments/resource_limits_requirements.py +5 -5
  152. reconcile/gql_definitions/fragments/resource_requests_requirements.py +5 -5
  153. reconcile/gql_definitions/fragments/resource_values.py +5 -5
  154. reconcile/gql_definitions/fragments/saas_slo_document.py +5 -5
  155. reconcile/gql_definitions/fragments/saas_target_namespace.py +5 -5
  156. reconcile/gql_definitions/fragments/serviceaccount_token.py +5 -5
  157. reconcile/gql_definitions/fragments/terraform_state.py +5 -5
  158. reconcile/gql_definitions/fragments/upgrade_policy.py +5 -5
  159. reconcile/gql_definitions/fragments/user.py +5 -5
  160. reconcile/gql_definitions/fragments/vault_secret.py +5 -5
  161. reconcile/gql_definitions/gcp/gcp_docker_repos.py +9 -9
  162. reconcile/gql_definitions/gcp/gcp_projects.py +9 -9
  163. reconcile/gql_definitions/gitlab_members/gitlab_instances.py +9 -9
  164. reconcile/gql_definitions/gitlab_members/permissions.py +9 -9
  165. reconcile/gql_definitions/glitchtip/glitchtip_instance.py +9 -9
  166. reconcile/gql_definitions/glitchtip/glitchtip_project.py +11 -11
  167. reconcile/gql_definitions/glitchtip_project_alerts/glitchtip_project.py +9 -9
  168. reconcile/gql_definitions/integrations/integrations.py +48 -51
  169. reconcile/gql_definitions/introspection.json +3207 -1683
  170. reconcile/gql_definitions/jenkins_configs/jenkins_configs.py +11 -11
  171. reconcile/gql_definitions/jenkins_configs/jenkins_instances.py +10 -10
  172. reconcile/gql_definitions/jira/jira_servers.py +5 -5
  173. reconcile/gql_definitions/jira_permissions_validator/jira_boards_for_permissions_validator.py +14 -10
  174. reconcile/gql_definitions/jumphosts/jumphosts.py +13 -13
  175. reconcile/gql_definitions/ldap_groups/roles.py +5 -5
  176. reconcile/gql_definitions/ldap_groups/settings.py +9 -9
  177. reconcile/gql_definitions/maintenance/maintenances.py +5 -5
  178. reconcile/gql_definitions/membershipsources/roles.py +5 -5
  179. reconcile/gql_definitions/ocm_labels/clusters.py +18 -19
  180. reconcile/gql_definitions/ocm_labels/organizations.py +5 -5
  181. reconcile/gql_definitions/openshift_cluster_bots/clusters.py +22 -22
  182. reconcile/gql_definitions/openshift_groups/managed_groups.py +5 -5
  183. reconcile/gql_definitions/openshift_groups/managed_roles.py +6 -6
  184. reconcile/gql_definitions/openshift_serviceaccount_tokens/tokens.py +10 -10
  185. reconcile/gql_definitions/quay_membership/quay_membership.py +6 -6
  186. reconcile/gql_definitions/rhcs/certs.py +33 -87
  187. reconcile/gql_definitions/rhcs/openshift_resource_rhcs_cert.py +43 -0
  188. reconcile/gql_definitions/rhidp/organizations.py +18 -18
  189. reconcile/gql_definitions/service_dependencies/jenkins_instance_fragment.py +5 -5
  190. reconcile/gql_definitions/service_dependencies/service_dependencies.py +8 -8
  191. reconcile/gql_definitions/sharding/aws_accounts.py +10 -10
  192. reconcile/gql_definitions/sharding/ocm_organization.py +8 -8
  193. reconcile/gql_definitions/skupper_network/site_controller_template.py +5 -5
  194. reconcile/gql_definitions/skupper_network/skupper_networks.py +10 -10
  195. reconcile/gql_definitions/slack_usergroups/clusters.py +5 -5
  196. reconcile/gql_definitions/slack_usergroups/permissions.py +9 -9
  197. reconcile/gql_definitions/slack_usergroups/users.py +5 -5
  198. reconcile/gql_definitions/slo_documents/slo_documents.py +5 -5
  199. reconcile/gql_definitions/status_board/status_board.py +6 -7
  200. reconcile/gql_definitions/statuspage/statuspages.py +9 -9
  201. reconcile/gql_definitions/templating/template_collection.py +5 -5
  202. reconcile/gql_definitions/templating/templates.py +5 -5
  203. reconcile/gql_definitions/terraform_cloudflare_dns/app_interface_cloudflare_dns_settings.py +6 -6
  204. reconcile/gql_definitions/terraform_cloudflare_dns/terraform_cloudflare_zones.py +11 -11
  205. reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_accounts.py +11 -11
  206. reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_resources.py +20 -25
  207. reconcile/gql_definitions/terraform_cloudflare_users/app_interface_setting_cloudflare_and_vault.py +6 -6
  208. reconcile/gql_definitions/terraform_cloudflare_users/terraform_cloudflare_roles.py +12 -12
  209. reconcile/gql_definitions/terraform_init/aws_accounts.py +23 -9
  210. reconcile/gql_definitions/terraform_repo/terraform_repo.py +9 -9
  211. reconcile/gql_definitions/terraform_resources/database_access_manager.py +5 -5
  212. reconcile/gql_definitions/terraform_resources/terraform_resources_namespaces.py +440 -407
  213. reconcile/gql_definitions/terraform_tgw_attachments/aws_accounts.py +23 -17
  214. reconcile/gql_definitions/unleash_feature_toggles/feature_toggles.py +9 -9
  215. reconcile/gql_definitions/vault_instances/vault_instances.py +61 -61
  216. reconcile/gql_definitions/vault_policies/vault_policies.py +11 -11
  217. reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator.py +8 -8
  218. reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator_peered_cluster_fragment.py +5 -5
  219. reconcile/integrations_manager.py +3 -3
  220. reconcile/jenkins_worker_fleets.py +10 -8
  221. reconcile/jira_permissions_validator.py +237 -122
  222. reconcile/ldap_groups/integration.py +1 -1
  223. reconcile/ocm/types.py +35 -56
  224. reconcile/ocm_aws_infrastructure_access.py +1 -1
  225. reconcile/ocm_clusters.py +4 -4
  226. reconcile/ocm_labels/integration.py +3 -2
  227. reconcile/ocm_machine_pools.py +33 -27
  228. reconcile/openshift_base.py +113 -5
  229. reconcile/openshift_cluster_bots.py +3 -2
  230. reconcile/openshift_namespace_labels.py +1 -1
  231. reconcile/openshift_namespaces.py +97 -101
  232. reconcile/openshift_resources_base.py +6 -2
  233. reconcile/openshift_rhcs_certs.py +74 -37
  234. reconcile/openshift_rolebindings.py +230 -130
  235. reconcile/openshift_saas_deploy.py +6 -7
  236. reconcile/openshift_saas_deploy_change_tester.py +9 -7
  237. reconcile/openshift_saas_deploy_trigger_cleaner.py +3 -5
  238. reconcile/openshift_serviceaccount_tokens.py +2 -2
  239. reconcile/openshift_upgrade_watcher.py +4 -4
  240. reconcile/openshift_users.py +5 -3
  241. reconcile/oum/labelset.py +5 -3
  242. reconcile/oum/models.py +1 -4
  243. reconcile/prometheus_rules_tester/integration.py +3 -3
  244. reconcile/quay_mirror.py +1 -1
  245. reconcile/queries.py +131 -0
  246. reconcile/rhidp/common.py +3 -5
  247. reconcile/rhidp/sso_client/base.py +16 -5
  248. reconcile/saas_auto_promotions_manager/merge_request_manager/renderer.py +1 -1
  249. reconcile/saas_auto_promotions_manager/subscriber.py +4 -3
  250. reconcile/skupper_network/integration.py +2 -2
  251. reconcile/slack_usergroups.py +35 -14
  252. reconcile/sql_query.py +1 -0
  253. reconcile/status_board.py +6 -6
  254. reconcile/statuspage/atlassian.py +7 -7
  255. reconcile/statuspage/integrations/maintenances.py +4 -3
  256. reconcile/statuspage/page.py +4 -9
  257. reconcile/statuspage/status.py +5 -8
  258. reconcile/templates/rosa-classic-cluster-creation.sh.j2 +5 -1
  259. reconcile/templates/rosa-hcp-cluster-creation.sh.j2 +4 -1
  260. reconcile/templating/lib/merge_request_manager.py +2 -2
  261. reconcile/templating/lib/rendering.py +3 -3
  262. reconcile/templating/renderer.py +12 -13
  263. reconcile/terraform_aws_route53.py +7 -1
  264. reconcile/terraform_cloudflare_dns.py +3 -3
  265. reconcile/terraform_cloudflare_resources.py +5 -5
  266. reconcile/terraform_cloudflare_users.py +3 -2
  267. reconcile/terraform_init/integration.py +187 -23
  268. reconcile/terraform_repo.py +16 -12
  269. reconcile/terraform_resources.py +17 -7
  270. reconcile/terraform_tgw_attachments.py +27 -19
  271. reconcile/terraform_users.py +7 -0
  272. reconcile/terraform_vpc_peerings.py +14 -3
  273. reconcile/terraform_vpc_resources/integration.py +20 -8
  274. reconcile/typed_queries/app_interface_roles.py +10 -0
  275. reconcile/typed_queries/aws_account_tags.py +41 -0
  276. reconcile/typed_queries/cost_report/app_names.py +1 -1
  277. reconcile/typed_queries/cost_report/cost_namespaces.py +2 -2
  278. reconcile/typed_queries/saas_files.py +13 -13
  279. reconcile/typed_queries/status_board.py +2 -2
  280. reconcile/unleash_feature_toggles/integration.py +4 -2
  281. reconcile/utils/acs/base.py +6 -3
  282. reconcile/utils/acs/policies.py +2 -2
  283. reconcile/utils/aggregated_list.py +4 -3
  284. reconcile/utils/aws_api.py +51 -20
  285. reconcile/utils/aws_api_typed/api.py +38 -9
  286. reconcile/utils/aws_api_typed/cloudformation.py +149 -0
  287. reconcile/utils/aws_api_typed/logs.py +73 -0
  288. reconcile/utils/aws_api_typed/organization.py +4 -2
  289. reconcile/utils/binary.py +7 -12
  290. reconcile/utils/datetime_util.py +67 -0
  291. reconcile/utils/deadmanssnitch_api.py +1 -1
  292. reconcile/utils/differ.py +2 -3
  293. reconcile/utils/early_exit_cache.py +11 -12
  294. reconcile/utils/expiration.py +7 -3
  295. reconcile/utils/external_resource_spec.py +24 -1
  296. reconcile/utils/filtering.py +1 -1
  297. reconcile/utils/gitlab_api.py +7 -5
  298. reconcile/utils/glitchtip/client.py +6 -2
  299. reconcile/utils/glitchtip/models.py +25 -28
  300. reconcile/utils/gql.py +4 -7
  301. reconcile/utils/helm.py +2 -1
  302. reconcile/utils/helpers.py +1 -1
  303. reconcile/utils/instrumented_wrappers.py +1 -1
  304. reconcile/utils/internal_groups/client.py +2 -2
  305. reconcile/utils/internal_groups/models.py +8 -17
  306. reconcile/utils/jinja2/utils.py +6 -8
  307. reconcile/utils/jira_client.py +82 -63
  308. reconcile/utils/jjb_client.py +28 -15
  309. reconcile/utils/jobcontroller/controller.py +2 -2
  310. reconcile/utils/jobcontroller/models.py +17 -1
  311. reconcile/utils/json.py +74 -0
  312. reconcile/utils/membershipsources/app_interface_resolver.py +4 -2
  313. reconcile/utils/membershipsources/models.py +16 -23
  314. reconcile/utils/membershipsources/resolver.py +4 -2
  315. reconcile/utils/merge_request_manager/merge_request_manager.py +4 -4
  316. reconcile/utils/merge_request_manager/parser.py +6 -6
  317. reconcile/utils/metrics.py +5 -5
  318. reconcile/utils/models.py +304 -82
  319. reconcile/utils/mr/app_interface_reporter.py +2 -2
  320. reconcile/utils/mr/base.py +2 -2
  321. reconcile/utils/mr/notificator.py +3 -3
  322. reconcile/utils/mr/update_access_report_base.py +3 -4
  323. reconcile/utils/mr/user_maintenance.py +3 -2
  324. reconcile/utils/oc.py +249 -203
  325. reconcile/utils/oc_filters.py +3 -3
  326. reconcile/utils/ocm/addons.py +0 -1
  327. reconcile/utils/ocm/base.py +18 -21
  328. reconcile/utils/ocm/cluster_groups.py +1 -1
  329. reconcile/utils/ocm/identity_providers.py +2 -2
  330. reconcile/utils/ocm/labels.py +1 -1
  331. reconcile/utils/ocm/products.py +9 -3
  332. reconcile/utils/ocm/search_filters.py +3 -6
  333. reconcile/utils/ocm/service_log.py +4 -6
  334. reconcile/utils/ocm/sre_capability_labels.py +20 -13
  335. reconcile/utils/openshift_resource.py +10 -5
  336. reconcile/utils/output.py +3 -2
  337. reconcile/utils/pagerduty_api.py +10 -7
  338. reconcile/utils/promotion_state.py +6 -11
  339. reconcile/utils/raw_github_api.py +1 -1
  340. reconcile/utils/rhcsv2_certs.py +138 -35
  341. reconcile/utils/rosa/session.py +16 -0
  342. reconcile/utils/runtime/integration.py +2 -3
  343. reconcile/utils/runtime/runner.py +2 -2
  344. reconcile/utils/saasherder/interfaces.py +13 -20
  345. reconcile/utils/saasherder/models.py +25 -21
  346. reconcile/utils/saasherder/saasherder.py +55 -31
  347. reconcile/utils/slack_api.py +26 -4
  348. reconcile/utils/sloth.py +224 -0
  349. reconcile/utils/sqs_gateway.py +2 -1
  350. reconcile/utils/state.py +2 -1
  351. reconcile/utils/structs.py +1 -1
  352. reconcile/utils/terraform_client.py +5 -4
  353. reconcile/utils/terrascript_aws_client.py +192 -114
  354. reconcile/utils/unleash/server.py +2 -8
  355. reconcile/utils/vault.py +5 -12
  356. reconcile/utils/vcs.py +8 -8
  357. reconcile/vault_replication.py +107 -42
  358. tools/app_interface_reporter.py +4 -4
  359. tools/cli_commands/cost_report/cost_management_api.py +3 -3
  360. tools/cli_commands/cost_report/view.py +7 -6
  361. tools/cli_commands/erv2.py +1 -1
  362. tools/cli_commands/systems_and_tools.py +5 -1
  363. tools/qontract_cli.py +31 -18
  364. tools/template_validation.py +3 -1
  365. reconcile/gql_definitions/cna/__init__.py +0 -0
  366. reconcile/gql_definitions/cna/queries/__init__.py +0 -0
  367. reconcile/gql_definitions/ocm_oidc_idp/__init__.py +0 -0
  368. reconcile/gql_definitions/ocm_subscription_labels/__init__.py +0 -0
  369. {qontract_reconcile-0.10.2.dev334.dist-info → qontract_reconcile-0.10.2.dev439.dist-info}/WHEEL +0 -0
  370. {qontract_reconcile-0.10.2.dev334.dist-info → qontract_reconcile-0.10.2.dev439.dist-info}/entry_points.txt +0 -0
@@ -22,7 +22,6 @@ from typing import (
22
22
  TYPE_CHECKING,
23
23
  Any,
24
24
  Self,
25
- TypeAlias,
26
25
  cast,
27
26
  )
28
27
 
@@ -152,6 +151,7 @@ from reconcile.github_org import get_default_config
152
151
  from reconcile.gql_definitions.terraform_resources.terraform_resources_namespaces import (
153
152
  NamespaceTerraformResourceLifecycleV1,
154
153
  )
154
+ from reconcile.typed_queries.aws_account_tags import get_aws_account_tags
155
155
  from reconcile.utils import gql
156
156
  from reconcile.utils.aws_api import (
157
157
  AmiTag,
@@ -178,7 +178,11 @@ from reconcile.utils.external_resources import (
178
178
  from reconcile.utils.git import is_file_in_git_repo
179
179
  from reconcile.utils.gitlab_api import GitLabApi
180
180
  from reconcile.utils.jenkins_api import JenkinsApi
181
- from reconcile.utils.jinja2.utils import process_extracurlyjinja2_template
181
+ from reconcile.utils.jinja2.utils import (
182
+ process_extracurlyjinja2_template,
183
+ process_jinja2_template,
184
+ )
185
+ from reconcile.utils.json import json_dumps
182
186
  from reconcile.utils.password_validator import (
183
187
  PasswordPolicy,
184
188
  PasswordValidator,
@@ -202,7 +206,7 @@ if TYPE_CHECKING:
202
206
  from reconcile.utils.ocm import OCMMap
203
207
 
204
208
 
205
- TFResource: TypeAlias = type[
209
+ type TFResource = type[
206
210
  Resource | Data | Module | Provider | Variable | Output | Locals | Terraform
207
211
  ]
208
212
 
@@ -267,6 +271,8 @@ VARIABLE_KEYS = [
267
271
  "extra_tags",
268
272
  "lifecycle",
269
273
  "max_session_duration",
274
+ "secret_format",
275
+ "policy",
270
276
  ]
271
277
 
272
278
  EMAIL_REGEX = re.compile(r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$")
@@ -286,12 +292,6 @@ SUPPORTED_ALB_LISTENER_RULE_CONDITION_TYPE_MAPPING = {
286
292
  "source-ip": "source_ip",
287
293
  }
288
294
 
289
- DEFAULT_TAGS = {
290
- "tags": {
291
- "app": "app-sre-infra",
292
- },
293
- }
294
-
295
295
  AWS_ELB_ACCOUNT_IDS = {
296
296
  "us-east-1": "127311923021",
297
297
  "us-east-2": "033677994240",
@@ -374,6 +374,10 @@ class aws_s3_bucket_logging(Resource):
374
374
  pass
375
375
 
376
376
 
377
+ class aws_kinesis_resource_policy(Resource):
378
+ pass
379
+
380
+
377
381
  class aws_cloudfront_log_delivery_canonical_user_id(Data):
378
382
  pass
379
383
 
@@ -475,6 +479,7 @@ class TerrascriptClient:
475
479
  integration_prefix: str,
476
480
  thread_pool_size: int,
477
481
  accounts: Iterable[MutableMapping[str, Any]],
482
+ default_tags: Mapping[str, str] | None,
478
483
  settings: Mapping[str, Any] | None = None,
479
484
  prefetch_resources_by_schemas: Iterable[str] | None = None,
480
485
  secret_reader: SecretReaderBase | None = None,
@@ -488,6 +493,7 @@ class TerrascriptClient:
488
493
  else:
489
494
  self.secret_reader = SecretReader(settings=settings)
490
495
  self.configs: dict[str, dict] = {}
496
+ self.default_tags = default_tags or {"app": "app-sre-infra"}
491
497
  self.populate_configs(filtered_accounts)
492
498
  self.versions: dict[str, str] = {
493
499
  a["name"]: a["providerVersion"] for a in filtered_accounts
@@ -508,7 +514,7 @@ class TerrascriptClient:
508
514
  region=region,
509
515
  alias=region,
510
516
  skip_region_validation=True,
511
- default_tags=DEFAULT_TAGS,
517
+ default_tags={"tags": config["tags"]},
512
518
  )
513
519
 
514
520
  # Add default region, which will be in resourcesDefaultRegion
@@ -517,7 +523,7 @@ class TerrascriptClient:
517
523
  secret_key=config["aws_secret_access_key"],
518
524
  region=config["resourcesDefaultRegion"],
519
525
  skip_region_validation=True,
520
- default_tags=DEFAULT_TAGS,
526
+ default_tags={"tags": config["tags"]},
521
527
  )
522
528
 
523
529
  ts += Terraform(
@@ -800,6 +806,9 @@ class TerrascriptClient:
800
806
  config["supportedDeploymentRegions"] = account["supportedDeploymentRegions"]
801
807
  config["resourcesDefaultRegion"] = account["resourcesDefaultRegion"]
802
808
  config["terraformState"] = account["terraformState"]
809
+ config["tags"] = dict(self.default_tags) | get_aws_account_tags(
810
+ account.get("organization", None)
811
+ )
803
812
  self.configs[account_name] = config
804
813
 
805
814
  def _get_partition(self, account: str) -> str:
@@ -1054,7 +1063,9 @@ class TerrascriptClient:
1054
1063
  ignore_changes = (
1055
1064
  "all" if "all" in lifecycle.ignore_changes else lifecycle.ignore_changes
1056
1065
  )
1057
- return lifecycle.dict(by_alias=True) | {"ignore_changes": ignore_changes}
1066
+ return lifecycle.model_dump(by_alias=True) | {
1067
+ "ignore_changes": ignore_changes
1068
+ }
1058
1069
  return None
1059
1070
 
1060
1071
  def populate_additional_providers(
@@ -1069,25 +1080,15 @@ class TerrascriptClient:
1069
1080
  config = self.configs[account_name]
1070
1081
  existing_provider_aliases = {p.get("alias") for p in ts["provider"]["aws"]}
1071
1082
  if alias not in existing_provider_aliases:
1072
- if assume_role:
1073
- ts += provider.aws(
1074
- access_key=config["aws_access_key_id"],
1075
- secret_key=config["aws_secret_access_key"],
1076
- region=region,
1077
- alias=alias,
1078
- assume_role={"role_arn": assume_role},
1079
- skip_region_validation=True,
1080
- default_tags=DEFAULT_TAGS,
1081
- )
1082
- else:
1083
- ts += provider.aws(
1084
- access_key=config["aws_access_key_id"],
1085
- secret_key=config["aws_secret_access_key"],
1086
- region=region,
1087
- alias=alias,
1088
- skip_region_validation=True,
1089
- default_tags=DEFAULT_TAGS,
1090
- )
1083
+ ts += provider.aws(
1084
+ access_key=config["aws_access_key_id"],
1085
+ secret_key=config["aws_secret_access_key"],
1086
+ region=region,
1087
+ alias=alias,
1088
+ skip_region_validation=True,
1089
+ default_tags={"tags": config["tags"]},
1090
+ **{"assume_role": {"role_arn": assume_role}} if assume_role else {},
1091
+ )
1091
1092
 
1092
1093
  def populate_route53(
1093
1094
  self, desired_state: Iterable[Mapping[str, Any]], default_ttl: int = 300
@@ -1430,7 +1431,7 @@ class TerrascriptClient:
1430
1431
  req_account_name = req_account.name
1431
1432
  # Accepter's side of the connection - the cluster's account
1432
1433
  acc_account = accepter.account
1433
- acc_alias = self.get_provider_alias(acc_account.dict(by_alias=True))
1434
+ acc_alias = self.get_provider_alias(acc_account.model_dump(by_alias=True))
1434
1435
  acc_uid = acc_account.uid
1435
1436
  if acc_account.assume_role:
1436
1437
  acc_uid = awsh.get_account_uid_from_arn(acc_account.assume_role)
@@ -1947,7 +1948,7 @@ class TerrascriptClient:
1947
1948
  em_identifier = f"{identifier}-enhanced-monitoring"
1948
1949
  em_values = {
1949
1950
  "name": em_identifier,
1950
- "assume_role_policy": json.dumps(assume_role_policy, sort_keys=True),
1951
+ "assume_role_policy": json_dumps(assume_role_policy),
1951
1952
  }
1952
1953
  role_tf_resource = aws_iam_role(em_identifier, **em_values)
1953
1954
  tf_resources.append(role_tf_resource)
@@ -2216,6 +2217,43 @@ class TerrascriptClient:
2216
2217
  letters_and_digits = string.ascii_letters + string.digits
2217
2218
  return "".join(random.choice(letters_and_digits) for i in range(string_length))
2218
2219
 
2220
+ @staticmethod
2221
+ def _build_tf_resource_s3_lifecycle_rules(
2222
+ versioning: bool,
2223
+ common_values: Mapping[str, Any],
2224
+ ) -> list[dict]:
2225
+ lifecycle_rules = common_values.get("lifecycle_rules") or []
2226
+ if versioning and not any(
2227
+ "noncurrent_version_expiration" in lr for lr in lifecycle_rules
2228
+ ):
2229
+ # Add a default noncurrent object expiration rule
2230
+ # if one isn't already set
2231
+ rule = {
2232
+ "id": "expire_noncurrent_versions",
2233
+ "enabled": True,
2234
+ "noncurrent_version_expiration": {"days": 30},
2235
+ "expiration": {"expired_object_delete_marker": True},
2236
+ "abort_incomplete_multipart_upload_days": 3,
2237
+ }
2238
+ lifecycle_rules.append(rule)
2239
+
2240
+ if storage_class := common_values.get("storage_class"):
2241
+ sc = storage_class.upper()
2242
+ days = "1"
2243
+ if sc.endswith("_IA"):
2244
+ # Infrequent Access storage class has minimum 30 days
2245
+ # before transition
2246
+ days = "30"
2247
+ rule = {
2248
+ "id": sc + "_storage_class",
2249
+ "enabled": True,
2250
+ "transition": {"days": days, "storage_class": sc},
2251
+ "noncurrent_version_transition": {"days": days, "storage_class": sc},
2252
+ }
2253
+ lifecycle_rules.append(rule)
2254
+
2255
+ return lifecycle_rules
2256
+
2219
2257
  def populate_tf_resource_s3(self, spec: ExternalResourceSpec) -> aws_s3_bucket:
2220
2258
  account = spec.provisioner_name
2221
2259
  identifier = spec.identifier
@@ -2255,47 +2293,11 @@ class TerrascriptClient:
2255
2293
  request_payer = common_values.get("request_payer")
2256
2294
  if request_payer:
2257
2295
  values["request_payer"] = request_payer
2258
- lifecycle_rules = common_values.get("lifecycle_rules")
2259
- if lifecycle_rules:
2260
- # common_values['lifecycle_rules'] is a list of lifecycle_rules
2296
+ if lifecycle_rules := self._build_tf_resource_s3_lifecycle_rules(
2297
+ versioning=versioning,
2298
+ common_values=common_values,
2299
+ ):
2261
2300
  values["lifecycle_rule"] = lifecycle_rules
2262
- if versioning:
2263
- lrs = values.get("lifecycle_rule", [])
2264
- expiration_rule = False
2265
- for lr in lrs:
2266
- if "noncurrent_version_expiration" in lr:
2267
- expiration_rule = True
2268
- break
2269
- if not expiration_rule:
2270
- # Add a default noncurrent object expiration rule if
2271
- # if one isn't already set
2272
- rule = {
2273
- "id": "expire_noncurrent_versions",
2274
- "enabled": "true",
2275
- "noncurrent_version_expiration": {"days": 30},
2276
- }
2277
- if len(lrs) > 0:
2278
- lrs.append(rule)
2279
- else:
2280
- lrs = rule
2281
- sc = common_values.get("storage_class")
2282
- if sc:
2283
- sc = sc.upper()
2284
- days = "1"
2285
- if sc.endswith("_IA"):
2286
- # Infrequent Access storage class has minimum 30 days
2287
- # before transition
2288
- days = "30"
2289
- rule = {
2290
- "id": sc + "_storage_class",
2291
- "enabled": "true",
2292
- "transition": {"days": days, "storage_class": sc},
2293
- "noncurrent_version_transition": {"days": days, "storage_class": sc},
2294
- }
2295
- if values.get("lifecycle_rule"):
2296
- values["lifecycle_rule"].append(rule)
2297
- else:
2298
- values["lifecycle_rule"] = rule
2299
2301
  cors_rules = common_values.get("cors_rules")
2300
2302
  if cors_rules:
2301
2303
  # common_values['cors_rules'] is a list of cors_rules
@@ -2345,7 +2347,7 @@ class TerrascriptClient:
2345
2347
  }
2346
2348
  ],
2347
2349
  }
2348
- rc_values["assume_role_policy"] = json.dumps(role, sort_keys=True)
2350
+ rc_values["assume_role_policy"] = json_dumps(role)
2349
2351
  role_resource = aws_iam_role(id, **rc_values)
2350
2352
  tf_resources.append(role_resource)
2351
2353
 
@@ -2383,7 +2385,7 @@ class TerrascriptClient:
2383
2385
  },
2384
2386
  ],
2385
2387
  }
2386
- rc_values["policy"] = json.dumps(policy, sort_keys=True)
2388
+ rc_values["policy"] = json_dumps(policy)
2387
2389
  policy_resource = aws_iam_policy(id, **rc_values)
2388
2390
  tf_resources.append(policy_resource)
2389
2391
 
@@ -2598,7 +2600,7 @@ class TerrascriptClient:
2598
2600
  },
2599
2601
  ],
2600
2602
  }
2601
- values["policy"] = json.dumps(policy, sort_keys=True)
2603
+ values["policy"] = json_dumps(policy)
2602
2604
  values["depends_on"] = self.get_dependencies([user_tf_resource])
2603
2605
 
2604
2606
  tf_aws_iam_policy = aws_iam_policy(identifier, **values)
@@ -2877,7 +2879,7 @@ class TerrascriptClient:
2877
2879
  values: dict[str, Any] = {
2878
2880
  "name": identifier,
2879
2881
  "tags": common_values["tags"],
2880
- "assume_role_policy": json.dumps(assume_role_policy),
2882
+ "assume_role_policy": json_dumps(assume_role_policy),
2881
2883
  }
2882
2884
 
2883
2885
  inline_policy = common_values.get("inline_policy")
@@ -2931,7 +2933,7 @@ class TerrascriptClient:
2931
2933
  self, account: str, name: str, policy: Mapping[str, Any]
2932
2934
  ) -> None:
2933
2935
  tf_aws_iam_policy = aws_iam_policy(
2934
- f"{account}-{name}", name=name, policy=json.dumps(policy)
2936
+ f"{account}-{name}", name=name, policy=json_dumps(policy)
2935
2937
  )
2936
2938
  self.add_resource(account, tf_aws_iam_policy)
2937
2939
 
@@ -2973,7 +2975,7 @@ class TerrascriptClient:
2973
2975
  role_tf_resource = aws_iam_role(
2974
2976
  f"{account}-{name}",
2975
2977
  name=name,
2976
- assume_role_policy=json.dumps(assume_role_policy),
2978
+ assume_role_policy=json_dumps(assume_role_policy),
2977
2979
  managed_policy_arns=managed_policy_arns,
2978
2980
  max_session_duration=max_session_duration_hours * 3600,
2979
2981
  )
@@ -3017,7 +3019,7 @@ class TerrascriptClient:
3017
3019
  all_queues.append(queue_name)
3018
3020
  sqs_policy = values.pop("sqs_policy", None)
3019
3021
  if sqs_policy is not None:
3020
- values["policy"] = json.dumps(sqs_policy, sort_keys=True)
3022
+ values["policy"] = json_dumps(sqs_policy)
3021
3023
  dl_queue = values.pop("dl_queue", None)
3022
3024
  if dl_queue is not None:
3023
3025
  max_receive_count = int(values.pop("max_receive_count", 10))
@@ -3031,9 +3033,7 @@ class TerrascriptClient:
3031
3033
  "deadLetterTargetArn": "${" + dl_data.arn + "}",
3032
3034
  "maxReceiveCount": max_receive_count,
3033
3035
  }
3034
- values["redrive_policy"] = json.dumps(
3035
- redrive_policy, sort_keys=True
3036
- )
3036
+ values["redrive_policy"] = json_dumps(redrive_policy)
3037
3037
  kms_master_key_id = values.pop("kms_master_key_id", None)
3038
3038
  if kms_master_key_id is not None:
3039
3039
  if kms_master_key_id.startswith("arn:"):
@@ -3106,7 +3106,7 @@ class TerrascriptClient:
3106
3106
  "Resource": list(kms_keys),
3107
3107
  }
3108
3108
  policy["Statement"].append(kms_statement)
3109
- values["policy"] = json.dumps(policy, sort_keys=True)
3109
+ values["policy"] = json_dumps(policy)
3110
3110
  policy_tf_resource = aws_iam_policy(policy_identifier, **values)
3111
3111
  tf_resources.append(policy_tf_resource)
3112
3112
 
@@ -3256,7 +3256,7 @@ class TerrascriptClient:
3256
3256
  }
3257
3257
  ],
3258
3258
  }
3259
- values["policy"] = json.dumps(policy, sort_keys=True)
3259
+ values["policy"] = json_dumps(policy)
3260
3260
  values["depends_on"] = self.get_dependencies([user_tf_resource])
3261
3261
 
3262
3262
  tf_aws_iam_policy = aws_iam_policy(identifier, **values)
@@ -3366,7 +3366,7 @@ class TerrascriptClient:
3366
3366
  },
3367
3367
  ],
3368
3368
  }
3369
- values_policy["policy"] = json.dumps(policy, sort_keys=True)
3369
+ values_policy["policy"] = json_dumps(policy)
3370
3370
  values_policy["depends_on"] = self.get_dependencies([user_tf_resource])
3371
3371
 
3372
3372
  tf_aws_iam_policy = aws_iam_policy(identifier, **values_policy)
@@ -3416,7 +3416,7 @@ class TerrascriptClient:
3416
3416
  }
3417
3417
  ],
3418
3418
  }
3419
- values_policy["policy"] = json.dumps(policy, sort_keys=True)
3419
+ values_policy["policy"] = json_dumps(policy)
3420
3420
  values_policy["depends_on"] = self.get_dependencies([bucket_tf_resource])
3421
3421
  region = common_values.get("region") or self.default_regions.get(account)
3422
3422
  assert region # make mypy happy
@@ -3562,7 +3562,7 @@ class TerrascriptClient:
3562
3562
  }
3563
3563
  ],
3564
3564
  }
3565
- sqs_values["policy"] = json.dumps(sqs_policy, sort_keys=True)
3565
+ sqs_values["policy"] = json_dumps(sqs_policy)
3566
3566
 
3567
3567
  kms_encryption = common_values.get("kms_encryption", False)
3568
3568
  if kms_encryption:
@@ -3598,7 +3598,7 @@ class TerrascriptClient:
3598
3598
  },
3599
3599
  ],
3600
3600
  }
3601
- kms_values["policy"] = json.dumps(kms_policy, sort_keys=True)
3601
+ kms_values["policy"] = json_dumps(kms_policy)
3602
3602
  if provider:
3603
3603
  kms_values["provider"] = provider
3604
3604
 
@@ -3696,7 +3696,7 @@ class TerrascriptClient:
3696
3696
  "Resource": [sqs_values["kms_master_key_id"]],
3697
3697
  }
3698
3698
  policy["Statement"].append(kms_statement)
3699
- values_policy["policy"] = json.dumps(policy, sort_keys=True)
3699
+ values_policy["policy"] = json_dumps(policy)
3700
3700
  policy_tf_resource = aws_iam_policy(sqs_identifier, **values_policy)
3701
3701
  tf_resources.append(policy_tf_resource)
3702
3702
 
@@ -3767,7 +3767,7 @@ class TerrascriptClient:
3767
3767
  role_identifier = f"{identifier}-lambda-execution-role"
3768
3768
  role_values = {
3769
3769
  "name": role_identifier,
3770
- "assume_role_policy": json.dumps(assume_role_policy, sort_keys=True),
3770
+ "assume_role_policy": json_dumps(assume_role_policy),
3771
3771
  }
3772
3772
 
3773
3773
  role_tf_resource = aws_iam_role(role_identifier, **role_values)
@@ -3799,7 +3799,7 @@ class TerrascriptClient:
3799
3799
 
3800
3800
  policy_values = {
3801
3801
  "role": "${" + role_tf_resource.id + "}",
3802
- "policy": json.dumps(policy, sort_keys=True),
3802
+ "policy": json_dumps(policy),
3803
3803
  }
3804
3804
  policy_tf_resource = aws_iam_role_policy(policy_identifier, **policy_values)
3805
3805
  tf_resources.append(policy_tf_resource)
@@ -3927,7 +3927,7 @@ class TerrascriptClient:
3927
3927
  }
3928
3928
  values = {
3929
3929
  "name": identifier,
3930
- "policy": json.dumps(policy, sort_keys=True),
3930
+ "policy": json_dumps(policy),
3931
3931
  "depends_on": self.get_dependencies([user_tf_resource]),
3932
3932
  }
3933
3933
 
@@ -4024,6 +4024,22 @@ class TerrascriptClient:
4024
4024
  kinesis_tf_resource = aws_kinesis_stream(identifier, **kinesis_values)
4025
4025
  tf_resources.append(kinesis_tf_resource)
4026
4026
 
4027
+ # kinesis resource policy (optional)
4028
+ # Terraform resource reference:
4029
+ # https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kinesis_resource_policy
4030
+ if policy := common_values.get("policy"):
4031
+ policy_identifier = f"{identifier}-policy"
4032
+ policy_values: dict[str, Any] = {
4033
+ "resource_arn": "${" + kinesis_tf_resource.arn + "}",
4034
+ "policy": policy,
4035
+ }
4036
+ if provider:
4037
+ policy_values["provider"] = provider
4038
+ kinesis_policy_tf_resource = aws_kinesis_resource_policy(
4039
+ policy_identifier, **policy_values
4040
+ )
4041
+ tf_resources.append(kinesis_policy_tf_resource)
4042
+
4027
4043
  es_identifier = common_values.get("es_identifier", None)
4028
4044
  if es_identifier:
4029
4045
  es_resource = self._find_resource_spec(
@@ -4048,7 +4064,7 @@ class TerrascriptClient:
4048
4064
  role_identifier = f"{identifier}-lambda-execution-role"
4049
4065
  role_values = {
4050
4066
  "name": role_identifier,
4051
- "assume_role_policy": json.dumps(assume_role_policy, sort_keys=True),
4067
+ "assume_role_policy": json_dumps(assume_role_policy),
4052
4068
  "tags": tags,
4053
4069
  }
4054
4070
 
@@ -4085,7 +4101,7 @@ class TerrascriptClient:
4085
4101
  policy_tf_resource = aws_iam_policy(
4086
4102
  policy_identifier,
4087
4103
  name=policy_identifier,
4088
- policy=json.dumps(policy, sort_keys=True),
4104
+ policy=json_dumps(policy),
4089
4105
  tags=tags,
4090
4106
  )
4091
4107
  tf_resources.append(policy_tf_resource)
@@ -4300,7 +4316,7 @@ class TerrascriptClient:
4300
4316
  # iam user policy
4301
4317
  values_policy: dict[str, Any] = {
4302
4318
  "name": identifier,
4303
- "policy": json.dumps(policy, sort_keys=True),
4319
+ "policy": json_dumps(policy),
4304
4320
  "depends_on": self.get_dependencies([user_tf_resource]),
4305
4321
  }
4306
4322
 
@@ -4418,10 +4434,7 @@ class TerrascriptClient:
4418
4434
 
4419
4435
  :return: key is AWS account name and value is terraform configuration
4420
4436
  """
4421
- return {
4422
- name: json.dumps(ts, indent=2, sort_keys=True)
4423
- for name, ts in self.tss.items()
4424
- }
4437
+ return {name: json_dumps(ts, indent=2) for name, ts in self.tss.items()}
4425
4438
 
4426
4439
  def init_values(
4427
4440
  self, spec: ExternalResourceSpec, init_tags: bool = True
@@ -4533,7 +4546,7 @@ class TerrascriptClient:
4533
4546
  output_name = output_format.format(
4534
4547
  spec.output_prefix, self.integration_prefix, "annotations"
4535
4548
  )
4536
- anno_json = json.dumps(spec.annotations()).encode("utf-8")
4549
+ anno_json = json_dumps(spec.annotations()).encode("utf-8")
4537
4550
  output_value = base64.b64encode(anno_json).decode()
4538
4551
  tf_resources.append(Output(output_name, value=output_value))
4539
4552
 
@@ -4673,7 +4686,7 @@ class TerrascriptClient:
4673
4686
  }
4674
4687
  log_groups_policy_values = {
4675
4688
  "policy_name": "es-log-publishing-permissions",
4676
- "policy_document": json.dumps(log_groups_policy, sort_keys=True),
4689
+ "policy_document": json_dumps(log_groups_policy),
4677
4690
  }
4678
4691
  resource_policy = aws_cloudwatch_log_resource_policy(
4679
4692
  "es_log_publishing_resource_policy",
@@ -5005,7 +5018,7 @@ class TerrascriptClient:
5005
5018
  }
5006
5019
  ],
5007
5020
  }
5008
- es_values["access_policies"] = json.dumps(access_policies, sort_keys=True)
5021
+ es_values["access_policies"] = json_dumps(access_policies)
5009
5022
 
5010
5023
  region = values.get("region") or self.default_regions.get(account)
5011
5024
  assert region # make mypy happy
@@ -5061,7 +5074,7 @@ class TerrascriptClient:
5061
5074
 
5062
5075
  version_values = {
5063
5076
  "secret_id": "${" + aws_secret_resource.id + "}",
5064
- "secret_string": json.dumps(master_user, sort_keys=True),
5077
+ "secret_string": json_dumps(master_user),
5065
5078
  }
5066
5079
  if provider:
5067
5080
  version_values["provider"] = provider
@@ -5088,7 +5101,7 @@ class TerrascriptClient:
5088
5101
  iam_policy_resource = aws_iam_policy(
5089
5102
  secret_identifier,
5090
5103
  name=f"{identifier}-secretsmanager-policy",
5091
- policy=json.dumps(policy, sort_keys=True),
5104
+ policy=json_dumps(policy),
5092
5105
  tags=tags,
5093
5106
  )
5094
5107
  tf_resources.append(iam_policy_resource)
@@ -5500,7 +5513,7 @@ class TerrascriptClient:
5500
5513
  lb_access_logs_s3_bucket_policy_values = {
5501
5514
  "provider": provider,
5502
5515
  "bucket": f"${{{lb_access_logs_s3_bucket_tf_resource.id}}}",
5503
- "policy": json.dumps(policy, sort_keys=True),
5516
+ "policy": json_dumps(policy),
5504
5517
  }
5505
5518
  lb_access_logs_s3_bucket_policy_tf_resource = aws_s3_bucket_policy(
5506
5519
  policy_identifier, **lb_access_logs_s3_bucket_policy_values
@@ -5813,9 +5826,13 @@ class TerrascriptClient:
5813
5826
  assert secret # make mypy happy
5814
5827
  secret_data = self.secret_reader.read_all(secret)
5815
5828
 
5829
+ secret_format = common_values.get("secret_format")
5830
+ if secret_format is not None:
5831
+ secret_data = self._apply_secret_format(str(secret_format), secret_data)
5832
+
5816
5833
  version_values: dict[str, Any] = {
5817
5834
  "secret_id": "${" + aws_secret_resource.id + "}",
5818
- "secret_string": json.dumps(secret_data, sort_keys=True),
5835
+ "secret_string": json_dumps(secret_data),
5819
5836
  }
5820
5837
 
5821
5838
  if self._multiregion_account(account):
@@ -5836,6 +5853,66 @@ class TerrascriptClient:
5836
5853
 
5837
5854
  self.add_resources(account, tf_resources)
5838
5855
 
5856
+ @staticmethod
5857
+ def _unflatten_dotted_keys_dict(flat_dict: dict[str, str]) -> dict[str, Any]:
5858
+ """Convert a flat dictionary with dotted keys to a nested dictionary.
5859
+
5860
+ Example:
5861
+ {"db.host": "localhost", "db.port": "5432"} ->
5862
+ {"db": {"host": "localhost", "port": "5432"}}
5863
+
5864
+ Raises:
5865
+ ValueError: If there are conflicting keys (e.g., "a.b" and "a.b.c")
5866
+ """
5867
+ result: dict[str, Any] = {}
5868
+ for key, value in flat_dict.items():
5869
+ parts = key.split(".")
5870
+ current = result
5871
+ for i, part in enumerate(parts[:-1]):
5872
+ if part not in current:
5873
+ current[part] = {}
5874
+ elif not isinstance(current[part], dict):
5875
+ # Conflict: trying to traverse through a non-dict value
5876
+ conflicting_path = ".".join(parts[: i + 1])
5877
+ raise ValueError(
5878
+ f"Conflicting keys detected: '{conflicting_path}' is both a "
5879
+ f"value and a nested path in key '{key}'"
5880
+ )
5881
+ current = current[part]
5882
+
5883
+ # Check if we're trying to set a value where a dict already exists
5884
+ if parts[-1] in current and isinstance(current[parts[-1]], dict):
5885
+ raise ValueError(
5886
+ f"Conflicting keys detected: '{key}' conflicts with nested keys"
5887
+ )
5888
+
5889
+ current[parts[-1]] = value
5890
+
5891
+ return result
5892
+
5893
+ @staticmethod
5894
+ def _apply_secret_format(
5895
+ secret_format: str, secret_data: dict[str, str]
5896
+ ) -> dict[str, str]:
5897
+ # Convert flat dict with dotted keys to nested dict for Jinja2
5898
+ nested_secret_data = TerrascriptClient._unflatten_dotted_keys_dict(secret_data)
5899
+ rendered_data = process_jinja2_template(secret_format, nested_secret_data)
5900
+
5901
+ parsed_data = json.loads(rendered_data)
5902
+
5903
+ if not isinstance(parsed_data, dict):
5904
+ raise ValueError("secret_format must be a dictionary")
5905
+
5906
+ # validate secret is a dict[str, str]
5907
+ for k, v in parsed_data.items():
5908
+ if not isinstance(k, str):
5909
+ raise ValueError(f"key '{k}' is not a string")
5910
+
5911
+ if not isinstance(v, str):
5912
+ raise ValueError(f"dictionary value '{v}' under '{k}' is not a string")
5913
+
5914
+ return parsed_data
5915
+
5839
5916
  def get_commit_sha(self, repo_info: Mapping) -> str:
5840
5917
  url = repo_info["url"]
5841
5918
  ref = repo_info["ref"]
@@ -5854,7 +5931,8 @@ class TerrascriptClient:
5854
5931
  return commit.sha
5855
5932
  case "gitlab":
5856
5933
  gitlab = self.init_gitlab()
5857
- project = gitlab.get_project(url)
5934
+ if not (project := gitlab.get_project(url)):
5935
+ raise ValueError(f"could not find gitlab project for url {url}")
5858
5936
  commits = project.commits.list(ref_name=ref, per_page=1, page=1)
5859
5937
  return commits[0].id
5860
5938
  case _:
@@ -6135,7 +6213,7 @@ class TerrascriptClient:
6135
6213
  lambda_iam_role_resource = aws_iam_role(
6136
6214
  "lambda_role",
6137
6215
  name=f"ocm-{identifier}-cognito-lambda-role",
6138
- assume_role_policy=json.dumps(lambda_role_policy),
6216
+ assume_role_policy=json_dumps(lambda_role_policy),
6139
6217
  managed_policy_arns=[lambda_managed_policy_arn],
6140
6218
  force_detach_policies=False,
6141
6219
  max_session_duration=3600,
@@ -6810,7 +6888,7 @@ class TerrascriptClient:
6810
6888
  )
6811
6889
  tf_resources.append(api_gateway_stage_resource)
6812
6890
 
6813
- rest_api_policy = json.dumps({
6891
+ rest_api_policy = json_dumps({
6814
6892
  "Version": "2012-10-17",
6815
6893
  "Statement": [
6816
6894
  {
@@ -6914,7 +6992,7 @@ class TerrascriptClient:
6914
6992
  },
6915
6993
  ],
6916
6994
  }
6917
- cloudwatch_assume_role_policy = json.dumps(policy, sort_keys=True)
6995
+ cloudwatch_assume_role_policy = json_dumps(policy)
6918
6996
 
6919
6997
  cloudwatch_iam_role_resource = aws_iam_role(
6920
6998
  "cloudwatch_assume_role",
@@ -6942,7 +7020,7 @@ class TerrascriptClient:
6942
7020
  ],
6943
7021
  }
6944
7022
 
6945
- cloudwatch_iam_policy_document = json.dumps(policy, sort_keys=True)
7023
+ cloudwatch_iam_policy_document = json_dumps(policy)
6946
7024
 
6947
7025
  cloudwatch_iam_policy_resource = aws_iam_policy(
6948
7026
  "cloudwatch",
@@ -7187,7 +7265,7 @@ class TerrascriptClient:
7187
7265
 
7188
7266
  version_values = {
7189
7267
  "secret_id": "${" + secret_resource.arn + "}",
7190
- "secret_string": json.dumps(secret, sort_keys=True),
7268
+ "secret_string": json_dumps(secret),
7191
7269
  }
7192
7270
  version_resource = aws_secretsmanager_secret_version(
7193
7271
  secret_identifier, **version_values
@@ -7196,7 +7274,7 @@ class TerrascriptClient:
7196
7274
 
7197
7275
  secret_policy_values = {
7198
7276
  "secret_arn": "${" + secret_resource.arn + "}",
7199
- "policy": json.dumps({
7277
+ "policy": json_dumps({
7200
7278
  "Version": "2012-10-17",
7201
7279
  "Statement": [
7202
7280
  {