qontract-reconcile 0.10.2.dev310__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 (400) hide show
  1. {qontract_reconcile-0.10.2.dev310.dist-info → qontract_reconcile-0.10.2.dev439.dist-info}/METADATA +13 -12
  2. {qontract_reconcile-0.10.2.dev310.dist-info → qontract_reconcile-0.10.2.dev439.dist-info}/RECORD +396 -391
  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 +5 -5
  19. reconcile/aws_iam_keys.py +1 -0
  20. reconcile/aws_saml_idp/integration.py +12 -4
  21. reconcile/aws_saml_roles/integration.py +32 -25
  22. reconcile/aws_version_sync/integration.py +125 -84
  23. reconcile/change_owners/bundle.py +3 -3
  24. reconcile/change_owners/change_log_tracking.py +3 -2
  25. reconcile/change_owners/change_owners.py +1 -1
  26. reconcile/change_owners/diff.py +2 -4
  27. reconcile/checkpoint.py +12 -4
  28. reconcile/cli.py +111 -18
  29. reconcile/cluster_deployment_mapper.py +2 -3
  30. reconcile/dashdotdb_dora.py +5 -12
  31. reconcile/dashdotdb_slo.py +1 -1
  32. reconcile/database_access_manager.py +125 -121
  33. reconcile/deadmanssnitch.py +1 -5
  34. reconcile/dynatrace_token_provider/integration.py +1 -1
  35. reconcile/endpoints_discovery/integration.py +4 -1
  36. reconcile/endpoints_discovery/merge_request.py +1 -1
  37. reconcile/endpoints_discovery/merge_request_manager.py +9 -11
  38. reconcile/external_resources/factories.py +5 -12
  39. reconcile/external_resources/integration.py +1 -1
  40. reconcile/external_resources/manager.py +8 -5
  41. reconcile/external_resources/meta.py +0 -1
  42. reconcile/external_resources/metrics.py +1 -1
  43. reconcile/external_resources/model.py +20 -20
  44. reconcile/external_resources/reconciler.py +7 -4
  45. reconcile/external_resources/secrets_sync.py +10 -14
  46. reconcile/external_resources/state.py +26 -16
  47. reconcile/fleet_labeler/integration.py +1 -1
  48. reconcile/gabi_authorized_users.py +8 -5
  49. reconcile/gcp_image_mirror.py +2 -2
  50. reconcile/github_org.py +1 -1
  51. reconcile/github_owners.py +4 -0
  52. reconcile/gitlab_housekeeping.py +13 -15
  53. reconcile/gitlab_members.py +6 -12
  54. reconcile/gitlab_mr_sqs_consumer.py +2 -2
  55. reconcile/gitlab_owners.py +15 -11
  56. reconcile/gitlab_permissions.py +8 -12
  57. reconcile/glitchtip_project_alerts/integration.py +3 -1
  58. reconcile/gql_definitions/acs/acs_instances.py +10 -10
  59. reconcile/gql_definitions/acs/acs_policies.py +5 -5
  60. reconcile/gql_definitions/acs/acs_rbac.py +6 -6
  61. reconcile/gql_definitions/advanced_upgrade_service/aus_clusters.py +32 -32
  62. reconcile/gql_definitions/advanced_upgrade_service/aus_organization.py +26 -26
  63. reconcile/gql_definitions/app_interface_metrics_exporter/onboarding_status.py +6 -7
  64. reconcile/gql_definitions/app_sre_tekton_access_revalidation/roles.py +5 -5
  65. reconcile/gql_definitions/app_sre_tekton_access_revalidation/users.py +5 -5
  66. reconcile/gql_definitions/automated_actions/instance.py +51 -12
  67. reconcile/gql_definitions/aws_account_manager/aws_accounts.py +11 -11
  68. reconcile/gql_definitions/aws_ami_cleanup/aws_accounts.py +20 -10
  69. reconcile/gql_definitions/aws_cloudwatch_log_retention/aws_accounts.py +28 -68
  70. reconcile/gql_definitions/aws_saml_idp/aws_accounts.py +20 -10
  71. reconcile/gql_definitions/aws_saml_roles/aws_accounts.py +20 -10
  72. reconcile/gql_definitions/aws_saml_roles/roles.py +5 -5
  73. reconcile/gql_definitions/aws_version_sync/clusters.py +10 -10
  74. reconcile/gql_definitions/aws_version_sync/namespaces.py +5 -5
  75. reconcile/gql_definitions/change_owners/queries/change_types.py +5 -5
  76. reconcile/gql_definitions/change_owners/queries/self_service_roles.py +9 -9
  77. reconcile/gql_definitions/cluster_auth_rhidp/clusters.py +18 -18
  78. reconcile/gql_definitions/common/alerting_services_settings.py +9 -9
  79. reconcile/gql_definitions/common/app_code_component_repos.py +5 -5
  80. reconcile/gql_definitions/common/app_interface_custom_messages.py +5 -5
  81. reconcile/gql_definitions/common/app_interface_dms_settings.py +5 -5
  82. reconcile/gql_definitions/common/app_interface_repo_settings.py +5 -5
  83. reconcile/gql_definitions/common/app_interface_roles.py +120 -0
  84. reconcile/gql_definitions/common/app_interface_state_settings.py +10 -10
  85. reconcile/gql_definitions/common/app_interface_vault_settings.py +5 -5
  86. reconcile/gql_definitions/common/app_quay_repos_escalation_policies.py +5 -5
  87. reconcile/gql_definitions/common/apps.py +5 -5
  88. reconcile/gql_definitions/common/aws_vpc_requests.py +22 -9
  89. reconcile/gql_definitions/common/aws_vpcs.py +11 -11
  90. reconcile/gql_definitions/common/clusters.py +37 -35
  91. reconcile/gql_definitions/common/clusters_minimal.py +14 -14
  92. reconcile/gql_definitions/common/clusters_with_dms.py +6 -6
  93. reconcile/gql_definitions/common/clusters_with_peering.py +29 -30
  94. reconcile/gql_definitions/common/github_orgs.py +10 -10
  95. reconcile/gql_definitions/common/jira_settings.py +10 -10
  96. reconcile/gql_definitions/common/jiralert_settings.py +5 -5
  97. reconcile/gql_definitions/common/ldap_settings.py +5 -5
  98. reconcile/gql_definitions/common/namespaces.py +42 -44
  99. reconcile/gql_definitions/common/namespaces_minimal.py +15 -13
  100. reconcile/gql_definitions/common/ocm_env_telemeter.py +12 -12
  101. reconcile/gql_definitions/common/ocm_environments.py +19 -19
  102. reconcile/gql_definitions/common/pagerduty_instances.py +9 -9
  103. reconcile/gql_definitions/common/pgp_reencryption_settings.py +6 -6
  104. reconcile/gql_definitions/common/pipeline_providers.py +29 -29
  105. reconcile/gql_definitions/common/quay_instances.py +5 -5
  106. reconcile/gql_definitions/common/quay_orgs.py +5 -5
  107. reconcile/gql_definitions/common/reserved_networks.py +5 -5
  108. reconcile/gql_definitions/common/rhcs_provider_settings.py +5 -5
  109. reconcile/gql_definitions/common/saas_files.py +44 -44
  110. reconcile/gql_definitions/common/saas_target_namespaces.py +10 -10
  111. reconcile/gql_definitions/common/saasherder_settings.py +5 -5
  112. reconcile/gql_definitions/common/slack_workspaces.py +5 -5
  113. reconcile/gql_definitions/common/smtp_client_settings.py +19 -19
  114. reconcile/gql_definitions/common/state_aws_account.py +7 -8
  115. reconcile/gql_definitions/common/users.py +5 -5
  116. reconcile/gql_definitions/common/users_with_paths.py +5 -5
  117. reconcile/gql_definitions/cost_report/app_names.py +5 -5
  118. reconcile/gql_definitions/cost_report/cost_namespaces.py +5 -5
  119. reconcile/gql_definitions/cost_report/settings.py +9 -9
  120. reconcile/gql_definitions/dashdotdb_slo/slo_documents_query.py +43 -43
  121. reconcile/gql_definitions/dynatrace_token_provider/dynatrace_bootstrap_tokens.py +10 -10
  122. reconcile/gql_definitions/dynatrace_token_provider/token_specs.py +5 -5
  123. reconcile/gql_definitions/email_sender/apps.py +5 -5
  124. reconcile/gql_definitions/email_sender/emails.py +8 -8
  125. reconcile/gql_definitions/email_sender/users.py +6 -6
  126. reconcile/gql_definitions/endpoints_discovery/apps.py +10 -10
  127. reconcile/gql_definitions/external_resources/aws_accounts.py +9 -9
  128. reconcile/gql_definitions/external_resources/external_resources_modules.py +23 -23
  129. reconcile/gql_definitions/external_resources/external_resources_namespaces.py +494 -410
  130. reconcile/gql_definitions/external_resources/external_resources_settings.py +28 -26
  131. reconcile/gql_definitions/external_resources/fragments/external_resources_module_overrides.py +5 -5
  132. reconcile/gql_definitions/fleet_labeler/fleet_labels.py +40 -40
  133. reconcile/gql_definitions/fragments/aus_organization.py +5 -5
  134. reconcile/gql_definitions/fragments/aws_account_common.py +7 -5
  135. reconcile/gql_definitions/fragments/aws_account_managed.py +5 -5
  136. reconcile/gql_definitions/fragments/aws_account_sso.py +5 -5
  137. reconcile/gql_definitions/fragments/aws_infra_management_account.py +5 -5
  138. reconcile/gql_definitions/fragments/{aws_vpc_request_subnet.py → aws_organization.py} +12 -8
  139. reconcile/gql_definitions/fragments/aws_vpc.py +5 -5
  140. reconcile/gql_definitions/fragments/aws_vpc_request.py +12 -5
  141. reconcile/gql_definitions/fragments/container_image_mirror.py +5 -5
  142. reconcile/gql_definitions/fragments/deploy_resources.py +5 -5
  143. reconcile/gql_definitions/fragments/disable.py +5 -5
  144. reconcile/gql_definitions/fragments/email_service.py +5 -5
  145. reconcile/gql_definitions/fragments/email_user.py +5 -5
  146. reconcile/gql_definitions/fragments/jumphost_common_fields.py +5 -5
  147. reconcile/gql_definitions/fragments/membership_source.py +5 -5
  148. reconcile/gql_definitions/fragments/minimal_ocm_organization.py +5 -5
  149. reconcile/gql_definitions/fragments/oc_connection_cluster.py +5 -5
  150. reconcile/gql_definitions/fragments/ocm_environment.py +5 -5
  151. reconcile/gql_definitions/fragments/pipeline_provider_retention.py +5 -5
  152. reconcile/gql_definitions/fragments/prometheus_instance.py +5 -5
  153. reconcile/gql_definitions/fragments/resource_limits_requirements.py +5 -5
  154. reconcile/gql_definitions/fragments/resource_requests_requirements.py +5 -5
  155. reconcile/gql_definitions/fragments/resource_values.py +5 -5
  156. reconcile/gql_definitions/fragments/saas_slo_document.py +5 -5
  157. reconcile/gql_definitions/fragments/saas_target_namespace.py +5 -5
  158. reconcile/gql_definitions/fragments/serviceaccount_token.py +5 -5
  159. reconcile/gql_definitions/fragments/terraform_state.py +5 -5
  160. reconcile/gql_definitions/fragments/upgrade_policy.py +5 -5
  161. reconcile/gql_definitions/fragments/user.py +5 -5
  162. reconcile/gql_definitions/fragments/vault_secret.py +5 -5
  163. reconcile/gql_definitions/gcp/gcp_docker_repos.py +9 -9
  164. reconcile/gql_definitions/gcp/gcp_projects.py +9 -9
  165. reconcile/gql_definitions/gitlab_members/gitlab_instances.py +9 -9
  166. reconcile/gql_definitions/gitlab_members/permissions.py +9 -9
  167. reconcile/gql_definitions/glitchtip/glitchtip_instance.py +9 -9
  168. reconcile/gql_definitions/glitchtip/glitchtip_project.py +11 -11
  169. reconcile/gql_definitions/glitchtip_project_alerts/glitchtip_project.py +9 -9
  170. reconcile/gql_definitions/integrations/integrations.py +48 -51
  171. reconcile/gql_definitions/introspection.json +3510 -1865
  172. reconcile/gql_definitions/jenkins_configs/jenkins_configs.py +11 -11
  173. reconcile/gql_definitions/jenkins_configs/jenkins_instances.py +10 -10
  174. reconcile/gql_definitions/jira/jira_servers.py +5 -5
  175. reconcile/gql_definitions/jira_permissions_validator/jira_boards_for_permissions_validator.py +14 -10
  176. reconcile/gql_definitions/jumphosts/jumphosts.py +13 -13
  177. reconcile/gql_definitions/ldap_groups/roles.py +5 -5
  178. reconcile/gql_definitions/ldap_groups/settings.py +9 -9
  179. reconcile/gql_definitions/maintenance/maintenances.py +5 -5
  180. reconcile/gql_definitions/membershipsources/roles.py +5 -5
  181. reconcile/gql_definitions/ocm_labels/clusters.py +18 -19
  182. reconcile/gql_definitions/ocm_labels/organizations.py +5 -5
  183. reconcile/gql_definitions/openshift_cluster_bots/clusters.py +22 -22
  184. reconcile/gql_definitions/openshift_groups/managed_groups.py +5 -5
  185. reconcile/gql_definitions/openshift_groups/managed_roles.py +6 -6
  186. reconcile/gql_definitions/openshift_serviceaccount_tokens/tokens.py +10 -10
  187. reconcile/gql_definitions/quay_membership/quay_membership.py +6 -6
  188. reconcile/gql_definitions/rhcs/certs.py +33 -87
  189. reconcile/gql_definitions/rhcs/openshift_resource_rhcs_cert.py +43 -0
  190. reconcile/gql_definitions/rhidp/organizations.py +18 -18
  191. reconcile/gql_definitions/service_dependencies/jenkins_instance_fragment.py +5 -5
  192. reconcile/gql_definitions/service_dependencies/service_dependencies.py +8 -8
  193. reconcile/gql_definitions/sharding/aws_accounts.py +10 -10
  194. reconcile/gql_definitions/sharding/ocm_organization.py +8 -8
  195. reconcile/gql_definitions/skupper_network/site_controller_template.py +5 -5
  196. reconcile/gql_definitions/skupper_network/skupper_networks.py +10 -10
  197. reconcile/gql_definitions/slack_usergroups/clusters.py +5 -5
  198. reconcile/gql_definitions/slack_usergroups/permissions.py +9 -9
  199. reconcile/gql_definitions/slack_usergroups/users.py +5 -5
  200. reconcile/gql_definitions/slo_documents/slo_documents.py +5 -5
  201. reconcile/gql_definitions/status_board/status_board.py +6 -7
  202. reconcile/gql_definitions/statuspage/statuspages.py +9 -9
  203. reconcile/gql_definitions/templating/template_collection.py +5 -5
  204. reconcile/gql_definitions/templating/templates.py +5 -5
  205. reconcile/gql_definitions/terraform_cloudflare_dns/app_interface_cloudflare_dns_settings.py +6 -6
  206. reconcile/gql_definitions/terraform_cloudflare_dns/terraform_cloudflare_zones.py +11 -11
  207. reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_accounts.py +11 -11
  208. reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_resources.py +20 -25
  209. reconcile/gql_definitions/terraform_cloudflare_users/app_interface_setting_cloudflare_and_vault.py +6 -6
  210. reconcile/gql_definitions/terraform_cloudflare_users/terraform_cloudflare_roles.py +12 -12
  211. reconcile/gql_definitions/terraform_init/aws_accounts.py +23 -9
  212. reconcile/gql_definitions/terraform_repo/terraform_repo.py +9 -9
  213. reconcile/gql_definitions/terraform_resources/database_access_manager.py +5 -5
  214. reconcile/gql_definitions/terraform_resources/terraform_resources_namespaces.py +450 -402
  215. reconcile/gql_definitions/terraform_tgw_attachments/aws_accounts.py +23 -17
  216. reconcile/gql_definitions/unleash_feature_toggles/feature_toggles.py +9 -9
  217. reconcile/gql_definitions/vault_instances/vault_instances.py +61 -61
  218. reconcile/gql_definitions/vault_policies/vault_policies.py +11 -11
  219. reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator.py +8 -8
  220. reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator_peered_cluster_fragment.py +5 -5
  221. reconcile/integrations_manager.py +3 -3
  222. reconcile/jenkins_job_builder.py +1 -1
  223. reconcile/jenkins_worker_fleets.py +80 -11
  224. reconcile/jira_permissions_validator.py +237 -122
  225. reconcile/ldap_groups/integration.py +1 -1
  226. reconcile/ocm/types.py +35 -56
  227. reconcile/ocm_aws_infrastructure_access.py +1 -1
  228. reconcile/ocm_clusters.py +4 -4
  229. reconcile/ocm_labels/integration.py +3 -2
  230. reconcile/ocm_machine_pools.py +33 -27
  231. reconcile/openshift_base.py +122 -10
  232. reconcile/openshift_cluster_bots.py +5 -5
  233. reconcile/openshift_groups.py +5 -0
  234. reconcile/openshift_limitranges.py +1 -1
  235. reconcile/openshift_namespace_labels.py +1 -1
  236. reconcile/openshift_namespaces.py +97 -101
  237. reconcile/openshift_resources_base.py +10 -5
  238. reconcile/openshift_rhcs_certs.py +77 -40
  239. reconcile/openshift_rolebindings.py +230 -130
  240. reconcile/openshift_saas_deploy.py +6 -7
  241. reconcile/openshift_saas_deploy_change_tester.py +9 -7
  242. reconcile/openshift_saas_deploy_trigger_cleaner.py +3 -5
  243. reconcile/openshift_serviceaccount_tokens.py +8 -7
  244. reconcile/openshift_tekton_resources.py +1 -1
  245. reconcile/openshift_upgrade_watcher.py +4 -4
  246. reconcile/openshift_users.py +5 -3
  247. reconcile/oum/labelset.py +5 -3
  248. reconcile/oum/models.py +1 -4
  249. reconcile/oum/providers.py +1 -1
  250. reconcile/prometheus_rules_tester/integration.py +4 -4
  251. reconcile/quay_mirror.py +1 -1
  252. reconcile/queries.py +131 -0
  253. reconcile/requests_sender.py +8 -3
  254. reconcile/resource_scraper.py +1 -5
  255. reconcile/rhidp/common.py +3 -5
  256. reconcile/rhidp/sso_client/base.py +19 -10
  257. reconcile/saas_auto_promotions_manager/merge_request_manager/renderer.py +1 -1
  258. reconcile/saas_auto_promotions_manager/subscriber.py +4 -3
  259. reconcile/sendgrid_teammates.py +20 -9
  260. reconcile/skupper_network/integration.py +2 -2
  261. reconcile/slack_usergroups.py +35 -14
  262. reconcile/sql_query.py +1 -0
  263. reconcile/status.py +2 -2
  264. reconcile/status_board.py +6 -6
  265. reconcile/statuspage/atlassian.py +7 -7
  266. reconcile/statuspage/integrations/maintenances.py +4 -3
  267. reconcile/statuspage/page.py +4 -9
  268. reconcile/statuspage/status.py +5 -8
  269. reconcile/templates/rosa-classic-cluster-creation.sh.j2 +5 -1
  270. reconcile/templates/rosa-hcp-cluster-creation.sh.j2 +4 -1
  271. reconcile/templating/lib/merge_request_manager.py +2 -2
  272. reconcile/templating/lib/rendering.py +3 -3
  273. reconcile/templating/renderer.py +12 -13
  274. reconcile/terraform_aws_route53.py +18 -8
  275. reconcile/terraform_cloudflare_dns.py +3 -3
  276. reconcile/terraform_cloudflare_resources.py +12 -13
  277. reconcile/terraform_cloudflare_users.py +3 -2
  278. reconcile/terraform_init/integration.py +187 -23
  279. reconcile/terraform_repo.py +16 -12
  280. reconcile/terraform_resources.py +18 -10
  281. reconcile/terraform_tgw_attachments.py +28 -20
  282. reconcile/terraform_users.py +27 -22
  283. reconcile/terraform_vpc_peerings.py +15 -3
  284. reconcile/terraform_vpc_resources/integration.py +23 -8
  285. reconcile/typed_queries/app_interface_roles.py +10 -0
  286. reconcile/typed_queries/aws_account_tags.py +41 -0
  287. reconcile/typed_queries/cost_report/app_names.py +1 -1
  288. reconcile/typed_queries/cost_report/cost_namespaces.py +2 -2
  289. reconcile/typed_queries/saas_files.py +13 -13
  290. reconcile/typed_queries/status_board.py +2 -2
  291. reconcile/unleash_feature_toggles/integration.py +4 -2
  292. reconcile/utils/acs/base.py +6 -3
  293. reconcile/utils/acs/policies.py +2 -2
  294. reconcile/utils/aggregated_list.py +4 -3
  295. reconcile/utils/aws_api.py +51 -20
  296. reconcile/utils/aws_api_typed/api.py +38 -9
  297. reconcile/utils/aws_api_typed/cloudformation.py +149 -0
  298. reconcile/utils/aws_api_typed/logs.py +73 -0
  299. reconcile/utils/aws_api_typed/organization.py +4 -2
  300. reconcile/utils/binary.py +7 -12
  301. reconcile/utils/datetime_util.py +67 -0
  302. reconcile/utils/deadmanssnitch_api.py +1 -1
  303. reconcile/utils/differ.py +2 -3
  304. reconcile/utils/early_exit_cache.py +11 -12
  305. reconcile/utils/expiration.py +7 -3
  306. reconcile/utils/external_resource_spec.py +24 -1
  307. reconcile/utils/filtering.py +1 -1
  308. reconcile/utils/gitlab_api.py +7 -5
  309. reconcile/utils/glitchtip/client.py +6 -2
  310. reconcile/utils/glitchtip/models.py +25 -28
  311. reconcile/utils/gpg.py +5 -3
  312. reconcile/utils/gql.py +4 -7
  313. reconcile/utils/helm.py +2 -1
  314. reconcile/utils/helpers.py +1 -1
  315. reconcile/utils/imap_client.py +1 -1
  316. reconcile/utils/instrumented_wrappers.py +1 -1
  317. reconcile/utils/internal_groups/client.py +2 -2
  318. reconcile/utils/internal_groups/models.py +8 -17
  319. reconcile/utils/jenkins_api.py +24 -1
  320. reconcile/utils/jinja2/utils.py +6 -8
  321. reconcile/utils/jira_client.py +82 -63
  322. reconcile/utils/jjb_client.py +78 -46
  323. reconcile/utils/jobcontroller/controller.py +2 -2
  324. reconcile/utils/jobcontroller/models.py +17 -1
  325. reconcile/utils/json.py +74 -0
  326. reconcile/utils/ldap_client.py +4 -3
  327. reconcile/utils/lean_terraform_client.py +3 -1
  328. reconcile/utils/membershipsources/app_interface_resolver.py +4 -2
  329. reconcile/utils/membershipsources/models.py +16 -23
  330. reconcile/utils/membershipsources/resolver.py +4 -2
  331. reconcile/utils/merge_request_manager/merge_request_manager.py +4 -4
  332. reconcile/utils/merge_request_manager/parser.py +6 -6
  333. reconcile/utils/metrics.py +5 -5
  334. reconcile/utils/models.py +304 -82
  335. reconcile/utils/mr/__init__.py +3 -1
  336. reconcile/utils/mr/app_interface_reporter.py +6 -3
  337. reconcile/utils/mr/aws_access.py +1 -1
  338. reconcile/utils/mr/base.py +7 -13
  339. reconcile/utils/mr/clusters_updates.py +4 -2
  340. reconcile/utils/mr/notificator.py +3 -3
  341. reconcile/utils/mr/ocm_upgrade_scheduler_org_updates.py +4 -1
  342. reconcile/utils/mr/promote_qontract.py +28 -12
  343. reconcile/utils/mr/update_access_report_base.py +3 -4
  344. reconcile/utils/mr/user_maintenance.py +7 -6
  345. reconcile/utils/oc.py +445 -336
  346. reconcile/utils/oc_filters.py +3 -3
  347. reconcile/utils/ocm/addons.py +0 -1
  348. reconcile/utils/ocm/base.py +18 -21
  349. reconcile/utils/ocm/cluster_groups.py +1 -1
  350. reconcile/utils/ocm/identity_providers.py +2 -2
  351. reconcile/utils/ocm/labels.py +1 -1
  352. reconcile/utils/ocm/ocm.py +81 -71
  353. reconcile/utils/ocm/products.py +9 -3
  354. reconcile/utils/ocm/search_filters.py +3 -6
  355. reconcile/utils/ocm/service_log.py +4 -6
  356. reconcile/utils/ocm/sre_capability_labels.py +20 -13
  357. reconcile/utils/ocm_base_client.py +4 -4
  358. reconcile/utils/openshift_resource.py +83 -52
  359. reconcile/utils/openssl.py +2 -2
  360. reconcile/utils/output.py +3 -2
  361. reconcile/utils/pagerduty_api.py +10 -7
  362. reconcile/utils/promotion_state.py +6 -11
  363. reconcile/utils/raw_github_api.py +11 -8
  364. reconcile/utils/repo_owners.py +21 -29
  365. reconcile/utils/rhcsv2_certs.py +138 -35
  366. reconcile/utils/rosa/session.py +16 -0
  367. reconcile/utils/runtime/integration.py +2 -3
  368. reconcile/utils/runtime/meta.py +2 -1
  369. reconcile/utils/runtime/runner.py +2 -2
  370. reconcile/utils/saasherder/interfaces.py +13 -20
  371. reconcile/utils/saasherder/models.py +25 -21
  372. reconcile/utils/saasherder/saasherder.py +60 -32
  373. reconcile/utils/secret_reader.py +6 -6
  374. reconcile/utils/sharding.py +1 -1
  375. reconcile/utils/slack_api.py +26 -4
  376. reconcile/utils/sloth.py +224 -0
  377. reconcile/utils/sqs_gateway.py +16 -11
  378. reconcile/utils/state.py +2 -1
  379. reconcile/utils/structs.py +1 -1
  380. reconcile/utils/terraform_client.py +29 -26
  381. reconcile/utils/terrascript_aws_client.py +200 -116
  382. reconcile/utils/three_way_diff_strategy.py +1 -1
  383. reconcile/utils/unleash/server.py +2 -8
  384. reconcile/utils/vault.py +44 -41
  385. reconcile/utils/vcs.py +8 -8
  386. reconcile/vault_replication.py +119 -58
  387. tools/app_interface_reporter.py +4 -4
  388. tools/cli_commands/cost_report/cost_management_api.py +3 -3
  389. tools/cli_commands/cost_report/view.py +7 -6
  390. tools/cli_commands/erv2.py +1 -1
  391. tools/cli_commands/gpg_encrypt.py +4 -1
  392. tools/cli_commands/systems_and_tools.py +5 -1
  393. tools/qontract_cli.py +36 -21
  394. tools/template_validation.py +3 -1
  395. reconcile/gql_definitions/ocm_oidc_idp/__init__.py +0 -0
  396. reconcile/gql_definitions/ocm_subscription_labels/__init__.py +0 -0
  397. reconcile/jenkins/__init__.py +0 -0
  398. reconcile/jenkins/types.py +0 -77
  399. {qontract_reconcile-0.10.2.dev310.dist-info → qontract_reconcile-0.10.2.dev439.dist-info}/WHEEL +0 -0
  400. {qontract_reconcile-0.10.2.dev310.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-.]+$")
