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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (356) hide show
  1. {qontract_reconcile-0.10.2.dev349.dist-info → qontract_reconcile-0.10.2.dev414.dist-info}/METADATA +12 -11
  2. {qontract_reconcile-0.10.2.dev349.dist-info → qontract_reconcile-0.10.2.dev414.dist-info}/RECORD +356 -350
  3. reconcile/acs_rbac.py +2 -2
  4. reconcile/aus/advanced_upgrade_service.py +15 -12
  5. reconcile/aus/base.py +26 -27
  6. reconcile/aus/cluster_version_data.py +15 -5
  7. reconcile/aus/models.py +1 -1
  8. reconcile/automated_actions/config/integration.py +15 -3
  9. reconcile/aws_account_manager/integration.py +8 -8
  10. reconcile/aws_account_manager/reconciler.py +3 -3
  11. reconcile/aws_ami_cleanup/integration.py +8 -12
  12. reconcile/aws_ami_share.py +69 -62
  13. reconcile/aws_cloudwatch_log_retention/integration.py +155 -126
  14. reconcile/aws_ecr_image_pull_secrets.py +2 -2
  15. reconcile/aws_iam_keys.py +7 -41
  16. reconcile/aws_saml_idp/integration.py +12 -4
  17. reconcile/aws_saml_roles/integration.py +32 -25
  18. reconcile/aws_version_sync/integration.py +6 -12
  19. reconcile/change_owners/bundle.py +3 -3
  20. reconcile/change_owners/change_log_tracking.py +3 -2
  21. reconcile/change_owners/change_owners.py +1 -1
  22. reconcile/change_owners/diff.py +2 -4
  23. reconcile/checkpoint.py +11 -3
  24. reconcile/cli.py +33 -8
  25. reconcile/dashdotdb_dora.py +5 -12
  26. reconcile/dashdotdb_slo.py +1 -1
  27. reconcile/database_access_manager.py +123 -117
  28. reconcile/dynatrace_token_provider/integration.py +1 -1
  29. reconcile/endpoints_discovery/integration.py +4 -1
  30. reconcile/endpoints_discovery/merge_request.py +1 -1
  31. reconcile/endpoints_discovery/merge_request_manager.py +9 -11
  32. reconcile/external_resources/factories.py +5 -12
  33. reconcile/external_resources/integration.py +1 -1
  34. reconcile/external_resources/manager.py +24 -10
  35. reconcile/external_resources/meta.py +0 -1
  36. reconcile/external_resources/metrics.py +1 -1
  37. reconcile/external_resources/model.py +13 -13
  38. reconcile/external_resources/reconciler.py +7 -4
  39. reconcile/external_resources/secrets_sync.py +6 -8
  40. reconcile/external_resources/state.py +60 -17
  41. reconcile/fleet_labeler/integration.py +1 -1
  42. reconcile/gabi_authorized_users.py +8 -5
  43. reconcile/gcp_image_mirror.py +2 -2
  44. reconcile/github_org.py +1 -1
  45. reconcile/github_owners.py +4 -0
  46. reconcile/gitlab_housekeeping.py +13 -15
  47. reconcile/gitlab_members.py +6 -12
  48. reconcile/gitlab_mr_sqs_consumer.py +2 -2
  49. reconcile/gitlab_owners.py +15 -11
  50. reconcile/gitlab_permissions.py +8 -12
  51. reconcile/glitchtip_project_alerts/integration.py +3 -1
  52. reconcile/gql_definitions/acs/acs_instances.py +5 -5
  53. reconcile/gql_definitions/acs/acs_policies.py +5 -5
  54. reconcile/gql_definitions/acs/acs_rbac.py +5 -5
  55. reconcile/gql_definitions/advanced_upgrade_service/aus_clusters.py +5 -5
  56. reconcile/gql_definitions/advanced_upgrade_service/aus_organization.py +5 -5
  57. reconcile/gql_definitions/app_interface_metrics_exporter/onboarding_status.py +5 -5
  58. reconcile/gql_definitions/app_sre_tekton_access_revalidation/roles.py +5 -5
  59. reconcile/gql_definitions/app_sre_tekton_access_revalidation/users.py +5 -5
  60. reconcile/gql_definitions/automated_actions/instance.py +46 -7
  61. reconcile/gql_definitions/aws_account_manager/aws_accounts.py +5 -5
  62. reconcile/gql_definitions/aws_ami_cleanup/aws_accounts.py +15 -5
  63. reconcile/gql_definitions/aws_cloudwatch_log_retention/aws_accounts.py +27 -66
  64. reconcile/gql_definitions/aws_saml_idp/aws_accounts.py +15 -5
  65. reconcile/gql_definitions/aws_saml_roles/aws_accounts.py +15 -5
  66. reconcile/gql_definitions/aws_saml_roles/roles.py +5 -5
  67. reconcile/gql_definitions/aws_version_sync/clusters.py +5 -5
  68. reconcile/gql_definitions/aws_version_sync/namespaces.py +5 -5
  69. reconcile/gql_definitions/change_owners/queries/change_types.py +5 -5
  70. reconcile/gql_definitions/change_owners/queries/self_service_roles.py +5 -5
  71. reconcile/gql_definitions/cluster_auth_rhidp/clusters.py +5 -5
  72. reconcile/gql_definitions/common/alerting_services_settings.py +5 -5
  73. reconcile/gql_definitions/common/app_code_component_repos.py +5 -5
  74. reconcile/gql_definitions/common/app_interface_custom_messages.py +5 -5
  75. reconcile/gql_definitions/common/app_interface_dms_settings.py +5 -5
  76. reconcile/gql_definitions/common/app_interface_repo_settings.py +5 -5
  77. reconcile/gql_definitions/common/app_interface_roles.py +5 -5
  78. reconcile/gql_definitions/common/app_interface_state_settings.py +5 -5
  79. reconcile/gql_definitions/common/app_interface_vault_settings.py +5 -5
  80. reconcile/gql_definitions/common/app_quay_repos_escalation_policies.py +5 -5
  81. reconcile/gql_definitions/common/apps.py +5 -5
  82. reconcile/gql_definitions/common/aws_vpc_requests.py +15 -5
  83. reconcile/gql_definitions/common/aws_vpcs.py +5 -5
  84. reconcile/gql_definitions/common/clusters.py +7 -5
  85. reconcile/gql_definitions/common/clusters_minimal.py +5 -5
  86. reconcile/gql_definitions/common/clusters_with_dms.py +5 -5
  87. reconcile/gql_definitions/common/clusters_with_peering.py +5 -5
  88. reconcile/gql_definitions/common/github_orgs.py +5 -5
  89. reconcile/gql_definitions/common/jira_settings.py +5 -5
  90. reconcile/gql_definitions/common/jiralert_settings.py +5 -5
  91. reconcile/gql_definitions/common/ldap_settings.py +5 -5
  92. reconcile/gql_definitions/common/namespaces.py +5 -5
  93. reconcile/gql_definitions/common/namespaces_minimal.py +7 -5
  94. reconcile/gql_definitions/common/ocm_env_telemeter.py +5 -5
  95. reconcile/gql_definitions/common/ocm_environments.py +5 -5
  96. reconcile/gql_definitions/common/pagerduty_instances.py +5 -5
  97. reconcile/gql_definitions/common/pgp_reencryption_settings.py +5 -5
  98. reconcile/gql_definitions/common/pipeline_providers.py +5 -5
  99. reconcile/gql_definitions/common/quay_instances.py +5 -5
  100. reconcile/gql_definitions/common/quay_orgs.py +5 -5
  101. reconcile/gql_definitions/common/reserved_networks.py +5 -5
  102. reconcile/gql_definitions/common/rhcs_provider_settings.py +5 -5
  103. reconcile/gql_definitions/common/saas_files.py +5 -5
  104. reconcile/gql_definitions/common/saas_target_namespaces.py +5 -5
  105. reconcile/gql_definitions/common/saasherder_settings.py +5 -5
  106. reconcile/gql_definitions/common/slack_workspaces.py +5 -5
  107. reconcile/gql_definitions/common/smtp_client_settings.py +5 -5
  108. reconcile/gql_definitions/common/state_aws_account.py +5 -5
  109. reconcile/gql_definitions/common/users.py +5 -5
  110. reconcile/gql_definitions/common/users_with_paths.py +5 -5
  111. reconcile/gql_definitions/cost_report/app_names.py +5 -5
  112. reconcile/gql_definitions/cost_report/cost_namespaces.py +5 -5
  113. reconcile/gql_definitions/cost_report/settings.py +5 -5
  114. reconcile/gql_definitions/dashdotdb_slo/slo_documents_query.py +5 -5
  115. reconcile/gql_definitions/dynatrace_token_provider/dynatrace_bootstrap_tokens.py +5 -5
  116. reconcile/gql_definitions/dynatrace_token_provider/token_specs.py +5 -5
  117. reconcile/gql_definitions/email_sender/apps.py +5 -5
  118. reconcile/gql_definitions/email_sender/emails.py +5 -5
  119. reconcile/gql_definitions/email_sender/users.py +5 -5
  120. reconcile/gql_definitions/endpoints_discovery/apps.py +5 -5
  121. reconcile/gql_definitions/external_resources/aws_accounts.py +5 -5
  122. reconcile/gql_definitions/external_resources/external_resources_modules.py +5 -5
  123. reconcile/gql_definitions/external_resources/external_resources_namespaces.py +89 -6
  124. reconcile/gql_definitions/external_resources/external_resources_settings.py +7 -5
  125. reconcile/gql_definitions/external_resources/fragments/external_resources_module_overrides.py +5 -5
  126. reconcile/gql_definitions/fleet_labeler/fleet_labels.py +5 -5
  127. reconcile/gql_definitions/fragments/aus_organization.py +5 -5
  128. reconcile/gql_definitions/fragments/aws_account_common.py +7 -5
  129. reconcile/gql_definitions/fragments/aws_account_managed.py +5 -5
  130. reconcile/gql_definitions/fragments/aws_account_sso.py +5 -5
  131. reconcile/gql_definitions/fragments/aws_infra_management_account.py +5 -5
  132. reconcile/gql_definitions/fragments/aws_organization.py +33 -0
  133. reconcile/gql_definitions/fragments/aws_vpc.py +5 -5
  134. reconcile/gql_definitions/fragments/aws_vpc_request.py +7 -5
  135. reconcile/gql_definitions/fragments/container_image_mirror.py +5 -5
  136. reconcile/gql_definitions/fragments/deploy_resources.py +5 -5
  137. reconcile/gql_definitions/fragments/disable.py +5 -5
  138. reconcile/gql_definitions/fragments/email_service.py +5 -5
  139. reconcile/gql_definitions/fragments/email_user.py +5 -5
  140. reconcile/gql_definitions/fragments/jumphost_common_fields.py +5 -5
  141. reconcile/gql_definitions/fragments/membership_source.py +5 -5
  142. reconcile/gql_definitions/fragments/minimal_ocm_organization.py +5 -5
  143. reconcile/gql_definitions/fragments/oc_connection_cluster.py +5 -5
  144. reconcile/gql_definitions/fragments/ocm_environment.py +5 -5
  145. reconcile/gql_definitions/fragments/pipeline_provider_retention.py +5 -5
  146. reconcile/gql_definitions/fragments/prometheus_instance.py +5 -5
  147. reconcile/gql_definitions/fragments/resource_limits_requirements.py +5 -5
  148. reconcile/gql_definitions/fragments/resource_requests_requirements.py +5 -5
  149. reconcile/gql_definitions/fragments/resource_values.py +5 -5
  150. reconcile/gql_definitions/fragments/saas_slo_document.py +5 -5
  151. reconcile/gql_definitions/fragments/saas_target_namespace.py +5 -5
  152. reconcile/gql_definitions/fragments/serviceaccount_token.py +5 -5
  153. reconcile/gql_definitions/fragments/terraform_state.py +5 -5
  154. reconcile/gql_definitions/fragments/upgrade_policy.py +5 -5
  155. reconcile/gql_definitions/fragments/user.py +5 -5
  156. reconcile/gql_definitions/fragments/vault_secret.py +5 -5
  157. reconcile/gql_definitions/gcp/gcp_docker_repos.py +5 -5
  158. reconcile/gql_definitions/gcp/gcp_projects.py +5 -5
  159. reconcile/gql_definitions/gitlab_members/gitlab_instances.py +5 -5
  160. reconcile/gql_definitions/gitlab_members/permissions.py +5 -5
  161. reconcile/gql_definitions/glitchtip/glitchtip_instance.py +5 -5
  162. reconcile/gql_definitions/glitchtip/glitchtip_project.py +5 -5
  163. reconcile/gql_definitions/glitchtip_project_alerts/glitchtip_project.py +5 -5
  164. reconcile/gql_definitions/integrations/integrations.py +5 -5
  165. reconcile/gql_definitions/introspection.json +2137 -1053
  166. reconcile/gql_definitions/jenkins_configs/jenkins_configs.py +5 -5
  167. reconcile/gql_definitions/jenkins_configs/jenkins_instances.py +5 -5
  168. reconcile/gql_definitions/jira/jira_servers.py +5 -5
  169. reconcile/gql_definitions/jira_permissions_validator/jira_boards_for_permissions_validator.py +9 -5
  170. reconcile/gql_definitions/jumphosts/jumphosts.py +5 -5
  171. reconcile/gql_definitions/ldap_groups/roles.py +5 -5
  172. reconcile/gql_definitions/ldap_groups/settings.py +5 -5
  173. reconcile/gql_definitions/maintenance/maintenances.py +5 -5
  174. reconcile/gql_definitions/membershipsources/roles.py +5 -5
  175. reconcile/gql_definitions/ocm_labels/clusters.py +5 -5
  176. reconcile/gql_definitions/ocm_labels/organizations.py +5 -5
  177. reconcile/gql_definitions/openshift_cluster_bots/clusters.py +5 -5
  178. reconcile/gql_definitions/openshift_groups/managed_groups.py +5 -5
  179. reconcile/gql_definitions/openshift_groups/managed_roles.py +5 -5
  180. reconcile/gql_definitions/openshift_serviceaccount_tokens/tokens.py +5 -5
  181. reconcile/gql_definitions/quay_membership/quay_membership.py +5 -5
  182. reconcile/gql_definitions/rhcs/certs.py +5 -5
  183. reconcile/gql_definitions/rhidp/organizations.py +5 -5
  184. reconcile/gql_definitions/service_dependencies/jenkins_instance_fragment.py +5 -5
  185. reconcile/gql_definitions/service_dependencies/service_dependencies.py +5 -5
  186. reconcile/gql_definitions/sharding/aws_accounts.py +5 -5
  187. reconcile/gql_definitions/sharding/ocm_organization.py +5 -5
  188. reconcile/gql_definitions/skupper_network/site_controller_template.py +5 -5
  189. reconcile/gql_definitions/skupper_network/skupper_networks.py +5 -5
  190. reconcile/gql_definitions/slack_usergroups/clusters.py +5 -5
  191. reconcile/gql_definitions/slack_usergroups/permissions.py +5 -5
  192. reconcile/gql_definitions/slack_usergroups/users.py +5 -5
  193. reconcile/gql_definitions/slo_documents/slo_documents.py +5 -5
  194. reconcile/gql_definitions/status_board/status_board.py +5 -5
  195. reconcile/gql_definitions/statuspage/statuspages.py +5 -5
  196. reconcile/gql_definitions/templating/template_collection.py +5 -5
  197. reconcile/gql_definitions/templating/templates.py +5 -5
  198. reconcile/gql_definitions/terraform_cloudflare_dns/app_interface_cloudflare_dns_settings.py +5 -5
  199. reconcile/gql_definitions/terraform_cloudflare_dns/terraform_cloudflare_zones.py +5 -5
  200. reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_accounts.py +5 -5
  201. reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_resources.py +5 -5
  202. reconcile/gql_definitions/terraform_cloudflare_users/app_interface_setting_cloudflare_and_vault.py +5 -5
  203. reconcile/gql_definitions/terraform_cloudflare_users/terraform_cloudflare_roles.py +5 -5
  204. reconcile/gql_definitions/terraform_init/aws_accounts.py +19 -5
  205. reconcile/gql_definitions/terraform_repo/terraform_repo.py +5 -5
  206. reconcile/gql_definitions/terraform_resources/database_access_manager.py +5 -5
  207. reconcile/gql_definitions/terraform_resources/terraform_resources_namespaces.py +38 -6
  208. reconcile/gql_definitions/terraform_tgw_attachments/aws_accounts.py +15 -5
  209. reconcile/gql_definitions/unleash_feature_toggles/feature_toggles.py +5 -5
  210. reconcile/gql_definitions/vault_instances/vault_instances.py +5 -5
  211. reconcile/gql_definitions/vault_policies/vault_policies.py +5 -5
  212. reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator.py +5 -5
  213. reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator_peered_cluster_fragment.py +5 -5
  214. reconcile/integrations_manager.py +3 -3
  215. reconcile/jenkins_worker_fleets.py +10 -8
  216. reconcile/jira_permissions_validator.py +237 -122
  217. reconcile/ldap_groups/integration.py +1 -1
  218. reconcile/ocm/types.py +35 -56
  219. reconcile/ocm_aws_infrastructure_access.py +1 -1
  220. reconcile/ocm_clusters.py +4 -4
  221. reconcile/ocm_labels/integration.py +3 -2
  222. reconcile/ocm_machine_pools.py +23 -23
  223. reconcile/openshift_base.py +53 -2
  224. reconcile/openshift_cluster_bots.py +3 -2
  225. reconcile/openshift_namespace_labels.py +1 -1
  226. reconcile/openshift_namespaces.py +97 -101
  227. reconcile/openshift_resources_base.py +6 -2
  228. reconcile/openshift_rhcs_certs.py +5 -5
  229. reconcile/openshift_rolebindings.py +7 -11
  230. reconcile/openshift_saas_deploy.py +6 -7
  231. reconcile/openshift_saas_deploy_change_tester.py +9 -7
  232. reconcile/openshift_saas_deploy_trigger_cleaner.py +3 -5
  233. reconcile/openshift_serviceaccount_tokens.py +2 -2
  234. reconcile/openshift_upgrade_watcher.py +4 -4
  235. reconcile/oum/labelset.py +5 -3
  236. reconcile/oum/models.py +1 -4
  237. reconcile/prometheus_rules_tester/integration.py +3 -3
  238. reconcile/quay_mirror.py +1 -1
  239. reconcile/queries.py +131 -1
  240. reconcile/rhidp/common.py +3 -5
  241. reconcile/rhidp/sso_client/base.py +1 -1
  242. reconcile/saas_auto_promotions_manager/merge_request_manager/renderer.py +1 -1
  243. reconcile/saas_auto_promotions_manager/subscriber.py +4 -3
  244. reconcile/skupper_network/integration.py +2 -2
  245. reconcile/slack_usergroups.py +35 -14
  246. reconcile/sql_query.py +1 -0
  247. reconcile/status_board.py +6 -6
  248. reconcile/statuspage/atlassian.py +7 -7
  249. reconcile/statuspage/integrations/maintenances.py +4 -3
  250. reconcile/statuspage/page.py +4 -9
  251. reconcile/statuspage/status.py +5 -8
  252. reconcile/templates/rosa-classic-cluster-creation.sh.j2 +4 -0
  253. reconcile/templates/rosa-hcp-cluster-creation.sh.j2 +3 -0
  254. reconcile/templating/lib/rendering.py +3 -3
  255. reconcile/templating/renderer.py +4 -3
  256. reconcile/terraform_aws_route53.py +7 -1
  257. reconcile/terraform_cloudflare_dns.py +3 -3
  258. reconcile/terraform_cloudflare_resources.py +5 -5
  259. reconcile/terraform_cloudflare_users.py +3 -2
  260. reconcile/terraform_init/integration.py +187 -23
  261. reconcile/terraform_repo.py +16 -12
  262. reconcile/terraform_resources.py +17 -7
  263. reconcile/terraform_tgw_attachments.py +27 -19
  264. reconcile/terraform_users.py +7 -0
  265. reconcile/terraform_vpc_peerings.py +14 -3
  266. reconcile/terraform_vpc_resources/integration.py +10 -1
  267. reconcile/typed_queries/aws_account_tags.py +41 -0
  268. reconcile/typed_queries/cost_report/app_names.py +1 -1
  269. reconcile/typed_queries/cost_report/cost_namespaces.py +2 -2
  270. reconcile/typed_queries/saas_files.py +13 -13
  271. reconcile/typed_queries/status_board.py +2 -2
  272. reconcile/unleash_feature_toggles/integration.py +4 -2
  273. reconcile/utils/acs/base.py +6 -3
  274. reconcile/utils/acs/policies.py +2 -2
  275. reconcile/utils/aggregated_list.py +4 -3
  276. reconcile/utils/aws_api.py +51 -54
  277. reconcile/utils/aws_api_typed/api.py +38 -9
  278. reconcile/utils/aws_api_typed/cloudformation.py +149 -0
  279. reconcile/utils/aws_api_typed/logs.py +73 -0
  280. reconcile/utils/aws_api_typed/organization.py +4 -2
  281. reconcile/utils/datetime_util.py +67 -0
  282. reconcile/utils/deadmanssnitch_api.py +1 -1
  283. reconcile/utils/differ.py +2 -3
  284. reconcile/utils/early_exit_cache.py +11 -12
  285. reconcile/utils/expiration.py +7 -3
  286. reconcile/utils/external_resource_spec.py +24 -1
  287. reconcile/utils/filtering.py +1 -1
  288. reconcile/utils/gitlab_api.py +7 -5
  289. reconcile/utils/glitchtip/client.py +6 -2
  290. reconcile/utils/glitchtip/models.py +25 -28
  291. reconcile/utils/gql.py +4 -7
  292. reconcile/utils/helm.py +2 -1
  293. reconcile/utils/helpers.py +1 -1
  294. reconcile/utils/instrumented_wrappers.py +1 -1
  295. reconcile/utils/internal_groups/client.py +2 -2
  296. reconcile/utils/internal_groups/models.py +8 -17
  297. reconcile/utils/jinja2/utils.py +6 -101
  298. reconcile/utils/jira_client.py +82 -63
  299. reconcile/utils/jjb_client.py +9 -12
  300. reconcile/utils/jobcontroller/controller.py +1 -1
  301. reconcile/utils/jobcontroller/models.py +17 -1
  302. reconcile/utils/json.py +70 -0
  303. reconcile/utils/membershipsources/app_interface_resolver.py +4 -2
  304. reconcile/utils/membershipsources/models.py +16 -23
  305. reconcile/utils/membershipsources/resolver.py +4 -2
  306. reconcile/utils/merge_request_manager/merge_request_manager.py +4 -4
  307. reconcile/utils/merge_request_manager/parser.py +6 -6
  308. reconcile/utils/metrics.py +5 -5
  309. reconcile/utils/models.py +304 -82
  310. reconcile/utils/mr/app_interface_reporter.py +2 -2
  311. reconcile/utils/mr/base.py +2 -2
  312. reconcile/utils/mr/notificator.py +3 -3
  313. reconcile/utils/mr/update_access_report_base.py +3 -4
  314. reconcile/utils/mr/user_maintenance.py +3 -2
  315. reconcile/utils/oc.py +118 -97
  316. reconcile/utils/oc_filters.py +3 -3
  317. reconcile/utils/ocm/addons.py +0 -1
  318. reconcile/utils/ocm/base.py +17 -20
  319. reconcile/utils/ocm/cluster_groups.py +1 -1
  320. reconcile/utils/ocm/identity_providers.py +2 -2
  321. reconcile/utils/ocm/labels.py +1 -1
  322. reconcile/utils/ocm/products.py +9 -3
  323. reconcile/utils/ocm/search_filters.py +3 -6
  324. reconcile/utils/ocm/service_log.py +4 -6
  325. reconcile/utils/ocm/sre_capability_labels.py +20 -13
  326. reconcile/utils/openshift_resource.py +10 -5
  327. reconcile/utils/output.py +3 -2
  328. reconcile/utils/pagerduty_api.py +10 -7
  329. reconcile/utils/promotion_state.py +6 -11
  330. reconcile/utils/raw_github_api.py +1 -1
  331. reconcile/utils/rhcsv2_certs.py +1 -4
  332. reconcile/utils/runtime/integration.py +2 -3
  333. reconcile/utils/runtime/runner.py +2 -2
  334. reconcile/utils/saasherder/interfaces.py +13 -20
  335. reconcile/utils/saasherder/models.py +25 -21
  336. reconcile/utils/saasherder/saasherder.py +35 -24
  337. reconcile/utils/slack_api.py +26 -4
  338. reconcile/utils/sloth.py +171 -2
  339. reconcile/utils/sqs_gateway.py +2 -1
  340. reconcile/utils/state.py +2 -1
  341. reconcile/utils/structs.py +1 -1
  342. reconcile/utils/terraform_client.py +5 -4
  343. reconcile/utils/terrascript_aws_client.py +171 -114
  344. reconcile/utils/unleash/server.py +2 -8
  345. reconcile/utils/vault.py +5 -12
  346. reconcile/utils/vcs.py +8 -8
  347. reconcile/vault_replication.py +107 -42
  348. tools/app_interface_reporter.py +4 -4
  349. tools/cli_commands/cost_report/cost_management_api.py +3 -3
  350. tools/cli_commands/cost_report/view.py +7 -6
  351. tools/cli_commands/erv2.py +3 -1
  352. tools/cli_commands/systems_and_tools.py +5 -1
  353. tools/qontract_cli.py +31 -18
  354. tools/template_validation.py +3 -1
  355. {qontract_reconcile-0.10.2.dev349.dist-info → qontract_reconcile-0.10.2.dev414.dist-info}/WHEEL +0 -0
  356. {qontract_reconcile-0.10.2.dev349.dist-info → qontract_reconcile-0.10.2.dev414.dist-info}/entry_points.txt +0 -0
