qontract-reconcile 0.10.2.dev361__py3-none-any.whl → 0.10.2.dev474__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.
- {qontract_reconcile-0.10.2.dev361.dist-info → qontract_reconcile-0.10.2.dev474.dist-info}/METADATA +14 -13
- {qontract_reconcile-0.10.2.dev361.dist-info → qontract_reconcile-0.10.2.dev474.dist-info}/RECORD +371 -364
- {qontract_reconcile-0.10.2.dev361.dist-info → qontract_reconcile-0.10.2.dev474.dist-info}/WHEEL +1 -1
- reconcile/acs_rbac.py +2 -2
- reconcile/aus/advanced_upgrade_service.py +18 -12
- reconcile/aus/aus_sts_gate_handler.py +59 -0
- reconcile/aus/base.py +137 -34
- reconcile/aus/cluster_version_data.py +15 -5
- reconcile/aus/models.py +3 -1
- reconcile/aus/ocm_addons_upgrade_scheduler_org.py +1 -0
- reconcile/aus/ocm_upgrade_scheduler.py +8 -1
- reconcile/aus/ocm_upgrade_scheduler_org.py +20 -5
- reconcile/aus/version_gate_approver.py +1 -16
- reconcile/aus/version_gates/sts_version_gate_handler.py +5 -72
- reconcile/automated_actions/config/integration.py +16 -4
- reconcile/aws_account_manager/integration.py +21 -9
- reconcile/aws_account_manager/reconciler.py +3 -3
- reconcile/aws_account_manager/utils.py +1 -1
- reconcile/aws_ami_cleanup/integration.py +8 -12
- reconcile/aws_ami_share.py +69 -62
- reconcile/aws_cloudwatch_log_retention/integration.py +155 -126
- reconcile/aws_ecr_image_pull_secrets.py +1 -1
- reconcile/aws_iam_keys.py +1 -0
- reconcile/aws_saml_idp/integration.py +12 -4
- reconcile/aws_saml_roles/integration.py +30 -23
- reconcile/aws_version_sync/integration.py +6 -12
- reconcile/change_owners/README.md +1 -1
- reconcile/change_owners/bundle.py +3 -3
- reconcile/change_owners/change_log_tracking.py +3 -2
- reconcile/change_owners/change_owners.py +108 -42
- reconcile/change_owners/decision.py +1 -1
- reconcile/change_owners/diff.py +0 -2
- reconcile/checkpoint.py +11 -3
- reconcile/cli.py +94 -11
- reconcile/dashdotdb_dora.py +5 -12
- reconcile/dashdotdb_slo.py +1 -1
- reconcile/database_access_manager.py +123 -117
- reconcile/dynatrace_token_provider/integration.py +1 -1
- reconcile/endpoints_discovery/integration.py +4 -1
- reconcile/endpoints_discovery/merge_request.py +1 -1
- reconcile/endpoints_discovery/merge_request_manager.py +8 -8
- reconcile/external_resources/factories.py +4 -6
- reconcile/external_resources/integration.py +1 -1
- reconcile/external_resources/manager.py +8 -6
- reconcile/external_resources/meta.py +0 -1
- reconcile/external_resources/metrics.py +1 -1
- reconcile/external_resources/model.py +19 -15
- reconcile/external_resources/reconciler.py +7 -4
- reconcile/external_resources/secrets_sync.py +6 -10
- reconcile/external_resources/state.py +26 -16
- reconcile/fleet_labeler/integration.py +1 -1
- reconcile/gabi_authorized_users.py +5 -2
- reconcile/gcp_image_mirror.py +2 -2
- reconcile/github_org.py +1 -1
- reconcile/github_owners.py +4 -0
- reconcile/gitlab_housekeeping.py +13 -15
- reconcile/gitlab_members.py +6 -12
- reconcile/gitlab_owners.py +15 -11
- reconcile/gitlab_permissions.py +8 -12
- reconcile/glitchtip_project_alerts/integration.py +3 -1
- reconcile/gql_definitions/acs/acs_instances.py +5 -5
- reconcile/gql_definitions/acs/acs_policies.py +5 -5
- reconcile/gql_definitions/acs/acs_rbac.py +5 -5
- reconcile/gql_definitions/advanced_upgrade_service/aus_clusters.py +5 -5
- reconcile/gql_definitions/advanced_upgrade_service/aus_organization.py +5 -5
- reconcile/gql_definitions/app_interface_metrics_exporter/onboarding_status.py +5 -5
- reconcile/gql_definitions/app_sre_tekton_access_revalidation/roles.py +5 -5
- reconcile/gql_definitions/app_sre_tekton_access_revalidation/users.py +5 -5
- reconcile/gql_definitions/automated_actions/instance.py +46 -7
- reconcile/gql_definitions/aws_account_manager/aws_accounts.py +14 -5
- reconcile/gql_definitions/aws_ami_cleanup/aws_accounts.py +15 -5
- reconcile/gql_definitions/aws_cloudwatch_log_retention/aws_accounts.py +27 -66
- reconcile/gql_definitions/aws_saml_idp/aws_accounts.py +15 -5
- reconcile/gql_definitions/aws_saml_roles/aws_accounts.py +15 -5
- reconcile/gql_definitions/aws_saml_roles/roles.py +5 -5
- reconcile/gql_definitions/aws_version_sync/clusters.py +5 -5
- reconcile/gql_definitions/aws_version_sync/namespaces.py +5 -5
- reconcile/gql_definitions/change_owners/queries/change_types.py +5 -5
- reconcile/gql_definitions/change_owners/queries/self_service_roles.py +5 -5
- reconcile/gql_definitions/cluster_auth_rhidp/clusters.py +5 -5
- reconcile/gql_definitions/common/alerting_services_settings.py +5 -5
- reconcile/gql_definitions/common/app_code_component_repos.py +5 -5
- reconcile/gql_definitions/common/app_interface_custom_messages.py +5 -5
- reconcile/gql_definitions/common/app_interface_dms_settings.py +5 -5
- reconcile/gql_definitions/common/app_interface_repo_settings.py +5 -5
- reconcile/gql_definitions/common/app_interface_roles.py +5 -5
- reconcile/gql_definitions/common/app_interface_state_settings.py +5 -5
- reconcile/gql_definitions/common/app_interface_vault_settings.py +5 -5
- reconcile/gql_definitions/common/app_quay_repos_escalation_policies.py +5 -5
- reconcile/gql_definitions/common/apps.py +5 -5
- reconcile/gql_definitions/common/aws_vpc_requests.py +18 -5
- reconcile/gql_definitions/common/aws_vpcs.py +5 -5
- reconcile/gql_definitions/common/clusters.py +7 -5
- reconcile/gql_definitions/common/clusters_minimal.py +5 -5
- reconcile/gql_definitions/common/clusters_with_dms.py +5 -5
- reconcile/gql_definitions/common/clusters_with_peering.py +5 -5
- reconcile/gql_definitions/common/github_orgs.py +5 -5
- reconcile/gql_definitions/common/jira_settings.py +5 -5
- reconcile/gql_definitions/common/jiralert_settings.py +5 -5
- reconcile/gql_definitions/common/ldap_settings.py +5 -5
- reconcile/gql_definitions/common/namespaces.py +5 -5
- reconcile/gql_definitions/common/namespaces_minimal.py +7 -5
- reconcile/gql_definitions/common/ocm_env_telemeter.py +5 -5
- reconcile/gql_definitions/common/ocm_environments.py +5 -5
- reconcile/gql_definitions/common/pagerduty_instances.py +5 -5
- reconcile/gql_definitions/common/pgp_reencryption_settings.py +5 -5
- reconcile/gql_definitions/common/pipeline_providers.py +5 -5
- reconcile/gql_definitions/common/quay_instances.py +5 -5
- reconcile/gql_definitions/common/quay_orgs.py +5 -5
- reconcile/gql_definitions/common/reserved_networks.py +5 -5
- reconcile/gql_definitions/common/rhcs_provider_settings.py +5 -5
- reconcile/gql_definitions/common/saas_files.py +5 -5
- reconcile/gql_definitions/common/saas_target_namespaces.py +5 -5
- reconcile/gql_definitions/common/saasherder_settings.py +5 -5
- reconcile/gql_definitions/common/slack_workspaces.py +5 -5
- reconcile/gql_definitions/common/smtp_client_settings.py +5 -5
- reconcile/gql_definitions/common/state_aws_account.py +5 -5
- reconcile/gql_definitions/common/users.py +5 -5
- reconcile/gql_definitions/common/users_with_paths.py +5 -5
- reconcile/gql_definitions/cost_report/app_names.py +5 -5
- reconcile/gql_definitions/cost_report/cost_namespaces.py +5 -5
- reconcile/gql_definitions/cost_report/settings.py +5 -5
- reconcile/gql_definitions/dashdotdb_slo/slo_documents_query.py +5 -5
- reconcile/gql_definitions/dynatrace_token_provider/dynatrace_bootstrap_tokens.py +5 -5
- reconcile/gql_definitions/dynatrace_token_provider/token_specs.py +5 -5
- reconcile/gql_definitions/email_sender/apps.py +5 -5
- reconcile/gql_definitions/email_sender/emails.py +5 -5
- reconcile/gql_definitions/email_sender/users.py +5 -5
- reconcile/gql_definitions/endpoints_discovery/apps.py +5 -5
- reconcile/gql_definitions/external_resources/aws_accounts.py +5 -5
- reconcile/gql_definitions/external_resources/external_resources_modules.py +5 -5
- reconcile/gql_definitions/external_resources/external_resources_namespaces.py +38 -7
- reconcile/gql_definitions/external_resources/external_resources_settings.py +5 -5
- reconcile/gql_definitions/external_resources/fragments/external_resources_module_overrides.py +5 -5
- reconcile/gql_definitions/fleet_labeler/fleet_labels.py +5 -5
- reconcile/gql_definitions/fragments/aus_organization.py +5 -5
- reconcile/gql_definitions/fragments/aws_account_common.py +7 -5
- reconcile/gql_definitions/fragments/aws_account_managed.py +5 -5
- reconcile/gql_definitions/fragments/aws_account_sso.py +5 -5
- reconcile/gql_definitions/fragments/aws_infra_management_account.py +5 -5
- reconcile/gql_definitions/fragments/aws_organization.py +33 -0
- reconcile/gql_definitions/fragments/aws_vpc.py +5 -5
- reconcile/gql_definitions/fragments/aws_vpc_request.py +12 -5
- reconcile/gql_definitions/fragments/container_image_mirror.py +5 -5
- reconcile/gql_definitions/fragments/deploy_resources.py +5 -5
- reconcile/gql_definitions/fragments/disable.py +5 -5
- reconcile/gql_definitions/fragments/email_service.py +5 -5
- reconcile/gql_definitions/fragments/email_user.py +5 -5
- reconcile/gql_definitions/fragments/jumphost_common_fields.py +5 -5
- reconcile/gql_definitions/fragments/membership_source.py +5 -5
- reconcile/gql_definitions/fragments/minimal_ocm_organization.py +5 -5
- reconcile/gql_definitions/fragments/oc_connection_cluster.py +5 -5
- reconcile/gql_definitions/fragments/ocm_environment.py +5 -5
- reconcile/gql_definitions/fragments/pipeline_provider_retention.py +5 -5
- reconcile/gql_definitions/fragments/prometheus_instance.py +5 -5
- reconcile/gql_definitions/fragments/resource_limits_requirements.py +5 -5
- reconcile/gql_definitions/fragments/resource_requests_requirements.py +5 -5
- reconcile/gql_definitions/fragments/resource_values.py +5 -5
- reconcile/gql_definitions/fragments/saas_slo_document.py +5 -5
- reconcile/gql_definitions/fragments/saas_target_namespace.py +5 -5
- reconcile/gql_definitions/fragments/serviceaccount_token.py +5 -5
- reconcile/gql_definitions/fragments/terraform_state.py +5 -5
- reconcile/gql_definitions/fragments/upgrade_policy.py +5 -5
- reconcile/gql_definitions/fragments/user.py +5 -5
- reconcile/gql_definitions/fragments/vault_secret.py +5 -5
- reconcile/gql_definitions/gcp/gcp_docker_repos.py +5 -5
- reconcile/gql_definitions/gcp/gcp_projects.py +5 -5
- reconcile/gql_definitions/gitlab_members/gitlab_instances.py +5 -5
- reconcile/gql_definitions/gitlab_members/permissions.py +5 -5
- reconcile/gql_definitions/glitchtip/glitchtip_instance.py +5 -5
- reconcile/gql_definitions/glitchtip/glitchtip_project.py +5 -5
- reconcile/gql_definitions/glitchtip_project_alerts/glitchtip_project.py +5 -5
- reconcile/gql_definitions/integrations/integrations.py +5 -5
- reconcile/gql_definitions/introspection.json +775 -136
- reconcile/gql_definitions/jenkins_configs/jenkins_configs.py +5 -5
- reconcile/gql_definitions/jenkins_configs/jenkins_instances.py +5 -5
- reconcile/gql_definitions/jira/jira_servers.py +5 -5
- reconcile/gql_definitions/jira_permissions_validator/jira_boards_for_permissions_validator.py +9 -5
- reconcile/gql_definitions/jumphosts/jumphosts.py +5 -5
- reconcile/gql_definitions/ldap_groups/roles.py +5 -5
- reconcile/gql_definitions/ldap_groups/settings.py +5 -5
- reconcile/gql_definitions/maintenance/maintenances.py +5 -5
- reconcile/gql_definitions/membershipsources/roles.py +5 -5
- reconcile/gql_definitions/ocm_labels/clusters.py +5 -5
- reconcile/gql_definitions/ocm_labels/organizations.py +5 -5
- reconcile/gql_definitions/openshift_cluster_bots/clusters.py +5 -5
- reconcile/gql_definitions/openshift_groups/managed_groups.py +5 -5
- reconcile/gql_definitions/openshift_groups/managed_roles.py +5 -5
- reconcile/gql_definitions/openshift_serviceaccount_tokens/tokens.py +5 -5
- reconcile/gql_definitions/quay_membership/quay_membership.py +5 -5
- reconcile/gql_definitions/rhcs/certs.py +25 -79
- reconcile/gql_definitions/rhcs/openshift_resource_rhcs_cert.py +43 -0
- reconcile/gql_definitions/rhidp/organizations.py +5 -5
- reconcile/gql_definitions/service_dependencies/jenkins_instance_fragment.py +5 -5
- reconcile/gql_definitions/service_dependencies/service_dependencies.py +5 -5
- reconcile/gql_definitions/sharding/aws_accounts.py +5 -5
- reconcile/gql_definitions/sharding/ocm_organization.py +5 -5
- reconcile/gql_definitions/skupper_network/site_controller_template.py +5 -5
- reconcile/gql_definitions/skupper_network/skupper_networks.py +5 -5
- reconcile/gql_definitions/slack_usergroups/clusters.py +5 -5
- reconcile/gql_definitions/slack_usergroups/permissions.py +5 -5
- reconcile/gql_definitions/slack_usergroups/users.py +5 -5
- reconcile/gql_definitions/slo_documents/slo_documents.py +5 -5
- reconcile/gql_definitions/status_board/status_board.py +5 -5
- reconcile/gql_definitions/statuspage/statuspages.py +5 -5
- reconcile/gql_definitions/templating/template_collection.py +5 -5
- reconcile/gql_definitions/templating/templates.py +5 -5
- reconcile/gql_definitions/terraform_cloudflare_dns/app_interface_cloudflare_dns_settings.py +5 -5
- reconcile/gql_definitions/terraform_cloudflare_dns/terraform_cloudflare_zones.py +5 -5
- reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_accounts.py +5 -5
- reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_resources.py +5 -5
- reconcile/gql_definitions/terraform_cloudflare_users/app_interface_setting_cloudflare_and_vault.py +5 -5
- reconcile/gql_definitions/terraform_cloudflare_users/terraform_cloudflare_roles.py +5 -5
- reconcile/gql_definitions/terraform_init/aws_accounts.py +19 -5
- reconcile/gql_definitions/terraform_repo/terraform_repo.py +5 -5
- reconcile/gql_definitions/terraform_resources/database_access_manager.py +5 -5
- reconcile/gql_definitions/terraform_resources/terraform_resources_namespaces.py +37 -7
- reconcile/gql_definitions/terraform_tgw_attachments/aws_accounts.py +15 -5
- reconcile/gql_definitions/unleash_feature_toggles/feature_toggles.py +5 -5
- reconcile/gql_definitions/vault_instances/vault_instances.py +5 -5
- reconcile/gql_definitions/vault_policies/vault_policies.py +5 -5
- reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator.py +8 -5
- reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator_peered_cluster_fragment.py +6 -5
- reconcile/integrations_manager.py +3 -3
- reconcile/jenkins_worker_fleets.py +10 -8
- reconcile/jira_permissions_validator.py +237 -122
- reconcile/ldap_groups/integration.py +1 -1
- reconcile/ocm/types.py +35 -57
- reconcile/ocm_aws_infrastructure_access.py +1 -1
- reconcile/ocm_clusters.py +4 -4
- reconcile/ocm_labels/integration.py +3 -2
- reconcile/ocm_machine_pools.py +33 -27
- reconcile/openshift_base.py +113 -4
- reconcile/openshift_cluster_bots.py +1 -1
- reconcile/openshift_namespace_labels.py +1 -1
- reconcile/openshift_namespaces.py +96 -101
- reconcile/openshift_resources_base.py +6 -2
- reconcile/openshift_rhcs_certs.py +74 -37
- reconcile/openshift_rolebindings.py +7 -11
- reconcile/openshift_saas_deploy.py +4 -5
- reconcile/openshift_saas_deploy_change_tester.py +9 -7
- reconcile/openshift_saas_deploy_trigger_cleaner.py +3 -5
- reconcile/openshift_serviceaccount_tokens.py +2 -2
- reconcile/openshift_upgrade_watcher.py +4 -4
- reconcile/oum/labelset.py +5 -3
- reconcile/oum/models.py +1 -4
- reconcile/prometheus_rules_tester/integration.py +3 -3
- reconcile/quay_base.py +25 -6
- reconcile/quay_membership.py +55 -29
- reconcile/quay_mirror.py +1 -1
- reconcile/quay_mirror_org.py +6 -4
- reconcile/quay_permissions.py +81 -75
- reconcile/quay_repos.py +35 -37
- reconcile/queries.py +132 -1
- reconcile/rhidp/common.py +3 -5
- reconcile/rhidp/sso_client/base.py +16 -5
- reconcile/saas_auto_promotions_manager/merge_request_manager/renderer.py +1 -1
- reconcile/saas_auto_promotions_manager/subscriber.py +4 -3
- reconcile/skupper_network/integration.py +2 -2
- reconcile/slack_usergroups.py +35 -14
- reconcile/sql_query.py +1 -0
- reconcile/status_board.py +6 -6
- reconcile/statuspage/atlassian.py +7 -7
- reconcile/statuspage/integrations/maintenances.py +4 -3
- reconcile/statuspage/page.py +4 -9
- reconcile/statuspage/status.py +5 -8
- reconcile/templates/rosa-classic-cluster-creation.sh.j2 +1 -1
- reconcile/templates/rosa-hcp-cluster-creation.sh.j2 +1 -1
- reconcile/templating/lib/rendering.py +3 -3
- reconcile/templating/renderer.py +2 -2
- reconcile/templating/validator.py +4 -4
- reconcile/terraform_aws_route53.py +7 -1
- reconcile/terraform_cloudflare_dns.py +3 -3
- reconcile/terraform_cloudflare_resources.py +5 -5
- reconcile/terraform_cloudflare_users.py +3 -2
- reconcile/terraform_init/integration.py +187 -23
- reconcile/terraform_repo.py +16 -12
- reconcile/terraform_resources.py +6 -6
- reconcile/terraform_tgw_attachments.py +27 -19
- reconcile/terraform_users.py +7 -0
- reconcile/terraform_vpc_peerings.py +14 -3
- reconcile/terraform_vpc_resources/integration.py +20 -8
- reconcile/terraform_vpc_resources/merge_request.py +12 -2
- reconcile/terraform_vpc_resources/merge_request_manager.py +43 -19
- reconcile/typed_queries/aws_account_tags.py +41 -0
- reconcile/typed_queries/cost_report/app_names.py +1 -1
- reconcile/typed_queries/cost_report/cost_namespaces.py +2 -2
- reconcile/typed_queries/saas_files.py +20 -15
- reconcile/typed_queries/status_board.py +2 -2
- reconcile/unleash_feature_toggles/integration.py +4 -2
- reconcile/utils/acs/base.py +6 -3
- reconcile/utils/acs/policies.py +2 -2
- reconcile/utils/aws_api.py +51 -20
- reconcile/utils/aws_api_typed/api.py +38 -9
- reconcile/utils/aws_api_typed/cloudformation.py +149 -0
- reconcile/utils/aws_api_typed/logs.py +73 -0
- reconcile/utils/aws_api_typed/organization.py +4 -2
- reconcile/utils/binary.py +7 -12
- reconcile/utils/datetime_util.py +67 -0
- reconcile/utils/deadmanssnitch_api.py +1 -1
- reconcile/utils/differ.py +2 -3
- reconcile/utils/early_exit_cache.py +11 -12
- reconcile/utils/environ.py +5 -0
- reconcile/utils/expiration.py +7 -3
- reconcile/utils/external_resource_spec.py +2 -0
- reconcile/utils/filtering.py +1 -1
- reconcile/utils/gitlab_api.py +19 -5
- reconcile/utils/glitchtip/client.py +6 -2
- reconcile/utils/glitchtip/models.py +25 -28
- reconcile/utils/gql.py +4 -7
- reconcile/utils/helpers.py +1 -1
- reconcile/utils/instrumented_wrappers.py +1 -1
- reconcile/utils/internal_groups/client.py +2 -2
- reconcile/utils/internal_groups/models.py +8 -17
- reconcile/utils/jinja2/utils.py +6 -101
- reconcile/utils/jira_client.py +82 -63
- reconcile/utils/jjb_client.py +26 -13
- reconcile/utils/jobcontroller/controller.py +2 -2
- reconcile/utils/jobcontroller/models.py +17 -1
- reconcile/utils/json.py +43 -1
- reconcile/utils/membershipsources/app_interface_resolver.py +4 -2
- reconcile/utils/membershipsources/models.py +16 -23
- reconcile/utils/membershipsources/resolver.py +4 -2
- reconcile/utils/merge_request_manager/merge_request_manager.py +4 -4
- reconcile/utils/merge_request_manager/parser.py +6 -6
- reconcile/utils/metrics.py +5 -5
- reconcile/utils/models.py +304 -82
- reconcile/utils/mr/app_interface_reporter.py +2 -2
- reconcile/utils/mr/notificator.py +3 -3
- reconcile/utils/mr/update_access_report_base.py +3 -4
- reconcile/utils/mr/user_maintenance.py +3 -2
- reconcile/utils/oc.py +252 -201
- reconcile/utils/oc_filters.py +3 -3
- reconcile/utils/ocm/addons.py +0 -1
- reconcile/utils/ocm/base.py +17 -20
- reconcile/utils/ocm/cluster_groups.py +1 -1
- reconcile/utils/ocm/identity_providers.py +2 -2
- reconcile/utils/ocm/labels.py +1 -1
- reconcile/utils/ocm/products.py +8 -8
- reconcile/utils/ocm/search_filters.py +3 -6
- reconcile/utils/ocm/service_log.py +4 -6
- reconcile/utils/ocm/sre_capability_labels.py +20 -13
- reconcile/utils/openshift_resource.py +8 -3
- reconcile/utils/pagerduty_api.py +10 -7
- reconcile/utils/promotion_state.py +6 -11
- reconcile/utils/quay_api.py +74 -87
- reconcile/utils/raw_github_api.py +1 -1
- reconcile/utils/rhcsv2_certs.py +86 -23
- reconcile/utils/rosa/session.py +16 -0
- reconcile/utils/runtime/integration.py +2 -3
- reconcile/utils/runtime/runner.py +2 -2
- reconcile/utils/saasherder/interfaces.py +13 -20
- reconcile/utils/saasherder/models.py +23 -20
- reconcile/utils/saasherder/saasherder.py +50 -27
- reconcile/utils/slack_api.py +2 -2
- reconcile/utils/sloth.py +171 -2
- reconcile/utils/structs.py +1 -1
- reconcile/utils/terraform_client.py +5 -4
- reconcile/utils/terrascript_aws_client.py +274 -124
- reconcile/utils/unleash/server.py +2 -8
- reconcile/utils/vault.py +5 -12
- reconcile/utils/vcs.py +8 -8
- reconcile/vault_replication.py +107 -42
- reconcile/vpc_peerings_validator.py +13 -0
- tools/app_interface_reporter.py +4 -4
- tools/cli_commands/cost_report/cost_management_api.py +3 -3
- tools/cli_commands/cost_report/view.py +7 -6
- tools/cli_commands/erv2.py +1 -1
- tools/qontract_cli.py +28 -17
- tools/template_validation.py +3 -1
- {qontract_reconcile-0.10.2.dev361.dist-info → qontract_reconcile-0.10.2.dev474.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,10 @@ 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
|
|
181
|
+
from reconcile.utils.jinja2.utils import (
|
|
182
|
+
process_extracurlyjinja2_template,
|
|
183
|
+
process_jinja2_template,
|
|
184
|
+
)
|
|
182
185
|
from reconcile.utils.json import json_dumps
|
|
183
186
|
from reconcile.utils.password_validator import (
|
|
184
187
|
PasswordPolicy,
|
|
@@ -203,7 +206,7 @@ if TYPE_CHECKING:
|
|
|
203
206
|
from reconcile.utils.ocm import OCMMap
|
|
204
207
|
|
|
205
208
|
|
|
206
|
-
TFResource
|
|
209
|
+
type TFResource = type[
|
|
207
210
|
Resource | Data | Module | Provider | Variable | Output | Locals | Terraform
|
|
208
211
|
]
|
|
209
212
|
|
|
@@ -268,6 +271,8 @@ VARIABLE_KEYS = [
|
|
|
268
271
|
"extra_tags",
|
|
269
272
|
"lifecycle",
|
|
270
273
|
"max_session_duration",
|
|
274
|
+
"secret_format",
|
|
275
|
+
"policy",
|
|
271
276
|
]
|
|
272
277
|
|
|
273
278
|
EMAIL_REGEX = re.compile(r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$")
|
|
@@ -369,6 +374,10 @@ class aws_s3_bucket_logging(Resource):
|
|
|
369
374
|
pass
|
|
370
375
|
|
|
371
376
|
|
|
377
|
+
class aws_kinesis_resource_policy(Resource):
|
|
378
|
+
pass
|
|
379
|
+
|
|
380
|
+
|
|
372
381
|
class aws_cloudfront_log_delivery_canonical_user_id(Data):
|
|
373
382
|
pass
|
|
374
383
|
|
|
@@ -470,10 +479,10 @@ class TerrascriptClient:
|
|
|
470
479
|
integration_prefix: str,
|
|
471
480
|
thread_pool_size: int,
|
|
472
481
|
accounts: Iterable[MutableMapping[str, Any]],
|
|
482
|
+
default_tags: Mapping[str, str] | None,
|
|
473
483
|
settings: Mapping[str, Any] | None = None,
|
|
474
484
|
prefetch_resources_by_schemas: Iterable[str] | None = None,
|
|
475
485
|
secret_reader: SecretReaderBase | None = None,
|
|
476
|
-
default_tags: Mapping[str, str] | None = None,
|
|
477
486
|
) -> None:
|
|
478
487
|
self.integration = integration
|
|
479
488
|
self.integration_prefix = integration_prefix
|
|
@@ -484,16 +493,11 @@ class TerrascriptClient:
|
|
|
484
493
|
else:
|
|
485
494
|
self.secret_reader = SecretReader(settings=settings)
|
|
486
495
|
self.configs: dict[str, dict] = {}
|
|
496
|
+
self.default_tags = default_tags or {"app": "app-sre-infra"}
|
|
487
497
|
self.populate_configs(filtered_accounts)
|
|
488
498
|
self.versions: dict[str, str] = {
|
|
489
499
|
a["name"]: a["providerVersion"] for a in filtered_accounts
|
|
490
500
|
}
|
|
491
|
-
self.default_tags = {
|
|
492
|
-
"tags": default_tags
|
|
493
|
-
or {
|
|
494
|
-
"app": "app-sre-infra",
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
501
|
tss = {}
|
|
498
502
|
locks = {}
|
|
499
503
|
self.supported_regions = {}
|
|
@@ -510,7 +514,7 @@ class TerrascriptClient:
|
|
|
510
514
|
region=region,
|
|
511
515
|
alias=region,
|
|
512
516
|
skip_region_validation=True,
|
|
513
|
-
default_tags=
|
|
517
|
+
default_tags={"tags": config["tags"]},
|
|
514
518
|
)
|
|
515
519
|
|
|
516
520
|
# Add default region, which will be in resourcesDefaultRegion
|
|
@@ -519,7 +523,7 @@ class TerrascriptClient:
|
|
|
519
523
|
secret_key=config["aws_secret_access_key"],
|
|
520
524
|
region=config["resourcesDefaultRegion"],
|
|
521
525
|
skip_region_validation=True,
|
|
522
|
-
default_tags=
|
|
526
|
+
default_tags={"tags": config["tags"]},
|
|
523
527
|
)
|
|
524
528
|
|
|
525
529
|
ts += Terraform(
|
|
@@ -802,6 +806,9 @@ class TerrascriptClient:
|
|
|
802
806
|
config["supportedDeploymentRegions"] = account["supportedDeploymentRegions"]
|
|
803
807
|
config["resourcesDefaultRegion"] = account["resourcesDefaultRegion"]
|
|
804
808
|
config["terraformState"] = account["terraformState"]
|
|
809
|
+
config["tags"] = dict(self.default_tags) | get_aws_account_tags(
|
|
810
|
+
account.get("organization", None)
|
|
811
|
+
)
|
|
805
812
|
self.configs[account_name] = config
|
|
806
813
|
|
|
807
814
|
def _get_partition(self, account: str) -> str:
|
|
@@ -1056,7 +1063,9 @@ class TerrascriptClient:
|
|
|
1056
1063
|
ignore_changes = (
|
|
1057
1064
|
"all" if "all" in lifecycle.ignore_changes else lifecycle.ignore_changes
|
|
1058
1065
|
)
|
|
1059
|
-
return lifecycle.
|
|
1066
|
+
return lifecycle.model_dump(by_alias=True) | {
|
|
1067
|
+
"ignore_changes": ignore_changes
|
|
1068
|
+
}
|
|
1060
1069
|
return None
|
|
1061
1070
|
|
|
1062
1071
|
def populate_additional_providers(
|
|
@@ -1071,25 +1080,15 @@ class TerrascriptClient:
|
|
|
1071
1080
|
config = self.configs[account_name]
|
|
1072
1081
|
existing_provider_aliases = {p.get("alias") for p in ts["provider"]["aws"]}
|
|
1073
1082
|
if alias not in existing_provider_aliases:
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
)
|
|
1084
|
-
else:
|
|
1085
|
-
ts += provider.aws(
|
|
1086
|
-
access_key=config["aws_access_key_id"],
|
|
1087
|
-
secret_key=config["aws_secret_access_key"],
|
|
1088
|
-
region=region,
|
|
1089
|
-
alias=alias,
|
|
1090
|
-
skip_region_validation=True,
|
|
1091
|
-
default_tags=self.default_tags,
|
|
1092
|
-
)
|
|
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
|
+
)
|
|
1093
1092
|
|
|
1094
1093
|
def populate_route53(
|
|
1095
1094
|
self, desired_state: Iterable[Mapping[str, Any]], default_ttl: int = 300
|
|
@@ -1432,7 +1431,7 @@ class TerrascriptClient:
|
|
|
1432
1431
|
req_account_name = req_account.name
|
|
1433
1432
|
# Accepter's side of the connection - the cluster's account
|
|
1434
1433
|
acc_account = accepter.account
|
|
1435
|
-
acc_alias = self.get_provider_alias(acc_account.
|
|
1434
|
+
acc_alias = self.get_provider_alias(acc_account.model_dump(by_alias=True))
|
|
1436
1435
|
acc_uid = acc_account.uid
|
|
1437
1436
|
if acc_account.assume_role:
|
|
1438
1437
|
acc_uid = awsh.get_account_uid_from_arn(acc_account.assume_role)
|
|
@@ -2218,14 +2217,59 @@ class TerrascriptClient:
|
|
|
2218
2217
|
letters_and_digits = string.ascii_letters + string.digits
|
|
2219
2218
|
return "".join(random.choice(letters_and_digits) for i in range(string_length))
|
|
2220
2219
|
|
|
2221
|
-
|
|
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
|
+
|
|
2257
|
+
def _populate_tf_resource_s3_bucket(
|
|
2258
|
+
self,
|
|
2259
|
+
spec: ExternalResourceSpec,
|
|
2260
|
+
common_values: dict[str, Any],
|
|
2261
|
+
) -> tuple[aws_s3_bucket, list[TFResource]]:
|
|
2262
|
+
"""Create S3 bucket with configuration and notifications.
|
|
2263
|
+
|
|
2264
|
+
Creates aws_s3_bucket with versioning, encryption, lifecycle rules,
|
|
2265
|
+
CORS, logging, and replication. Also creates aws_s3_bucket_notification
|
|
2266
|
+
for SQS/SNS event notifications if configured.
|
|
2267
|
+
"""
|
|
2222
2268
|
account = spec.provisioner_name
|
|
2223
2269
|
identifier = spec.identifier
|
|
2224
|
-
common_values = self.init_values(spec)
|
|
2225
2270
|
output_prefix = spec.output_prefix
|
|
2226
2271
|
|
|
2227
2272
|
tf_resources: list[TFResource] = []
|
|
2228
|
-
self.init_common_outputs(tf_resources, spec)
|
|
2229
2273
|
|
|
2230
2274
|
# s3 bucket
|
|
2231
2275
|
# Terraform resource reference:
|
|
@@ -2257,47 +2301,11 @@ class TerrascriptClient:
|
|
|
2257
2301
|
request_payer = common_values.get("request_payer")
|
|
2258
2302
|
if request_payer:
|
|
2259
2303
|
values["request_payer"] = request_payer
|
|
2260
|
-
lifecycle_rules
|
|
2261
|
-
|
|
2262
|
-
|
|
2304
|
+
if lifecycle_rules := self._build_tf_resource_s3_lifecycle_rules(
|
|
2305
|
+
versioning=versioning,
|
|
2306
|
+
common_values=common_values,
|
|
2307
|
+
):
|
|
2263
2308
|
values["lifecycle_rule"] = lifecycle_rules
|
|
2264
|
-
if versioning:
|
|
2265
|
-
lrs = values.get("lifecycle_rule", [])
|
|
2266
|
-
expiration_rule = False
|
|
2267
|
-
for lr in lrs:
|
|
2268
|
-
if "noncurrent_version_expiration" in lr:
|
|
2269
|
-
expiration_rule = True
|
|
2270
|
-
break
|
|
2271
|
-
if not expiration_rule:
|
|
2272
|
-
# Add a default noncurrent object expiration rule if
|
|
2273
|
-
# if one isn't already set
|
|
2274
|
-
rule = {
|
|
2275
|
-
"id": "expire_noncurrent_versions",
|
|
2276
|
-
"enabled": "true",
|
|
2277
|
-
"noncurrent_version_expiration": {"days": 30},
|
|
2278
|
-
}
|
|
2279
|
-
if len(lrs) > 0:
|
|
2280
|
-
lrs.append(rule)
|
|
2281
|
-
else:
|
|
2282
|
-
lrs = rule
|
|
2283
|
-
sc = common_values.get("storage_class")
|
|
2284
|
-
if sc:
|
|
2285
|
-
sc = sc.upper()
|
|
2286
|
-
days = "1"
|
|
2287
|
-
if sc.endswith("_IA"):
|
|
2288
|
-
# Infrequent Access storage class has minimum 30 days
|
|
2289
|
-
# before transition
|
|
2290
|
-
days = "30"
|
|
2291
|
-
rule = {
|
|
2292
|
-
"id": sc + "_storage_class",
|
|
2293
|
-
"enabled": "true",
|
|
2294
|
-
"transition": {"days": days, "storage_class": sc},
|
|
2295
|
-
"noncurrent_version_transition": {"days": days, "storage_class": sc},
|
|
2296
|
-
}
|
|
2297
|
-
if values.get("lifecycle_rule"):
|
|
2298
|
-
values["lifecycle_rule"].append(rule)
|
|
2299
|
-
else:
|
|
2300
|
-
values["lifecycle_rule"] = rule
|
|
2301
2309
|
cors_rules = common_values.get("cors_rules")
|
|
2302
2310
|
if cors_rules:
|
|
2303
2311
|
# common_values['cors_rules'] is a list of cors_rules
|
|
@@ -2438,8 +2446,7 @@ class TerrascriptClient:
|
|
|
2438
2446
|
output_name = output_prefix + "__endpoint"
|
|
2439
2447
|
tf_resources.append(Output(output_name, value=endpoint))
|
|
2440
2448
|
|
|
2441
|
-
sqs_identifier
|
|
2442
|
-
if sqs_identifier is not None:
|
|
2449
|
+
if sqs_identifier := common_values.get("sqs_identifier"):
|
|
2443
2450
|
sqs_values = {"name": sqs_identifier}
|
|
2444
2451
|
sqs_provider = values.get("provider")
|
|
2445
2452
|
if sqs_provider:
|
|
@@ -2458,11 +2465,9 @@ class TerrascriptClient:
|
|
|
2458
2465
|
}
|
|
2459
2466
|
],
|
|
2460
2467
|
}
|
|
2461
|
-
filter_prefix
|
|
2462
|
-
if filter_prefix is not None:
|
|
2468
|
+
if filter_prefix := common_values.get("filter_prefix"):
|
|
2463
2469
|
notification_values["queue"][0]["filter_prefix"] = filter_prefix
|
|
2464
|
-
filter_suffix
|
|
2465
|
-
if filter_suffix is not None:
|
|
2470
|
+
if filter_suffix := common_values.get("filter_suffix"):
|
|
2466
2471
|
notification_values["queue"][0]["filter_suffix"] = filter_suffix
|
|
2467
2472
|
|
|
2468
2473
|
notification_tf_resource = aws_s3_bucket_notification(
|
|
@@ -2542,21 +2547,48 @@ class TerrascriptClient:
|
|
|
2542
2547
|
)
|
|
2543
2548
|
tf_resources.append(notification_tf_resource)
|
|
2544
2549
|
|
|
2545
|
-
|
|
2546
|
-
if bucket_policy:
|
|
2547
|
-
values = {
|
|
2548
|
-
"bucket": identifier,
|
|
2549
|
-
"policy": bucket_policy,
|
|
2550
|
-
"depends_on": self.get_dependencies([bucket_tf_resource]),
|
|
2551
|
-
}
|
|
2552
|
-
if self._multiregion_account(account):
|
|
2553
|
-
values["provider"] = "aws." + region
|
|
2554
|
-
bucket_policy_tf_resource = aws_s3_bucket_policy(identifier, **values)
|
|
2555
|
-
tf_resources.append(bucket_policy_tf_resource)
|
|
2550
|
+
return bucket_tf_resource, tf_resources
|
|
2556
2551
|
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2552
|
+
def _populate_tf_resource_s3_bucket_policy(
|
|
2553
|
+
self,
|
|
2554
|
+
spec: ExternalResourceSpec,
|
|
2555
|
+
bucket_tf_resource: aws_s3_bucket,
|
|
2556
|
+
policy: str,
|
|
2557
|
+
common_values: dict[str, Any],
|
|
2558
|
+
) -> list[TFResource]:
|
|
2559
|
+
"""Create S3 bucket policy resource.
|
|
2560
|
+
|
|
2561
|
+
Creates aws_s3_bucket_policy with the provided policy document.
|
|
2562
|
+
"""
|
|
2563
|
+
account = spec.provisioner_name
|
|
2564
|
+
identifier = spec.identifier
|
|
2565
|
+
region = common_values.get("region") or self.default_regions.get(account)
|
|
2566
|
+
assert region # make mypy happy
|
|
2567
|
+
|
|
2568
|
+
values: dict[str, Any] = {
|
|
2569
|
+
"bucket": identifier,
|
|
2570
|
+
"policy": policy,
|
|
2571
|
+
"depends_on": self.get_dependencies([bucket_tf_resource]),
|
|
2572
|
+
}
|
|
2573
|
+
if self._multiregion_account(account):
|
|
2574
|
+
values["provider"] = "aws." + region
|
|
2575
|
+
bucket_policy_tf_resource = aws_s3_bucket_policy(identifier, **values)
|
|
2576
|
+
return [bucket_policy_tf_resource]
|
|
2577
|
+
|
|
2578
|
+
def _populate_tf_resource_s3_iam(
|
|
2579
|
+
self,
|
|
2580
|
+
spec: ExternalResourceSpec,
|
|
2581
|
+
bucket_tf_resource: aws_s3_bucket,
|
|
2582
|
+
common_values: dict[str, Any],
|
|
2583
|
+
) -> list[TFResource]:
|
|
2584
|
+
"""Create IAM resources for S3 bucket access.
|
|
2585
|
+
|
|
2586
|
+
Creates aws_iam_user, aws_iam_access_key, aws_iam_policy,
|
|
2587
|
+
and aws_iam_user_policy_attachment for bucket access.
|
|
2588
|
+
"""
|
|
2589
|
+
identifier = spec.identifier
|
|
2590
|
+
output_prefix = spec.output_prefix
|
|
2591
|
+
tf_resources: list[TFResource] = []
|
|
2560
2592
|
|
|
2561
2593
|
# iam user for bucket
|
|
2562
2594
|
values = {
|
|
@@ -2614,6 +2646,32 @@ class TerrascriptClient:
|
|
|
2614
2646
|
)
|
|
2615
2647
|
tf_resources.append(tf_user_policy_attachment)
|
|
2616
2648
|
|
|
2649
|
+
return tf_resources
|
|
2650
|
+
|
|
2651
|
+
def populate_tf_resource_s3(self, spec: ExternalResourceSpec) -> aws_s3_bucket:
|
|
2652
|
+
account = spec.provisioner_name
|
|
2653
|
+
common_values = self.init_values(spec)
|
|
2654
|
+
|
|
2655
|
+
tf_resources: list[TFResource] = []
|
|
2656
|
+
self.init_common_outputs(tf_resources, spec)
|
|
2657
|
+
|
|
2658
|
+
bucket_tf_resource, bucket_resources = self._populate_tf_resource_s3_bucket(
|
|
2659
|
+
spec, common_values
|
|
2660
|
+
)
|
|
2661
|
+
tf_resources.extend(bucket_resources)
|
|
2662
|
+
|
|
2663
|
+
bucket_policy = common_values.get("bucket_policy")
|
|
2664
|
+
if bucket_policy:
|
|
2665
|
+
tf_resources.extend(
|
|
2666
|
+
self._populate_tf_resource_s3_bucket_policy(
|
|
2667
|
+
spec, bucket_tf_resource, bucket_policy, common_values
|
|
2668
|
+
)
|
|
2669
|
+
)
|
|
2670
|
+
|
|
2671
|
+
tf_resources.extend(
|
|
2672
|
+
self._populate_tf_resource_s3_iam(spec, bucket_tf_resource, common_values)
|
|
2673
|
+
)
|
|
2674
|
+
|
|
2617
2675
|
self.add_resources(account, tf_resources)
|
|
2618
2676
|
|
|
2619
2677
|
return bucket_tf_resource
|
|
@@ -3388,42 +3446,53 @@ class TerrascriptClient:
|
|
|
3388
3446
|
common_values = self.init_values(spec)
|
|
3389
3447
|
output_prefix = spec.output_prefix
|
|
3390
3448
|
|
|
3391
|
-
bucket_tf_resource = self.populate_tf_resource_s3(spec)
|
|
3392
|
-
|
|
3393
3449
|
tf_resources: list[TFResource] = []
|
|
3450
|
+
self.init_common_outputs(tf_resources, spec)
|
|
3451
|
+
|
|
3452
|
+
bucket_tf_resource, bucket_resources = self._populate_tf_resource_s3_bucket(
|
|
3453
|
+
spec, common_values
|
|
3454
|
+
)
|
|
3455
|
+
tf_resources.extend(bucket_resources)
|
|
3456
|
+
|
|
3457
|
+
tf_resources.extend(
|
|
3458
|
+
self._populate_tf_resource_s3_iam(spec, bucket_tf_resource, common_values)
|
|
3459
|
+
)
|
|
3394
3460
|
|
|
3395
3461
|
# cloudfront origin access identity
|
|
3396
3462
|
values = {"comment": f"{identifier}-cf-identity"}
|
|
3397
3463
|
cf_oai_tf_resource = aws_cloudfront_origin_access_identity(identifier, **values)
|
|
3398
3464
|
tf_resources.append(cf_oai_tf_resource)
|
|
3399
3465
|
|
|
3400
|
-
# bucket policy for cloudfront
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
"
|
|
3404
|
-
"
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
"Action": "s3:GetObject",
|
|
3410
|
-
"Resource": [
|
|
3411
|
-
f"arn:aws:s3:::{identifier}/{enable_dir}/*"
|
|
3412
|
-
for enable_dir in common_values.get(
|
|
3413
|
-
"get_object_enable_dirs", []
|
|
3414
|
-
)
|
|
3415
|
-
],
|
|
3416
|
-
}
|
|
3466
|
+
# bucket policy for cloudfront - merge custom policy with CloudFront access statement
|
|
3467
|
+
cf_statement = {
|
|
3468
|
+
"Sid": "Grant access to CloudFront Origin Identity",
|
|
3469
|
+
"Effect": "Allow",
|
|
3470
|
+
"Principal": {"AWS": "${" + cf_oai_tf_resource.iam_arn + "}"},
|
|
3471
|
+
"Action": "s3:GetObject",
|
|
3472
|
+
"Resource": [
|
|
3473
|
+
f"arn:aws:s3:::{identifier}/{enable_dir}/*"
|
|
3474
|
+
for enable_dir in common_values.get("get_object_enable_dirs", [])
|
|
3417
3475
|
],
|
|
3418
3476
|
}
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3477
|
+
|
|
3478
|
+
custom_bucket_policy = common_values.get("bucket_policy")
|
|
3479
|
+
if custom_bucket_policy:
|
|
3480
|
+
# if the user specifies a custom bucket policy then we merge their statements with the cloudfront origin identity policy
|
|
3481
|
+
if isinstance(custom_bucket_policy, str):
|
|
3482
|
+
custom_bucket_policy = json.loads(custom_bucket_policy)
|
|
3483
|
+
custom_bucket_policy.setdefault("Statement", []).append(cf_statement)
|
|
3484
|
+
policy = custom_bucket_policy
|
|
3485
|
+
else:
|
|
3486
|
+
policy = {
|
|
3487
|
+
"Version": "2012-10-17",
|
|
3488
|
+
"Statement": [cf_statement],
|
|
3489
|
+
}
|
|
3490
|
+
|
|
3491
|
+
tf_resources.extend(
|
|
3492
|
+
self._populate_tf_resource_s3_bucket_policy(
|
|
3493
|
+
spec, bucket_tf_resource, json_dumps(policy), common_values
|
|
3494
|
+
)
|
|
3495
|
+
)
|
|
3427
3496
|
|
|
3428
3497
|
distribution_config = common_values.get("distribution_config", {})
|
|
3429
3498
|
# aws_s3_bucket_acl
|
|
@@ -4024,6 +4093,22 @@ class TerrascriptClient:
|
|
|
4024
4093
|
kinesis_tf_resource = aws_kinesis_stream(identifier, **kinesis_values)
|
|
4025
4094
|
tf_resources.append(kinesis_tf_resource)
|
|
4026
4095
|
|
|
4096
|
+
# kinesis resource policy (optional)
|
|
4097
|
+
# Terraform resource reference:
|
|
4098
|
+
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kinesis_resource_policy
|
|
4099
|
+
if policy := common_values.get("policy"):
|
|
4100
|
+
policy_identifier = f"{identifier}-policy"
|
|
4101
|
+
policy_values: dict[str, Any] = {
|
|
4102
|
+
"resource_arn": "${" + kinesis_tf_resource.arn + "}",
|
|
4103
|
+
"policy": policy,
|
|
4104
|
+
}
|
|
4105
|
+
if provider:
|
|
4106
|
+
policy_values["provider"] = provider
|
|
4107
|
+
kinesis_policy_tf_resource = aws_kinesis_resource_policy(
|
|
4108
|
+
policy_identifier, **policy_values
|
|
4109
|
+
)
|
|
4110
|
+
tf_resources.append(kinesis_policy_tf_resource)
|
|
4111
|
+
|
|
4027
4112
|
es_identifier = common_values.get("es_identifier", None)
|
|
4028
4113
|
if es_identifier:
|
|
4029
4114
|
es_resource = self._find_resource_spec(
|
|
@@ -5810,6 +5895,10 @@ class TerrascriptClient:
|
|
|
5810
5895
|
assert secret # make mypy happy
|
|
5811
5896
|
secret_data = self.secret_reader.read_all(secret)
|
|
5812
5897
|
|
|
5898
|
+
secret_format = common_values.get("secret_format")
|
|
5899
|
+
if secret_format is not None:
|
|
5900
|
+
secret_data = self._apply_secret_format(str(secret_format), secret_data)
|
|
5901
|
+
|
|
5813
5902
|
version_values: dict[str, Any] = {
|
|
5814
5903
|
"secret_id": "${" + aws_secret_resource.id + "}",
|
|
5815
5904
|
"secret_string": json_dumps(secret_data),
|
|
@@ -5833,6 +5922,66 @@ class TerrascriptClient:
|
|
|
5833
5922
|
|
|
5834
5923
|
self.add_resources(account, tf_resources)
|
|
5835
5924
|
|
|
5925
|
+
@staticmethod
|
|
5926
|
+
def _unflatten_dotted_keys_dict(flat_dict: dict[str, str]) -> dict[str, Any]:
|
|
5927
|
+
"""Convert a flat dictionary with dotted keys to a nested dictionary.
|
|
5928
|
+
|
|
5929
|
+
Example:
|
|
5930
|
+
{"db.host": "localhost", "db.port": "5432"} ->
|
|
5931
|
+
{"db": {"host": "localhost", "port": "5432"}}
|
|
5932
|
+
|
|
5933
|
+
Raises:
|
|
5934
|
+
ValueError: If there are conflicting keys (e.g., "a.b" and "a.b.c")
|
|
5935
|
+
"""
|
|
5936
|
+
result: dict[str, Any] = {}
|
|
5937
|
+
for key, value in flat_dict.items():
|
|
5938
|
+
parts = key.split(".")
|
|
5939
|
+
current = result
|
|
5940
|
+
for i, part in enumerate(parts[:-1]):
|
|
5941
|
+
if part not in current:
|
|
5942
|
+
current[part] = {}
|
|
5943
|
+
elif not isinstance(current[part], dict):
|
|
5944
|
+
# Conflict: trying to traverse through a non-dict value
|
|
5945
|
+
conflicting_path = ".".join(parts[: i + 1])
|
|
5946
|
+
raise ValueError(
|
|
5947
|
+
f"Conflicting keys detected: '{conflicting_path}' is both a "
|
|
5948
|
+
f"value and a nested path in key '{key}'"
|
|
5949
|
+
)
|
|
5950
|
+
current = current[part]
|
|
5951
|
+
|
|
5952
|
+
# Check if we're trying to set a value where a dict already exists
|
|
5953
|
+
if parts[-1] in current and isinstance(current[parts[-1]], dict):
|
|
5954
|
+
raise ValueError(
|
|
5955
|
+
f"Conflicting keys detected: '{key}' conflicts with nested keys"
|
|
5956
|
+
)
|
|
5957
|
+
|
|
5958
|
+
current[parts[-1]] = value
|
|
5959
|
+
|
|
5960
|
+
return result
|
|
5961
|
+
|
|
5962
|
+
@staticmethod
|
|
5963
|
+
def _apply_secret_format(
|
|
5964
|
+
secret_format: str, secret_data: dict[str, str]
|
|
5965
|
+
) -> dict[str, str]:
|
|
5966
|
+
# Convert flat dict with dotted keys to nested dict for Jinja2
|
|
5967
|
+
nested_secret_data = TerrascriptClient._unflatten_dotted_keys_dict(secret_data)
|
|
5968
|
+
rendered_data = process_jinja2_template(secret_format, nested_secret_data)
|
|
5969
|
+
|
|
5970
|
+
parsed_data = json.loads(rendered_data)
|
|
5971
|
+
|
|
5972
|
+
if not isinstance(parsed_data, dict):
|
|
5973
|
+
raise ValueError("secret_format must be a dictionary")
|
|
5974
|
+
|
|
5975
|
+
# validate secret is a dict[str, str]
|
|
5976
|
+
for k, v in parsed_data.items():
|
|
5977
|
+
if not isinstance(k, str):
|
|
5978
|
+
raise ValueError(f"key '{k}' is not a string")
|
|
5979
|
+
|
|
5980
|
+
if not isinstance(v, str):
|
|
5981
|
+
raise ValueError(f"dictionary value '{v}' under '{k}' is not a string")
|
|
5982
|
+
|
|
5983
|
+
return parsed_data
|
|
5984
|
+
|
|
5836
5985
|
def get_commit_sha(self, repo_info: Mapping) -> str:
|
|
5837
5986
|
url = repo_info["url"]
|
|
5838
5987
|
ref = repo_info["ref"]
|
|
@@ -5851,7 +6000,8 @@ class TerrascriptClient:
|
|
|
5851
6000
|
return commit.sha
|
|
5852
6001
|
case "gitlab":
|
|
5853
6002
|
gitlab = self.init_gitlab()
|
|
5854
|
-
project
|
|
6003
|
+
if not (project := gitlab.get_project(url)):
|
|
6004
|
+
raise ValueError(f"could not find gitlab project for url {url}")
|
|
5855
6005
|
commits = project.commits.list(ref_name=ref, per_page=1, page=1)
|
|
5856
6006
|
return commits[0].id
|
|
5857
6007
|
case _:
|
|
@@ -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:
|
reconcile/utils/vault.py
CHANGED
|
@@ -6,7 +6,7 @@ import threading
|
|
|
6
6
|
import time
|
|
7
7
|
from collections.abc import Mapping
|
|
8
8
|
from functools import lru_cache
|
|
9
|
-
from typing import Any, Self
|
|
9
|
+
from typing import Any, Self
|
|
10
10
|
|
|
11
11
|
import hvac
|
|
12
12
|
import requests
|
|
@@ -48,13 +48,6 @@ class VaultConnectionError(Exception):
|
|
|
48
48
|
pass
|
|
49
49
|
|
|
50
50
|
|
|
51
|
-
class Secret(TypedDict):
|
|
52
|
-
path: str
|
|
53
|
-
field: str
|
|
54
|
-
format: str | None
|
|
55
|
-
version: str | None
|
|
56
|
-
|
|
57
|
-
|
|
58
51
|
SECRET_VERSION_LATEST = "LATEST"
|
|
59
52
|
|
|
60
53
|
|
|
@@ -197,7 +190,7 @@ class VaultClient:
|
|
|
197
190
|
self._client.auth_approle(self.role_id, self.secret_id)
|
|
198
191
|
|
|
199
192
|
@retry()
|
|
200
|
-
def read_all_with_version(self, secret: Mapping) -> tuple[
|
|
193
|
+
def read_all_with_version(self, secret: Mapping) -> tuple[dict, int | None]:
|
|
201
194
|
"""Returns a dictionary of keys and values in a Vault secret and the
|
|
202
195
|
version of the secret, for V1 secrets, version will be None.
|
|
203
196
|
|
|
@@ -207,7 +200,7 @@ class VaultClient:
|
|
|
207
200
|
a v2 KV engine)
|
|
208
201
|
"""
|
|
209
202
|
secret_path = secret["path"]
|
|
210
|
-
secret_version = secret.get("version")
|
|
203
|
+
secret_version = secret.get("version") or SECRET_VERSION_LATEST
|
|
211
204
|
|
|
212
205
|
kv_version = self._get_mount_version_by_secret_path(secret_path)
|
|
213
206
|
|
|
@@ -250,7 +243,7 @@ class VaultClient:
|
|
|
250
243
|
|
|
251
244
|
def __read_all_v2(
|
|
252
245
|
self, path: str, version: str | None
|
|
253
|
-
) -> tuple[dict[str, Any],
|
|
246
|
+
) -> tuple[dict[str, Any], int]:
|
|
254
247
|
path_split = path.split("/")
|
|
255
248
|
mount_point = path_split[0]
|
|
256
249
|
read_path = "/".join(path_split[1:])
|
|
@@ -294,7 +287,7 @@ class VaultClient:
|
|
|
294
287
|
return secret["data"]
|
|
295
288
|
|
|
296
289
|
@retry()
|
|
297
|
-
def read(self, secret:
|
|
290
|
+
def read(self, secret: Mapping[str, Any]) -> Any:
|
|
298
291
|
"""Returns a value of a key in a Vault secret.
|
|
299
292
|
|
|
300
293
|
The input secret is a dictionary which contains the following fields:
|