@@ -282,15 +288,10 @@ SUPPORTED_ALB_LISTENER_RULE_CONDITION_TYPE_MAPPING = {
282
288
  "host-header": "host_header",
283
289
  "http-request-method": "http_request_method",
284
290
  "path-pattern": "path_pattern",
291
+ "query-string": "query_string",
285
292
  "source-ip": "source_ip",
286
293
  }
287
294
 
288
- DEFAULT_TAGS = {
289
- "tags": {
290
- "app": "app-sre-infra",
291
- },
292
- }
293
-
294
295
  AWS_ELB_ACCOUNT_IDS = {
295
296
  "us-east-1": "127311923021",
296
297
  "us-east-2": "033677994240",
@@ -373,6 +374,10 @@ class aws_s3_bucket_logging(Resource):
373
374
  pass
374
375
 
375
376
 
377
+ class aws_kinesis_resource_policy(Resource):
378
+ pass
379
+
380
+
376
381
  class aws_cloudfront_log_delivery_canonical_user_id(Data):
377
382
  pass
378
383
 
@@ -474,6 +479,7 @@ class TerrascriptClient:
474
479
  integration_prefix: str,
475
480
  thread_pool_size: int,
476
481
  accounts: Iterable[MutableMapping[str, Any]],
482
+ default_tags: Mapping[str, str] | None,
477
483
  settings: Mapping[str, Any] | None = None,
478
484
  prefetch_resources_by_schemas: Iterable[str] | None = None,
479
485
  secret_reader: SecretReaderBase | None = None,
@@ -487,6 +493,7 @@ class TerrascriptClient:
487
493
  else:
488
494
  self.secret_reader = SecretReader(settings=settings)
489
495
  self.configs: dict[str, dict] = {}
496
+ self.default_tags = default_tags or {"app": "app-sre-infra"}
490
497
  self.populate_configs(filtered_accounts)
491
498
  self.versions: dict[str, str] = {
492
499
  a["name"]: a["providerVersion"] for a in filtered_accounts
@@ -507,7 +514,7 @@ class TerrascriptClient:
507
514
  region=region,
508
515
  alias=region,
509
516
  skip_region_validation=True,
510
- default_tags=DEFAULT_TAGS,
517
+ default_tags={"tags": config["tags"]},
511
518
  )
512
519
 
513
520
  # Add default region, which will be in resourcesDefaultRegion
@@ -516,7 +523,7 @@ class TerrascriptClient:
516
523
  secret_key=config["aws_secret_access_key"],
517
524
  region=config["resourcesDefaultRegion"],
518
525
  skip_region_validation=True,
519
- default_tags=DEFAULT_TAGS,
526
+ default_tags={"tags": config["tags"]},
520
527
  )