@@ -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,7 @@ VARIABLE_KEYS = [
267
271
  "extra_tags",
268
272
  "lifecycle",
269
273
  "max_session_duration",
274
+ "secret_format",
270
275
  ]
271
276
 
272
277
  EMAIL_REGEX = re.compile(r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$")
@@ -286,12 +291,6 @@ SUPPORTED_ALB_LISTENER_RULE_CONDITION_TYPE_MAPPING = {
286
291
  "source-ip": "source_ip",
287
292
  }
288
293
 
289
- DEFAULT_TAGS = {
290
- "tags": {
291
- "app": "app-sre-infra",
292
- },
293
- }
294
-
295
294
  AWS_ELB_ACCOUNT_IDS = {
296
295
  "us-east-1": "127311923021",
297
296
  "us-east-2": "033677994240",
@@ -475,6 +474,7 @@ class TerrascriptClient:
475
474
  integration_prefix: str,
476
475
  thread_pool_size: int,
477
476
  accounts: Iterable[MutableMapping[str, Any]],
477
+ default_tags: Mapping[str, str] | None,
478
478
  settings: Mapping[str, Any] | None = None,
479
479
  prefetch_resources_by_schemas: Iterable[str] | None = None,
480
480
  secret_reader: SecretReaderBase | None = None,
@@ -488,6 +488,7 @@ class TerrascriptClient:
488
488
  else:
489
489
  self.secret_reader = SecretReader(settings=settings)
490
490
  self.configs: dict[str, dict] = {}
491
+ self.default_tags = default_tags or {"app": "app-sre-infra"}
491
492
  self.populate_configs(filtered_accounts)
492
493
  self.versions: dict[str, str] = {
493
494
  a["name"]: a["providerVersion"] for a in filtered_accounts
@@ -508,7 +509,7 @@ class TerrascriptClient:
508
509
  region=region,
509
510
  alias=region,
510
511
  skip_region_validation=True,
511
- default_tags=DEFAULT_TAGS,
512
+ default_tags={"tags": config["tags"]},
512
513
  )
513
514
 
514
515
  # Add default region, which will be in resourcesDefaultRegion
@@ -517,7 +518,7 @@ class TerrascriptClient:
517
518
  secret_key=config["aws_secret_access_key"],
518
519
  region=config["resourcesDefaultRegion"],
519
520
  skip_region_validation=True,
520
- default_tags=DEFAULT_TAGS,
521
+ default_tags={"tags": config["tags"]},
521
522
  )
522
523
 
523
524
  ts += Terraform(
@@ -800,6 +801,9 @@ class TerrascriptClient:
800
801
  config["supportedDeploymentRegions"] = account["supportedDeploymentRegions"]
801
802
  config["resourcesDefaultRegion"] = account["resourcesDefaultRegion"]
802
803
  config["terraformState"] = account["terraformState"]
804
+ config["tags"] = dict(self.default_tags) | get_aws_account_tags(
805
+ account.get("organization", None)
806
+ )
803
807
  self.configs[account_name] = config
804
808
 
805
809
  def _get_partition(self, account: str) -> str:
@@ -1054,7 +1058,9 @@ class TerrascriptClient:
1054
1058
  ignore_changes = (
1055
1059
  "all" if "all" in lifecycle.ignore_changes else lifecycle.ignore_changes
1056
1060
  )
1057
- return lifecycle.dict(by_alias=True) | {"ignore_changes": ignore_changes}
1061
+ return lifecycle.model_dump(by_alias=True) | {
1062
+ "ignore_changes": ignore_changes
1063
+ }
1058
1064
  return None
1059
1065
 
1060
1066
  def populate_additional_providers(
@@ -1069,25 +1075,15 @@ class TerrascriptClient:
1069
1075
  config = self.configs[account_name]
1070
1076
  existing_provider_aliases = {p.get("alias") for p in ts["provider"]["aws"]}
1071
1077
  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
- )
1078
+ ts += provider.aws(
1079
+ access_key=config["aws_access_key_id"],
1080
+ secret_key=config["aws_secret_access_key"],
1081
+ region=region,
1082
+ alias=alias,
1083
+ skip_region_validation=True,
1084
+ default_tags={"tags": config["tags"]},
1085
+ **{"assume_role": {"role_arn": assume_role}} if assume_role else {},
1086
+ )
1091
1087
 
1092
1088
  def populate_route53(
1093
1089
  self, desired_state: Iterable[Mapping[str, Any]], default_ttl: int = 300
@@ -1430,7 +1426,7 @@ class TerrascriptClient:
1430
1426
  req_account_name = req_account.name
1431
1427
  # Accepter's side of the connection - the cluster's account
1432
1428
  acc_account = accepter.account
1433
- acc_alias = self.get_provider_alias(acc_account.dict(by_alias=True))
1429
+ acc_alias = self.get_provider_alias(acc_account.model_dump(by_alias=True))
1434
1430
  acc_uid = acc_account.uid
1435
1431
  if acc_account.assume_role:
1436
1432
  acc_uid = awsh.get_account_uid_from_arn(acc_account.assume_role)
@@ -1947,7 +1943,7 @@ class TerrascriptClient:
1947
1943
  em_identifier = f"{identifier}-enhanced-monitoring"
1948
1944
  em_values = {
1949
1945
  "name": em_identifier,
1950
- "assume_role_policy": json.dumps(assume_role_policy, sort_keys=True),
1946
+ "assume_role_policy": json_dumps(assume_role_policy),
1951
1947
  }
1952
1948
  role_tf_resource = aws_iam_role(em_identifier, **em_values)
1953
1949
  tf_resources.append(role_tf_resource)
@@ -2216,6 +2212,43 @@ class TerrascriptClient:
2216
2212
  letters_and_digits = string.ascii_letters + string.digits
2217
2213
  return "".join(random.choice(letters_and_digits) for i in range(string_length))
2218
2214
 
2215
+ @staticmethod
2216
+ def _build_tf_resource_s3_lifecycle_rules(
2217
+ versioning: bool,
2218
+ common_values: Mapping[str, Any],
2219
+ ) -> list[dict]:
2220
+ lifecycle_rules = common_values.get("lifecycle_rules") or []
2221
+ if versioning and not any(
2222
+ "noncurrent_version_expiration" in lr for lr in lifecycle_rules
2223
+ ):
2224
+ # Add a default noncurrent object expiration rule
2225
+ # if one isn't already set
2226
+ rule = {
2227
+ "id": "expire_noncurrent_versions",
2228
+ "enabled": True,
2229
+ "noncurrent_version_expiration": {"days": 30},
2230
+ "expiration": {"expired_object_delete_marker": True},
2231
+ "abort_incomplete_multipart_upload_days": 3,
2232
+ }
2233
+ lifecycle_rules.append(rule)
2234
+
2235
+ if storage_class := common_values.get("storage_class"):
2236
+ sc = storage_class.upper()
2237
+ days = "1"
2238
+ if sc.endswith("_IA"):
2239
+ # Infrequent Access storage class has minimum 30 days
2240
+ # before transition
2241
+ days = "30"
2242
+ rule = {
2243
+ "id": sc + "_storage_class",
2244
+ "enabled": True,
2245
+ "transition": {"days": days, "storage_class": sc},
2246
+ "noncurrent_version_transition": {"days": days, "storage_class": sc},
2247
+ }
2248
+ lifecycle_rules.append(rule)
2249
+
2250
+ return lifecycle_rules
2251
+
2219
2252
  def populate_tf_resource_s3(self, spec: ExternalResourceSpec) -> aws_s3_bucket:
2220
2253
  account = spec.provisioner_name
2221
2254
  identifier = spec.identifier
@@ -2255,47 +2288,11 @@ class TerrascriptClient:
2255
2288
  request_payer = common_values.get("request_payer")
2256
2289
  if request_payer:
2257
2290
  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
2291
+ if lifecycle_rules := self._build_tf_resource_s3_lifecycle_rules(
2292
+ versioning=versioning,
2293
+ common_values=common_values,
2294
+ ):
2261
2295
  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
2296
  cors_rules = common_values.get("cors_rules")
2300
2297
  if cors_rules:
2301
2298
  # common_values['cors_rules'] is a list of cors_rules
@@ -2345,7 +2342,7 @@ class TerrascriptClient:
2345
2342
  }
2346
2343
  ],
2347
2344
  }