521
528
 
522
529
  ts += Terraform(
@@ -799,6 +806,9 @@ class TerrascriptClient:
799
806
  config["supportedDeploymentRegions"] = account["supportedDeploymentRegions"]
800
807
  config["resourcesDefaultRegion"] = account["resourcesDefaultRegion"]
801
808
  config["terraformState"] = account["terraformState"]
809
+ config["tags"] = dict(self.default_tags) | get_aws_account_tags(
810
+ account.get("organization", None)
811
+ )
802
812
  self.configs[account_name] = config
803
813
 
804
814
  def _get_partition(self, account: str) -> str:
@@ -1053,7 +1063,9 @@ class TerrascriptClient:
1053
1063
  ignore_changes = (
1054
1064
  "all" if "all" in lifecycle.ignore_changes else lifecycle.ignore_changes
1055
1065
  )
1056
- return lifecycle.dict(by_alias=True) | {"ignore_changes": ignore_changes}
1066
+ return lifecycle.model_dump(by_alias=True) | {
1067
+ "ignore_changes": ignore_changes
1068
+ }
1057
1069
  return None
1058
1070
 
1059
1071
  def populate_additional_providers(
@@ -1068,25 +1080,15 @@ class TerrascriptClient:
1068
1080
  config = self.configs[account_name]
1069
1081
  existing_provider_aliases = {p.get("alias") for p in ts["provider"]["aws"]}
1070
1082
  if alias not in existing_provider_aliases:
1071
- if assume_role:
1072
- ts += provider.aws(
1073
- access_key=config["aws_access_key_id"],
1074
- secret_key=config["aws_secret_access_key"],
1075
- region=region,
1076
- alias=alias,
1077
- assume_role={"role_arn": assume_role},
1078
- skip_region_validation=True,
1079
- default_tags=DEFAULT_TAGS,
1080
- )
1081
- else:
1082
- ts += provider.aws(
1083
- access_key=config["aws_access_key_id"],
1084
- secret_key=config["aws_secret_access_key"],
1085
- region=region,
1086
- alias=alias,
1087
- skip_region_validation=True,
1088
- default_tags=DEFAULT_TAGS,
1089
- )
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
+ )
1090
1092
 
1091
1093
  def populate_route53(
1092
1094
  self, desired_state: Iterable[Mapping[str, Any]], default_ttl: int = 300
@@ -1343,13 +1345,13 @@ class TerrascriptClient:
1343
1345
  vpc_module_values["public_subnets"] = request.subnets.public
1344
1346
  vpc_module_values["public_subnet_tags"] = (
1345
1347
  VPC_REQUEST_DEFAULT_PUBLIC_SUBNET_TAGS
1346
- | (request.subnets.public_subnet_tags or {}),
1348
+ | (request.subnets.public_subnet_tags or {})
1347
1349
  )
1348
1350
  if request.subnets.private:
1349
1351
  vpc_module_values["private_subnets"] = request.subnets.private
1350
1352
  vpc_module_values["private_subnet_tags"] = (
1351
1353
  VPC_REQUEST_DEFAULT_PRIVATE_SUBNET_TAGS
1352
- | (request.subnets.private_subnet_tags or {}),
1354
+ | (request.subnets.private_subnet_tags or {})
1353
1355
  )
1354
1356
  if request.subnets.availability_zones:
1355
1357
  vpc_module_values["azs"] = request.subnets.availability_zones
@@ -1429,7 +1431,7 @@ class TerrascriptClient:
1429
1431
  req_account_name = req_account.name
1430
1432
  # Accepter's side of the connection - the cluster's account
1431
1433
  acc_account = accepter.account
1432
- 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))
1433
1435
  acc_uid = acc_account.uid