2348
- rc_values["assume_role_policy"] = json.dumps(role, sort_keys=True)
2345
+ rc_values["assume_role_policy"] = json_dumps(role)
2349
2346
  role_resource = aws_iam_role(id, **rc_values)
2350
2347
  tf_resources.append(role_resource)
2351
2348
 
@@ -2383,7 +2380,7 @@ class TerrascriptClient:
2383
2380
  },
2384
2381
  ],
2385
2382
  }
2386
- rc_values["policy"] = json.dumps(policy, sort_keys=True)
2383
+ rc_values["policy"] = json_dumps(policy)
2387
2384
  policy_resource = aws_iam_policy(id, **rc_values)
2388
2385
  tf_resources.append(policy_resource)
2389
2386
 
@@ -2598,7 +2595,7 @@ class TerrascriptClient:
2598
2595
  },
2599
2596
  ],
2600
2597
  }
2601
- values["policy"] = json.dumps(policy, sort_keys=True)
2598
+ values["policy"] = json_dumps(policy)
2602
2599
  values["depends_on"] = self.get_dependencies([user_tf_resource])
2603
2600
 
2604
2601
  tf_aws_iam_policy = aws_iam_policy(identifier, **values)
@@ -2877,7 +2874,7 @@ class TerrascriptClient:
2877
2874
  values: dict[str, Any] = {
2878
2875
  "name": identifier,
2879
2876
  "tags": common_values["tags"],
2880
- "assume_role_policy": json.dumps(assume_role_policy),
2877
+ "assume_role_policy": json_dumps(assume_role_policy),
2881
2878
  }