1434
1436
  if acc_account.assume_role:
1435
1437
  acc_uid = awsh.get_account_uid_from_arn(acc_account.assume_role)
@@ -1946,7 +1948,7 @@ class TerrascriptClient:
1946
1948
  em_identifier = f"{identifier}-enhanced-monitoring"
1947
1949
  em_values = {
1948
1950
  "name": em_identifier,
1949
- "assume_role_policy": json.dumps(assume_role_policy, sort_keys=True),
1951
+ "assume_role_policy": json_dumps(assume_role_policy),
1950
1952
  }
1951
1953
  role_tf_resource = aws_iam_role(em_identifier, **em_values)
1952
1954
  tf_resources.append(role_tf_resource)
@@ -2215,6 +2217,43 @@ class TerrascriptClient:
2215
2217
  letters_and_digits = string.ascii_letters + string.digits
2216
2218
  return "".join(random.choice(letters_and_digits) for i in range(string_length))
2217
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
+
2218
2257
  def populate_tf_resource_s3(self, spec: ExternalResourceSpec) -> aws_s3_bucket:
2219
2258
  account = spec.provisioner_name
2220
2259
  identifier = spec.identifier
@@ -2254,47 +2293,11 @@ class TerrascriptClient:
2254
2293
  request_payer = common_values.get("request_payer")
2255
2294
  if request_payer:
2256
2295
  values["request_payer"] = request_payer
2257
- lifecycle_rules = common_values.get("lifecycle_rules")
2258
- if lifecycle_rules:
2259
- # 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
+ ):
2260
2300
  values["lifecycle_rule"] = lifecycle_rules
2261
- if versioning:
2262
- lrs = values.get("lifecycle_rule", [])
2263
- expiration_rule = False
2264
- for lr in lrs:
2265
- if "noncurrent_version_expiration" in lr:
2266
- expiration_rule = True
2267
- break
2268
- if not expiration_rule:
2269
- # Add a default noncurrent object expiration rule if
2270
- # if one isn't already set
2271
- rule = {
2272
- "id": "expire_noncurrent_versions",
2273
- "enabled": "true",
2274
- "noncurrent_version_expiration": {"days": 30},
2275
- }
2276
- if len(lrs) > 0:
2277
- lrs.append(rule)
2278
- else:
2279
- lrs = rule
2280
- sc = common_values.get("storage_class")
2281
- if sc:
2282
- sc = sc.upper()
2283
- days = "1"
2284
- if sc.endswith("_IA"):
2285
- # Infrequent Access storage class has minimum 30 days
2286
- # before transition
2287
- days = "30"
2288
- rule = {
2289
- "id": sc + "_storage_class",
2290
- "enabled": "true",
2291
- "transition": {"days": days, "storage_class": sc},
2292
- "noncurrent_version_transition": {"days": days, "storage_class": sc},
2293
- }
2294
- if values.get("lifecycle_rule"):
2295
- values["lifecycle_rule"].append(rule)
2296
- else:
2297
- values["lifecycle_rule"] = rule
2298
2301
  cors_rules = common_values.get("cors_rules")