2882
2879
 
2883
2880
  inline_policy = common_values.get("inline_policy")
@@ -2931,7 +2928,7 @@ class TerrascriptClient:
2931
2928
  self, account: str, name: str, policy: Mapping[str, Any]
2932
2929
  ) -> None:
2933
2930
  tf_aws_iam_policy = aws_iam_policy(
2934
- f"{account}-{name}", name=name, policy=json.dumps(policy)
2931
+ f"{account}-{name}", name=name, policy=json_dumps(policy)
2935
2932
  )
2936
2933
  self.add_resource(account, tf_aws_iam_policy)
2937
2934
 
@@ -2973,7 +2970,7 @@ class TerrascriptClient:
2973
2970
  role_tf_resource = aws_iam_role(
2974
2971
  f"{account}-{name}",
2975
2972
  name=name,
2976
- assume_role_policy=json.dumps(assume_role_policy),
2973
+ assume_role_policy=json_dumps(assume_role_policy),
2977
2974
  managed_policy_arns=managed_policy_arns,
2978
2975
  max_session_duration=max_session_duration_hours * 3600,
2979
2976
  )
@@ -3017,7 +3014,7 @@ class TerrascriptClient:
3017
3014
  all_queues.append(queue_name)
3018
3015
  sqs_policy = values.pop("sqs_policy", None)
3019
3016
  if sqs_policy is not None:
3020
- values["policy"] = json.dumps(sqs_policy, sort_keys=True)
3017
+ values["policy"] = json_dumps(sqs_policy)
3021
3018
  dl_queue = values.pop("dl_queue", None)
3022
3019
  if dl_queue is not None:
3023
3020
  max_receive_count = int(values.pop("max_receive_count", 10))
@@ -3031,9 +3028,7 @@ class TerrascriptClient:
3031
3028
  "deadLetterTargetArn": "${" + dl_data.arn + "}",
3032
3029
  "maxReceiveCount": max_receive_count,
3033
3030
  }
3034
- values["redrive_policy"] = json.dumps(
3035
- redrive_policy, sort_keys=True
3036
- )
3031
+ values["redrive_policy"] = json_dumps(redrive_policy)
3037
3032
  kms_master_key_id = values.pop("kms_master_key_id", None)
3038
3033
  if kms_master_key_id is not None:
3039
3034
  if kms_master_key_id.startswith("arn:"):
@@ -3106,7 +3101,7 @@ class TerrascriptClient:
3106
3101
  "Resource": list(kms_keys),
3107
3102
  }
3108
3103
  policy["Statement"].append(kms_statement)
3109
- values["policy"] = json.dumps(policy, sort_keys=True)
3104
+ values["policy"] = json_dumps(policy)
3110
3105
  policy_tf_resource = aws_iam_policy(policy_identifier, **values)