2299
2302
  if cors_rules:
2300
2303
  # common_values['cors_rules'] is a list of cors_rules
@@ -2344,7 +2347,7 @@ class TerrascriptClient:
2344
2347
  }
2345
2348
  ],
2346
2349
  }
2347
- rc_values["assume_role_policy"] = json.dumps(role, sort_keys=True)
2350
+ rc_values["assume_role_policy"] = json_dumps(role)
2348
2351
  role_resource = aws_iam_role(id, **rc_values)
2349
2352
  tf_resources.append(role_resource)
2350
2353
 
@@ -2382,7 +2385,7 @@ class TerrascriptClient:
2382
2385
  },
2383
2386
  ],
2384
2387
  }
2385
- rc_values["policy"] = json.dumps(policy, sort_keys=True)
2388
+ rc_values["policy"] = json_dumps(policy)
2386
2389
  policy_resource = aws_iam_policy(id, **rc_values)
2387
2390
  tf_resources.append(policy_resource)
2388
2391
 
@@ -2597,7 +2600,7 @@ class TerrascriptClient:
2597
2600
  },
2598
2601
  ],
2599
2602
  }
2600
- values["policy"] = json.dumps(policy, sort_keys=True)
2603
+ values["policy"] = json_dumps(policy)
2601
2604
  values["depends_on"] = self.get_dependencies([user_tf_resource])
2602
2605
 
2603
2606
  tf_aws_iam_policy = aws_iam_policy(identifier, **values)
@@ -2876,7 +2879,7 @@ class TerrascriptClient:
2876
2879
  values: dict[str, Any] = {
2877
2880
  "name": identifier,
2878
2881
  "tags": common_values["tags"],
2879
- "assume_role_policy": json.dumps(assume_role_policy),
2882
+ "assume_role_policy": json_dumps(assume_role_policy),
2880
2883
  }
2881
2884
 
2882
2885
  inline_policy = common_values.get("inline_policy")
@@ -2930,7 +2933,7 @@ class TerrascriptClient:
2930
2933
  self, account: str, name: str, policy: Mapping[str, Any]
2931
2934
  ) -> None:
2932
2935
  tf_aws_iam_policy = aws_iam_policy(
2933
- f"{account}-{name}", name=name, policy=json.dumps(policy)
2936
+ f"{account}-{name}", name=name, policy=json_dumps(policy)
2934
2937
  )
2935
2938
  self.add_resource(account, tf_aws_iam_policy)
2936
2939
 
@@ -2972,7 +2975,7 @@ class TerrascriptClient:
2972
2975
  role_tf_resource = aws_iam_role(
2973
2976
  f"{account}-{name}",
2974
2977
  name=name,
2975
- assume_role_policy=json.dumps(assume_role_policy),
2978
+ assume_role_policy=json_dumps(assume_role_policy),
2976
2979
  managed_policy_arns=managed_policy_arns,
2977
2980
  max_session_duration=max_session_duration_hours * 3600,
2978
2981
  )
@@ -3016,7 +3019,7 @@ class TerrascriptClient:
3016
3019
  all_queues.append(queue_name)
3017
3020
  sqs_policy = values.pop("sqs_policy", None)
3018
3021
  if sqs_policy is not None:
3019
- values["policy"] = json.dumps(sqs_policy, sort_keys=True)
3022
+ values["policy"] = json_dumps(sqs_policy)
3020
3023
  dl_queue = values.pop("dl_queue", None)
3021
3024
  if dl_queue is not None:
3022
3025
  max_receive_count = int(values.pop("max_receive_count", 10))
@@ -3030,9 +3033,7 @@ class TerrascriptClient:
3030
3033
  "deadLetterTargetArn": "${" + dl_data.arn + "}",
3031
3034
  "maxReceiveCount": max_receive_count,
3032
3035
  }
3033
- values["redrive_policy"] = json.dumps(
3034
- redrive_policy, sort_keys=True
3035
- )
3036
+ values["redrive_policy"] = json_dumps(redrive_policy)
3036
3037
  kms_master_key_id = values.pop("kms_master_key_id", None)
3037
3038
  if kms_master_key_id is not None:
3038
3039
  if kms_master_key_id.startswith("arn:"):
@@ -3105,7 +3106,7 @@ class TerrascriptClient:
3105
3106
  "Resource": list(kms_keys),
3106
3107
  }
3107
3108
  policy["Statement"].append(kms_statement)
3108
- values["policy"] = json.dumps(policy, sort_keys=True)
3109
+ values["policy"] = json_dumps(policy)
3109
3110
  policy_tf_resource = aws_iam_policy(policy_identifier, **values)
3110
3111
  tf_resources.append(policy_tf_resource)
3111
3112
 