3111
3106
  tf_resources.append(policy_tf_resource)
3112
3107
 
@@ -3256,7 +3251,7 @@ class TerrascriptClient:
3256
3251
  }
3257
3252
  ],
3258
3253
  }
3259
- values["policy"] = json.dumps(policy, sort_keys=True)
3254
+ values["policy"] = json_dumps(policy)
3260
3255
  values["depends_on"] = self.get_dependencies([user_tf_resource])
3261
3256
 
3262
3257
  tf_aws_iam_policy = aws_iam_policy(identifier, **values)
@@ -3366,7 +3361,7 @@ class TerrascriptClient:
3366
3361
  },
3367
3362
  ],
3368
3363
  }
3369
- values_policy["policy"] = json.dumps(policy, sort_keys=True)
3364
+ values_policy["policy"] = json_dumps(policy)
3370
3365
  values_policy["depends_on"] = self.get_dependencies([user_tf_resource])
3371
3366
 
3372
3367
  tf_aws_iam_policy = aws_iam_policy(identifier, **values_policy)
@@ -3416,7 +3411,7 @@ class TerrascriptClient:
3416
3411
  }
3417
3412
  ],
3418
3413
  }
3419
- values_policy["policy"] = json.dumps(policy, sort_keys=True)
3414
+ values_policy["policy"] = json_dumps(policy)
3420
3415
  values_policy["depends_on"] = self.get_dependencies([bucket_tf_resource])
3421
3416
  region = common_values.get("region") or self.default_regions.get(account)
3422
3417
  assert region # make mypy happy
@@ -3562,7 +3557,7 @@ class TerrascriptClient:
3562
3557
  }
3563
3558
  ],
3564
3559
  }
3565
- sqs_values["policy"] = json.dumps(sqs_policy, sort_keys=True)
3560
+ sqs_values["policy"] = json_dumps(sqs_policy)
3566
3561
 
3567
3562
  kms_encryption = common_values.get("kms_encryption", False)
3568
3563
  if kms_encryption:
@@ -3598,7 +3593,7 @@ class TerrascriptClient:
3598
3593
  },
3599
3594
  ],
3600
3595
  }
3601
- kms_values["policy"] = json.dumps(kms_policy, sort_keys=True)
3596
+ kms_values["policy"] = json_dumps(kms_policy)
3602
3597
  if provider:
3603
3598
  kms_values["provider"] = provider
3604
3599
 
@@ -3696,7 +3691,7 @@ class TerrascriptClient:
3696
3691
  "Resource": [sqs_values["kms_master_key_id"]],
3697
3692
  }
3698
3693
  policy["Statement"].append(kms_statement)
3699
- values_policy["policy"] = json.dumps(policy, sort_keys=True)
3694
+ values_policy["policy"] = json_dumps(policy)
3700
3695
  policy_tf_resource = aws_iam_policy(sqs_identifier, **values_policy)
3701
3696
  tf_resources.append(policy_tf_resource)
3702
3697
 
@@ -3767,7 +3762,7 @@ class TerrascriptClient:
3767
3762
  role_identifier = f"{identifier}-lambda-execution-role"
3768
3763
  role_values = {
3769
3764
  "name": role_identifier,
3770
- "assume_role_policy": json.dumps(assume_role_policy, sort_keys=True),
3765
+ "assume_role_policy": json_dumps(assume_role_policy),
3771
3766
  }
3772
3767
 