@@ -3255,7 +3256,7 @@ class TerrascriptClient:
3255
3256
  }
3256
3257
  ],
3257
3258
  }
3258
- values["policy"] = json.dumps(policy, sort_keys=True)
3259
+ values["policy"] = json_dumps(policy)
3259
3260
  values["depends_on"] = self.get_dependencies([user_tf_resource])
3260
3261
 
3261
3262
  tf_aws_iam_policy = aws_iam_policy(identifier, **values)
@@ -3365,7 +3366,7 @@ class TerrascriptClient:
3365
3366
  },
3366
3367
  ],
3367
3368
  }
3368
- values_policy["policy"] = json.dumps(policy, sort_keys=True)
3369
+ values_policy["policy"] = json_dumps(policy)
3369
3370
  values_policy["depends_on"] = self.get_dependencies([user_tf_resource])
3370
3371
 
3371
3372
  tf_aws_iam_policy = aws_iam_policy(identifier, **values_policy)
@@ -3415,7 +3416,7 @@ class TerrascriptClient:
3415
3416
  }
3416
3417
  ],
3417
3418
  }
3418
- values_policy["policy"] = json.dumps(policy, sort_keys=True)
3419
+ values_policy["policy"] = json_dumps(policy)
3419
3420
  values_policy["depends_on"] = self.get_dependencies([bucket_tf_resource])
3420
3421
  region = common_values.get("region") or self.default_regions.get(account)
3421
3422
  assert region # make mypy happy
@@ -3561,7 +3562,7 @@ class TerrascriptClient:
3561
3562
  }
3562
3563
  ],
3563
3564
  }
3564
- sqs_values["policy"] = json.dumps(sqs_policy, sort_keys=True)
3565
+ sqs_values["policy"] = json_dumps(sqs_policy)
3565
3566
 
3566
3567
  kms_encryption = common_values.get("kms_encryption", False)
3567
3568
  if kms_encryption:
@@ -3597,7 +3598,7 @@ class TerrascriptClient:
3597
3598
  },
3598
3599
  ],
3599
3600
  }
3600
- kms_values["policy"] = json.dumps(kms_policy, sort_keys=True)
3601
+ kms_values["policy"] = json_dumps(kms_policy)
3601
3602
  if provider:
3602
3603
  kms_values["provider"] = provider
3603
3604
 
@@ -3695,7 +3696,7 @@ class TerrascriptClient:
3695
3696
  "Resource": [sqs_values["kms_master_key_id"]],
3696
3697
  }
3697
3698
  policy["Statement"].append(kms_statement)
3698
- values_policy["policy"] = json.dumps(policy, sort_keys=True)
3699
+ values_policy["policy"] = json_dumps(policy)
3699
3700
  policy_tf_resource = aws_iam_policy(sqs_identifier, **values_policy)
3700
3701
  tf_resources.append(policy_tf_resource)
3701
3702
 
@@ -3766,7 +3767,7 @@ class TerrascriptClient:
3766
3767
  role_identifier = f"{identifier}-lambda-execution-role"
3767
3768
  role_values = {
3768
3769
  "name": role_identifier,
3769
- "assume_role_policy": json.dumps(assume_role_policy, sort_keys=True),
3770
+ "assume_role_policy": json_dumps(assume_role_policy),
3770
3771
  }
3771
3772
 
3772
3773
  role_tf_resource = aws_iam_role(role_identifier, **role_values)
@@ -3798,7 +3799,7 @@ class TerrascriptClient:
3798
3799
 
3799
3800
  policy_values = {
3800
3801
  "role": "${" + role_tf_resource.id + "}",
3801
- "policy": json.dumps(policy, sort_keys=True),
3802
+ "policy": json_dumps(policy),
3802
3803
  }
3803
3804
  policy_tf_resource = aws_iam_role_policy(policy_identifier, **policy_values)
3804
3805
  tf_resources.append(policy_tf_resource)
@@ -3926,7 +3927,7 @@ class TerrascriptClient:
3926
3927
  }
3927
3928
  values = {
3928
3929
  "name": identifier,
3929
- "policy": json.dumps(policy, sort_keys=True),
3930
+ "policy": json_dumps(policy),
3930
3931
  "depends_on": self.get_dependencies([user_tf_resource]),
3931
3932
  }
3932
3933
 
@@ -4023,6 +4024,22 @@ class TerrascriptClient:
4023
4024
  kinesis_tf_resource = aws_kinesis_stream(identifier, **kinesis_values)
4024
4025
  tf_resources.append(kinesis_tf_resource)
4025
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
+
4026
4043
  es_identifier = common_values.get("es_identifier", None)
4027
4044
  if es_identifier:
4028
4045
  es_resource = self._find_resource_spec(
@@ -4047,7 +4064,7 @@ class TerrascriptClient:
4047
4064
  role_identifier = f"{identifier}-lambda-execution-role"
4048
4065
  role_values = {
4049
4066
  "name": role_identifier,
4050
- "assume_role_policy": json.dumps(assume_role_policy, sort_keys=True),
4067
+ "assume_role_policy": json_dumps(assume_role_policy),
4051
4068
  "tags": tags,
4052
4069
  }
4053
4070
 
@@ -4084,7 +4101,7 @@ class TerrascriptClient:
4084
4101
  policy_tf_resource = aws_iam_policy(
4085
4102
  policy_identifier,
4086
4103
  name=policy_identifier,
4087
- policy=json.dumps(policy, sort_keys=True),
4104
+ policy=json_dumps(policy),
4088
4105
  tags=tags,
4089
4106
  )
4090
4107
  tf_resources.append(policy_tf_resource)
@@ -4299,7 +4316,7 @@ class TerrascriptClient:
4299
4316
  # iam user policy
4300
4317
  values_policy: dict[str, Any] = {
4301
4318
  "name": identifier,
4302
- "policy": json.dumps(policy, sort_keys=True),
4319
+ "policy": json_dumps(policy),
4303
4320
  "depends_on": self.get_dependencies([user_tf_resource]),
4304
4321
  }
4305
4322
 
@@ -4417,10 +4434,7 @@ class TerrascriptClient:
4417
4434
 
4418
4435
  :return: key is AWS account name and value is terraform configuration
4419
4436
  """
4420
- return {
4421
- name: json.dumps(ts, indent=2, sort_keys=True)
4422
- for name, ts in self.tss.items()
4423
- }
4437
+ return {name: json_dumps(ts, indent=2) for name, ts in self.tss.items()}
4424
4438
 
4425
4439
  def init_values(
4426
4440
  self, spec: ExternalResourceSpec, init_tags: bool = True
@@ -4532,7 +4546,7 @@ class TerrascriptClient:
4532
4546
  output_name = output_format.format(
4533
4547
  spec.output_prefix, self.integration_prefix, "annotations"
4534
4548
  )
4535
- anno_json = json.dumps(spec.annotations()).encode("utf-8")
4549
+ anno_json = json_dumps(spec.annotations()).encode("utf-8")
4536
4550
  output_value = base64.b64encode(anno_json).decode()
4537
4551
  tf_resources.append(Output(output_name, value=output_value))
4538
4552
 
@@ -4672,7 +4686,7 @@ class TerrascriptClient:
4672
4686
  }
4673
4687
  log_groups_policy_values = {
4674
4688
  "policy_name": "es-log-publishing-permissions",
4675
- "policy_document": json.dumps(log_groups_policy, sort_keys=True),
4689
+ "policy_document": json_dumps(log_groups_policy),
4676
4690
  }
4677
4691
  resource_policy = aws_cloudwatch_log_resource_policy(
4678
4692
  "es_log_publishing_resource_policy",
@@ -5004,7 +5018,7 @@ class TerrascriptClient:
5004
5018
  }
5005
5019
  ],
5006
5020
  }
5007
- es_values["access_policies"] = json.dumps(access_policies, sort_keys=True)
5021
+ es_values["access_policies"] = json_dumps(access_policies)
5008
5022
 
5009
5023
  region = values.get("region") or self.default_regions.get(account)
5010
5024
  assert region # make mypy happy
@@ -5060,7 +5074,7 @@ class TerrascriptClient:
5060
5074
 
5061
5075
  version_values = {
5062
5076
  "secret_id": "${" + aws_secret_resource.id + "}",
5063
- "secret_string": json.dumps(master_user, sort_keys=True),
5077
+ "secret_string": json_dumps(master_user),
5064
5078
  }
5065
5079
  if provider:
5066
5080
  version_values["provider"] = provider
@@ -5087,7 +5101,7 @@ class TerrascriptClient:
5087
5101
  iam_policy_resource = aws_iam_policy(
5088
5102
  secret_identifier,
5089
5103
  name=f"{identifier}-secretsmanager-policy",
5090
- policy=json.dumps(policy, sort_keys=True),
5104
+ policy=json_dumps(policy),
5091
5105
  tags=tags,
5092
5106
  )
5093
5107
  tf_resources.append(iam_policy_resource)
@@ -5346,6 +5360,11 @@ class TerrascriptClient:
5346
5360
  )
5347
5361
  if condition_type_key is None:
5348
5362
  raise KeyError(f"unknown alb rule condition type {condition_type}")
5363
+
5364
+ # Query string conditions use a different structure than other condition types
5365
+ if condition_type == "query-string":
5366
+ return {condition_type_key: condition[condition_type_key]}
5367
+
5349
5368
  return {condition_type_key: {"values": condition[condition_type_key]}}
5350
5369
 
5351
5370
  @staticmethod
@@ -5494,7 +5513,7 @@ class TerrascriptClient:
5494
5513
  lb_access_logs_s3_bucket_policy_values = {
5495
5514
  "provider": provider,
5496
5515
  "bucket": f"${{{lb_access_logs_s3_bucket_tf_resource.id}}}",
5497
- "policy": json.dumps(policy, sort_keys=True),
5516
+ "policy": json_dumps(policy),
5498
5517
  }
5499
5518
  lb_access_logs_s3_bucket_policy_tf_resource = aws_s3_bucket_policy(
5500
5519
  policy_identifier, **lb_access_logs_s3_bucket_policy_values
@@ -5807,9 +5826,13 @@ class TerrascriptClient:
5807
5826
  assert secret # make mypy happy
5808
5827
  secret_data = self.secret_reader.read_all(secret)
5809
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
+
5810
5833
  version_values: dict[str, Any] = {
5811
5834
  "secret_id": "${" + aws_secret_resource.id + "}",
5812
- "secret_string": json.dumps(secret_data, sort_keys=True),
5835
+ "secret_string": json_dumps(secret_data),
5813
5836
  }
5814
5837
 
5815
5838
  if self._multiregion_account(account):
@@ -5830,6 +5853,66 @@ class TerrascriptClient:
5830
5853
 
5831
5854
  self.add_resources(account, tf_resources)
5832
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
+
5833
5916
  def get_commit_sha(self, repo_info: Mapping) -> str:
5834
5917
  url = repo_info["url"]
5835
5918
  ref = repo_info["ref"]
@@ -5848,7 +5931,8 @@ class TerrascriptClient:
5848
5931
  return commit.sha
5849
5932
  case "gitlab":
5850
5933
  gitlab = self.init_gitlab()
5851
- 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}")
5852
5936
  commits = project.commits.list(ref_name=ref, per_page=1, page=1)
5853
5937
  return commits[0].id
5854
5938
  case _:
@@ -6129,7 +6213,7 @@ class TerrascriptClient:
6129
6213
  lambda_iam_role_resource = aws_iam_role(
6130
6214
  "lambda_role",
6131
6215
  name=f"ocm-{identifier}-cognito-lambda-role",
6132
- assume_role_policy=json.dumps(lambda_role_policy),
6216
+ assume_role_policy=json_dumps(lambda_role_policy),
6133
6217
  managed_policy_arns=[lambda_managed_policy_arn],
6134
6218
  force_detach_policies=False,
6135
6219
  max_session_duration=3600,
@@ -6804,7 +6888,7 @@ class TerrascriptClient:
6804
6888
  )
6805
6889
  tf_resources.append(api_gateway_stage_resource)
6806
6890
 
6807
- rest_api_policy = json.dumps({
6891
+ rest_api_policy = json_dumps({
6808
6892
  "Version": "2012-10-17",
6809
6893
  "Statement": [
6810
6894
  {
@@ -6908,7 +6992,7 @@ class TerrascriptClient:
6908
6992
  },
6909
6993
  ],
6910
6994
  }
6911
- cloudwatch_assume_role_policy = json.dumps(policy, sort_keys=True)
6995
+ cloudwatch_assume_role_policy = json_dumps(policy)
6912
6996
 
6913
6997
  cloudwatch_iam_role_resource = aws_iam_role(
6914
6998
  "cloudwatch_assume_role",
@@ -6936,7 +7020,7 @@ class TerrascriptClient:
6936
7020
  ],
6937
7021
  }
6938
7022
 
6939
- cloudwatch_iam_policy_document = json.dumps(policy, sort_keys=True)
7023
+ cloudwatch_iam_policy_document = json_dumps(policy)
6940
7024
 
6941
7025
  cloudwatch_iam_policy_resource = aws_iam_policy(
6942
7026
  "cloudwatch",
@@ -7181,7 +7265,7 @@ class TerrascriptClient:
7181
7265
 
7182
7266
  version_values = {
7183
7267
  "secret_id": "${" + secret_resource.arn + "}",
7184
- "secret_string": json.dumps(secret, sort_keys=True),
7268
+ "secret_string": json_dumps(secret),
7185
7269
  }
7186
7270
  version_resource = aws_secretsmanager_secret_version(
7187
7271
  secret_identifier, **version_values
@@ -7190,7 +7274,7 @@ class TerrascriptClient:
7190
7274
 
7191
7275
  secret_policy_values = {
7192
7276
  "secret_arn": "${" + secret_resource.arn + "}",
7193
- "policy": json.dumps({
7277
+ "policy": json_dumps({
7194
7278
  "Version": "2012-10-17",
7195
7279
  "Statement": [
7196
7280
  {