3773
3768
  role_tf_resource = aws_iam_role(role_identifier, **role_values)
@@ -3799,7 +3794,7 @@ class TerrascriptClient:
3799
3794
 
3800
3795
  policy_values = {
3801
3796
  "role": "${" + role_tf_resource.id + "}",
3802
- "policy": json.dumps(policy, sort_keys=True),
3797
+ "policy": json_dumps(policy),
3803
3798
  }
3804
3799
  policy_tf_resource = aws_iam_role_policy(policy_identifier, **policy_values)
3805
3800
  tf_resources.append(policy_tf_resource)
@@ -3927,7 +3922,7 @@ class TerrascriptClient:
3927
3922
  }
3928
3923
  values = {
3929
3924
  "name": identifier,
3930
- "policy": json.dumps(policy, sort_keys=True),
3925
+ "policy": json_dumps(policy),
3931
3926
  "depends_on": self.get_dependencies([user_tf_resource]),
3932
3927
  }
3933
3928
 
@@ -4048,7 +4043,7 @@ class TerrascriptClient:
4048
4043
  role_identifier = f"{identifier}-lambda-execution-role"
4049
4044
  role_values = {
4050
4045
  "name": role_identifier,
4051
- "assume_role_policy": json.dumps(assume_role_policy, sort_keys=True),
4046
+ "assume_role_policy": json_dumps(assume_role_policy),
4052
4047
  "tags": tags,
4053
4048
  }
4054
4049
 
@@ -4085,7 +4080,7 @@ class TerrascriptClient:
4085
4080
  policy_tf_resource = aws_iam_policy(
4086
4081
  policy_identifier,
4087
4082
  name=policy_identifier,
4088
- policy=json.dumps(policy, sort_keys=True),
4083
+ policy=json_dumps(policy),
4089
4084
  tags=tags,
4090
4085
  )
4091
4086
  tf_resources.append(policy_tf_resource)
@@ -4300,7 +4295,7 @@ class TerrascriptClient:
4300
4295
  # iam user policy
4301
4296
  values_policy: dict[str, Any] = {
4302
4297
  "name": identifier,
4303
- "policy": json.dumps(policy, sort_keys=True),
4298
+ "policy": json_dumps(policy),
4304
4299
  "depends_on": self.get_dependencies([user_tf_resource]),
4305
4300
  }
4306
4301
 
@@ -4418,10 +4413,7 @@ class TerrascriptClient:
4418
4413
 
4419
4414
  :return: key is AWS account name and value is terraform configuration
4420
4415
  """
4421
- return {
4422
- name: json.dumps(ts, indent=2, sort_keys=True)
4423
- for name, ts in self.tss.items()
4424
- }
4416
+ return {name: json_dumps(ts, indent=2) for name, ts in self.tss.items()}
4425
4417
 
4426
4418
  def init_values(
4427
4419
  self, spec: ExternalResourceSpec, init_tags: bool = True
@@ -4533,7 +4525,7 @@ class TerrascriptClient:
4533
4525
  output_name = output_format.format(
4534
4526
  spec.output_prefix, self.integration_prefix, "annotations"
4535
4527
  )
4536
- anno_json = json.dumps(spec.annotations()).encode("utf-8")
4528
+ anno_json = json_dumps(spec.annotations()).encode("utf-8")
4537
4529
  output_value = base64.b64encode(anno_json).decode()
4538
4530
  tf_resources.append(Output(output_name, value=output_value))
4539
4531
 
@@ -4673,7 +4665,7 @@ class TerrascriptClient:
4673
4665
  }
4674
4666
  log_groups_policy_values = {
4675
4667
  "policy_name": "es-log-publishing-permissions",
4676
- "policy_document": json.dumps(log_groups_policy, sort_keys=True),
4668
+ "policy_document": json_dumps(log_groups_policy),
4677
4669
  }
4678
4670
  resource_policy = aws_cloudwatch_log_resource_policy(
4679
4671
  "es_log_publishing_resource_policy",
@@ -5005,7 +4997,7 @@ class TerrascriptClient:
5005
4997
  }
5006
4998
  ],
5007
4999
  }
5008
- es_values["access_policies"] = json.dumps(access_policies, sort_keys=True)
5000
+ es_values["access_policies"] = json_dumps(access_policies)
5009
5001
 
5010
5002
  region = values.get("region") or self.default_regions.get(account)
5011
5003
  assert region # make mypy happy
@@ -5061,7 +5053,7 @@ class TerrascriptClient:
5061
5053
 
5062
5054
  version_values = {
5063
5055
  "secret_id": "${" + aws_secret_resource.id + "}",
5064
- "secret_string": json.dumps(master_user, sort_keys=True),
5056
+ "secret_string": json_dumps(master_user),
5065
5057
  }
5066
5058
  if provider:
5067
5059
  version_values["provider"] = provider
@@ -5088,7 +5080,7 @@ class TerrascriptClient:
5088
5080
  iam_policy_resource = aws_iam_policy(
5089
5081
  secret_identifier,
5090
5082
  name=f"{identifier}-secretsmanager-policy",
5091
- policy=json.dumps(policy, sort_keys=True),
5083
+ policy=json_dumps(policy),
5092
5084
  tags=tags,
5093
5085
  )
5094
5086
  tf_resources.append(iam_policy_resource)
@@ -5500,7 +5492,7 @@ class TerrascriptClient:
5500
5492
  lb_access_logs_s3_bucket_policy_values = {
5501
5493
  "provider": provider,
5502
5494
  "bucket": f"${{{lb_access_logs_s3_bucket_tf_resource.id}}}",
5503
- "policy": json.dumps(policy, sort_keys=True),
5495
+ "policy": json_dumps(policy),
5504
5496
  }
5505
5497
  lb_access_logs_s3_bucket_policy_tf_resource = aws_s3_bucket_policy(
5506
5498
  policy_identifier, **lb_access_logs_s3_bucket_policy_values
@@ -5813,9 +5805,13 @@ class TerrascriptClient:
5813
5805
  assert secret # make mypy happy
5814
5806
  secret_data = self.secret_reader.read_all(secret)
5815
5807
 
5808
+ secret_format = common_values.get("secret_format")
5809
+ if secret_format is not None:
5810
+ secret_data = self._apply_secret_format(str(secret_format), secret_data)
5811
+
5816
5812
  version_values: dict[str, Any] = {
5817
5813
  "secret_id": "${" + aws_secret_resource.id + "}",
5818
- "secret_string": json.dumps(secret_data, sort_keys=True),
5814
+ "secret_string": json_dumps(secret_data),
5819
5815
  }
5820
5816
 
5821
5817
  if self._multiregion_account(account):
@@ -5836,6 +5832,66 @@ class TerrascriptClient:
5836
5832
 
5837
5833
  self.add_resources(account, tf_resources)
5838
5834
 
5835
+ @staticmethod
5836
+ def _unflatten_dotted_keys_dict(flat_dict: dict[str, str]) -> dict[str, Any]:
5837
+ """Convert a flat dictionary with dotted keys to a nested dictionary.
5838
+
5839
+ Example:
5840
+ {"db.host": "localhost", "db.port": "5432"} ->
5841
+ {"db": {"host": "localhost", "port": "5432"}}
5842
+
5843
+ Raises:
5844
+ ValueError: If there are conflicting keys (e.g., "a.b" and "a.b.c")
5845
+ """
5846
+ result: dict[str, Any] = {}
5847
+ for key, value in flat_dict.items():
5848
+ parts = key.split(".")
5849
+ current = result
5850
+ for i, part in enumerate(parts[:-1]):
5851
+ if part not in current:
5852
+ current[part] = {}
5853
+ elif not isinstance(current[part], dict):
5854
+ # Conflict: trying to traverse through a non-dict value
5855
+ conflicting_path = ".".join(parts[: i + 1])
5856
+ raise ValueError(
5857
+ f"Conflicting keys detected: '{conflicting_path}' is both a "
5858
+ f"value and a nested path in key '{key}'"
5859
+ )
5860
+ current = current[part]
5861
+
5862
+ # Check if we're trying to set a value where a dict already exists
5863
+ if parts[-1] in current and isinstance(current[parts[-1]], dict):
5864
+ raise ValueError(
5865
+ f"Conflicting keys detected: '{key}' conflicts with nested keys"
5866
+ )
5867
+
5868
+ current[parts[-1]] = value
5869
+
5870
+ return result
5871
+
5872
+ @staticmethod
5873
+ def _apply_secret_format(
5874
+ secret_format: str, secret_data: dict[str, str]
5875
+ ) -> dict[str, str]:
5876
+ # Convert flat dict with dotted keys to nested dict for Jinja2
5877
+ nested_secret_data = TerrascriptClient._unflatten_dotted_keys_dict(secret_data)
5878
+ rendered_data = process_jinja2_template(secret_format, nested_secret_data)
5879
+
5880
+ parsed_data = json.loads(rendered_data)
5881
+
5882
+ if not isinstance(parsed_data, dict):
5883
+ raise ValueError("secret_format must be a dictionary")
5884
+
5885
+ # validate secret is a dict[str, str]
5886
+ for k, v in parsed_data.items():
5887
+ if not isinstance(k, str):
5888
+ raise ValueError(f"key '{k}' is not a string")
5889
+
5890
+ if not isinstance(v, str):
5891
+ raise ValueError(f"dictionary value '{v}' under '{k}' is not a string")
5892
+
5893
+ return parsed_data
5894
+
5839
5895
  def get_commit_sha(self, repo_info: Mapping) -> str:
5840
5896
  url = repo_info["url"]
5841
5897
  ref = repo_info["ref"]
@@ -5854,7 +5910,8 @@ class TerrascriptClient:
5854
5910
  return commit.sha
5855
5911
  case "gitlab":
5856
5912
  gitlab = self.init_gitlab()
5857
- project = gitlab.get_project(url)
5913
+ if not (project := gitlab.get_project(url)):
5914
+ raise ValueError(f"could not find gitlab project for url {url}")
5858
5915
  commits = project.commits.list(ref_name=ref, per_page=1, page=1)
5859
5916
  return commits[0].id
5860
5917
  case _:
@@ -6135,7 +6192,7 @@ class TerrascriptClient:
6135
6192
  lambda_iam_role_resource = aws_iam_role(
6136
6193
  "lambda_role",
6137
6194
  name=f"ocm-{identifier}-cognito-lambda-role",
6138
- assume_role_policy=json.dumps(lambda_role_policy),
6195
+ assume_role_policy=json_dumps(lambda_role_policy),
6139
6196
  managed_policy_arns=[lambda_managed_policy_arn],
6140
6197
  force_detach_policies=False,
6141
6198
  max_session_duration=3600,
@@ -6810,7 +6867,7 @@ class TerrascriptClient:
6810
6867
  )
6811
6868
  tf_resources.append(api_gateway_stage_resource)
6812
6869
 
6813
- rest_api_policy = json.dumps({
6870
+ rest_api_policy = json_dumps({
6814
6871
  "Version": "2012-10-17",
6815
6872
  "Statement": [
6816
6873
  {
@@ -6914,7 +6971,7 @@ class TerrascriptClient:
6914
6971
  },
6915
6972
  ],
6916
6973
  }
6917
- cloudwatch_assume_role_policy = json.dumps(policy, sort_keys=True)
6974
+ cloudwatch_assume_role_policy = json_dumps(policy)
6918
6975
 
6919
6976
  cloudwatch_iam_role_resource = aws_iam_role(
6920
6977
  "cloudwatch_assume_role",
@@ -6942,7 +6999,7 @@ class TerrascriptClient:
6942
6999
  ],
6943
7000
  }
6944
7001
 
6945
- cloudwatch_iam_policy_document = json.dumps(policy, sort_keys=True)
7002
+ cloudwatch_iam_policy_document = json_dumps(policy)
6946
7003
 
6947
7004
  cloudwatch_iam_policy_resource = aws_iam_policy(
6948
7005
  "cloudwatch",
@@ -7187,7 +7244,7 @@ class TerrascriptClient:
7187
7244
 
7188
7245
  version_values = {
7189
7246
  "secret_id": "${" + secret_resource.arn + "}",
7190
- "secret_string": json.dumps(secret, sort_keys=True),
7247
+ "secret_string": json_dumps(secret),
7191
7248
  }
7192
7249
  version_resource = aws_secretsmanager_secret_version(
7193
7250
  secret_identifier, **version_values
@@ -7196,7 +7253,7 @@ class TerrascriptClient:
7196
7253
 
7197
7254
  secret_policy_values = {
7198
7255
  "secret_arn": "${" + secret_resource.arn + "}",
7199
- "policy": json.dumps({
7256
+ "policy": json_dumps({
7200
7257
  "Version": "2012-10-17",
7201
7258
  "Statement": [
7202
7259
  {
@@ -24,30 +24,24 @@ class Environment(BaseModel):
24
24
  return self.name == other
25
25
 
26
26
 
27
- class FeatureToggle(BaseModel):
27
+ class FeatureToggle(BaseModel, validate_by_name=True, validate_by_alias=True):
28
28
  name: str
29
29
  type: FeatureToggleType = FeatureToggleType.release
30
30
  description: str | None = None
31
31
  impression_data: bool = Field(False, alias="impressionData")
32
32
  environments: list[Environment]
33
33
 
34
- class Config:
35
- allow_population_by_field_name = True
36
-
37
34
  def __eq__(self, other: object) -> bool:
38
35
  if isinstance(other, FeatureToggle):
39
36
  return self.name == other.name
40
37
  return self.name == other
41
38
 
42
39
 
43
- class Project(BaseModel):
40
+ class Project(BaseModel, validate_by_name=True, validate_by_alias=True):
44
41
  pk: str = Field(alias="id")
45
42
  name: str
46
43
  feature_toggles: list[FeatureToggle] = []
47
44
 
48
- class Config:
49
- allow_population_by_field_name = True
50
-
51
45
 
52
46
  class TokenAuth(BearerTokenAuth):
53
47
  def __call__(self, r: requests.PreparedRequest) -> requests.PreparedRequest: