diff --git a/live/database/terragrunt.hcl b/live/database/terragrunt.hcl new file mode 100644 index 0000000..0d31e4b --- /dev/null +++ b/live/database/terragrunt.hcl @@ -0,0 +1,50 @@ +include "root" { + path = find_in_parent_folders() +} + +locals { + repo_root = try(get_repo_root(), "${get_terragrunt_dir()}/../..") + infra_config = yamldecode(file("${local.repo_root}/infrastructure.yaml")) + + env_override = trimspace(get_env("INFRA_ENV", "")) + env_from_file = try(trimspace(local.infra_config.current_environment), "") + available_envs = sort(keys(try(local.infra_config.environments, {}))) + env_fallback = try(local.available_envs[0], "") + + env_name = local.env_override != "" ? local.env_override : ( + local.env_from_file != "" ? local.env_from_file : local.env_fallback + ) + + env_config = try(local.infra_config.environments[local.env_name], {}) +} + +remote_state { + backend = "s3" + generate = { + path = "backend.tf" + if_exists = "overwrite_terragrunt" + } + config = { + endpoint = get_env("TF_STATE_S3_ENDPOINT", "") + bucket = get_env("TF_STATE_S3_BUCKET", "") + key = "${local.env_name}/database/terraform.tfstate" + region = get_env("TF_STATE_S3_REGION", "ru-central1") + access_key = get_env("S3_ACCESS_KEY", get_env("AWS_ACCESS_KEY_ID", "")) + secret_key = get_env("S3_SECRET_KEY", get_env("AWS_SECRET_ACCESS_KEY", "")) + skip_region_validation = true + skip_credentials_validation = true + skip_metadata_api_check = true + skip_bucket_root_access = true + force_path_style = true + } +} + +terraform { + source = "${get_terragrunt_dir()}/../../modules//pg-incluster" +} + +inputs = { + databases = try(local.env_config.databases, []) + default_host = try(local.env_config.postgresql.default_host, "postgresql.postgresql.svc.cluster.local") + default_port = try(local.env_config.postgresql.default_port, 5432) +} diff --git a/live/kafka-topics/terragrunt.hcl b/live/kafka-topics/terragrunt.hcl new file mode 100644 index 0000000..9055b4c --- /dev/null +++ b/live/kafka-topics/terragrunt.hcl @@ -0,0 +1,52 @@ +include "root" { + path = find_in_parent_folders() +} + +locals { + repo_root = try(get_repo_root(), "${get_terragrunt_dir()}/../..") + infra_config = yamldecode(file("${local.repo_root}/infrastructure.yaml")) + + env_override = trimspace(get_env("INFRA_ENV", "")) + env_from_file = try(trimspace(local.infra_config.current_environment), "") + available_envs = sort(keys(try(local.infra_config.environments, {}))) + env_fallback = try(local.available_envs[0], "") + + env_name = local.env_override != "" ? local.env_override : ( + local.env_from_file != "" ? local.env_from_file : local.env_fallback + ) + + env_config = try(local.infra_config.environments[local.env_name], {}) +} + +remote_state { + backend = "s3" + generate = { + path = "backend.tf" + if_exists = "overwrite_terragrunt" + } + config = { + endpoint = get_env("TF_STATE_S3_ENDPOINT", "") + bucket = get_env("TF_STATE_S3_BUCKET", "") + key = "${local.env_name}/kafka-topics/terraform.tfstate" + region = get_env("TF_STATE_S3_REGION", "ru-central1") + access_key = get_env("S3_ACCESS_KEY", get_env("AWS_ACCESS_KEY_ID", "")) + secret_key = get_env("S3_SECRET_KEY", get_env("AWS_SECRET_ACCESS_KEY", "")) + skip_region_validation = true + skip_credentials_validation = true + skip_metadata_api_check = true + skip_bucket_root_access = true + force_path_style = true + } +} + +terraform { + source = "${get_terragrunt_dir()}/../../modules//kafka-topics-incluster" +} + +inputs = { + environment = local.env_name + topics = try(local.env_config.kafka.topics, []) + users = try(local.env_config.kafka.users, []) + kafka_cluster_refs = try(local.env_config.kafka.kafka_cluster_refs, {}) + kafka_policy = try(local.env_config.kafka.kafka_policy, {}) +} diff --git a/live/rabbitmq/terragrunt.hcl b/live/rabbitmq/terragrunt.hcl new file mode 100644 index 0000000..7c1d221 --- /dev/null +++ b/live/rabbitmq/terragrunt.hcl @@ -0,0 +1,62 @@ +include "root" { + path = find_in_parent_folders() +} + +locals { + repo_root = try(get_repo_root(), "${get_terragrunt_dir()}/../..") + infra_config = yamldecode(file("${local.repo_root}/infrastructure.yaml")) + + env_override = trimspace(get_env("INFRA_ENV", "")) + env_from_file = try(trimspace(local.infra_config.current_environment), "") + available_envs = sort(keys(try(local.infra_config.environments, {}))) + env_fallback = try(local.available_envs[0], "") + + env_name = local.env_override != "" ? local.env_override : ( + local.env_from_file != "" ? local.env_from_file : local.env_fallback + ) + + env_config = try(local.infra_config.environments[local.env_name], {}) +} + +remote_state { + backend = "s3" + generate = { + path = "backend.tf" + if_exists = "overwrite_terragrunt" + } + config = { + endpoint = get_env("TF_STATE_S3_ENDPOINT", "") + bucket = get_env("TF_STATE_S3_BUCKET", "") + key = "${local.env_name}/rabbitmq/terraform.tfstate" + region = get_env("TF_STATE_S3_REGION", "ru-central1") + access_key = get_env("S3_ACCESS_KEY", get_env("AWS_ACCESS_KEY_ID", "")) + secret_key = get_env("S3_SECRET_KEY", get_env("AWS_SECRET_ACCESS_KEY", "")) + skip_region_validation = true + skip_credentials_validation = true + skip_metadata_api_check = true + skip_bucket_root_access = true + force_path_style = true + } +} + +terraform { + source = "${get_terragrunt_dir()}/../../modules//rabbitmq" +} + +inputs = { + environment = local.env_name + management_endpoint = get_env("RABBITMQ_ENDPOINT", "http://rabbitmq.rabbitmq.svc.cluster.local:15672") + management_username = get_env("RABBITMQ_ADMIN_USER", "admin") + management_password = get_env("RABBITMQ_ADMIN_PASSWORD", "") + amqp_host = try(local.env_config.rabbitmq.amqp_host, "rabbitmq.rabbitmq.svc.cluster.local") + amqp_port = try(local.env_config.rabbitmq.amqp_port, 5672) + + vhosts = try(local.env_config.rabbitmq.vhosts, []) + users = try(local.env_config.rabbitmq.users, []) + permissions = try(local.env_config.rabbitmq.permissions, []) + topic_permissions = try(local.env_config.rabbitmq.topic_permissions, []) + exchanges = try(local.env_config.rabbitmq.exchanges, []) + queues = try(local.env_config.rabbitmq.queues, []) + bindings = try(local.env_config.rabbitmq.bindings, []) + policies = try(local.env_config.rabbitmq.policies, []) +} diff --git a/live/s3/terragrunt.hcl b/live/s3/terragrunt.hcl new file mode 100644 index 0000000..e67ea8a --- /dev/null +++ b/live/s3/terragrunt.hcl @@ -0,0 +1,48 @@ +include "root" { + path = find_in_parent_folders() +} + +locals { + repo_root = try(get_repo_root(), "${get_terragrunt_dir()}/../..") + infra_config = yamldecode(file("${local.repo_root}/infrastructure.yaml")) + + env_override = trimspace(get_env("INFRA_ENV", "")) + env_from_file = try(trimspace(local.infra_config.current_environment), "") + available_envs = sort(keys(try(local.infra_config.environments, {}))) + env_fallback = try(local.available_envs[0], "") + + env_name = local.env_override != "" ? local.env_override : ( + local.env_from_file != "" ? local.env_from_file : local.env_fallback + ) + + env_config = try(local.infra_config.environments[local.env_name], {}) +} + +remote_state { + backend = "s3" + generate = { + path = "backend.tf" + if_exists = "overwrite_terragrunt" + } + config = { + endpoint = get_env("TF_STATE_S3_ENDPOINT", "") + bucket = get_env("TF_STATE_S3_BUCKET", "") + key = "${local.env_name}/s3/terraform.tfstate" + region = get_env("TF_STATE_S3_REGION", "ru-central1") + access_key = get_env("S3_ACCESS_KEY", get_env("AWS_ACCESS_KEY_ID", "")) + secret_key = get_env("S3_SECRET_KEY", get_env("AWS_SECRET_ACCESS_KEY", "")) + skip_region_validation = true + skip_credentials_validation = true + skip_metadata_api_check = true + skip_bucket_root_access = true + force_path_style = true + } +} + +terraform { + source = "${get_terragrunt_dir()}/../../modules//minio-buckets" +} + +inputs = { + buckets = try(local.env_config.buckets, []) +} diff --git a/live/secrets/terragrunt.hcl b/live/secrets/terragrunt.hcl new file mode 100644 index 0000000..efd1b84 --- /dev/null +++ b/live/secrets/terragrunt.hcl @@ -0,0 +1,143 @@ +include "root" { + path = find_in_parent_folders() +} + +locals { + repo_root = try(get_repo_root(), "${get_terragrunt_dir()}/../..") + infra_config = yamldecode(file("${local.repo_root}/infrastructure.yaml")) + + env_override = trimspace(get_env("INFRA_ENV", "")) + env_from_file = try(trimspace(local.infra_config.current_environment), "") + available_envs = sort(keys(try(local.infra_config.environments, {}))) + env_fallback = try(local.available_envs[0], "") + + env_name = local.env_override != "" ? local.env_override : ( + local.env_from_file != "" ? local.env_from_file : local.env_fallback + ) + + # Single declarative source: shape + data in one sops file. In CI it is + # decrypted ahead of time and the path is passed via INFRA_SECRET_VALUES_FILE; + # locally we fall back to direct sops decryption. Values reach the module only + # via inputs (ephemeral), never written to disk. + secret_values_env_file = get_env("INFRA_SECRET_VALUES_FILE", "") + secret_values_raw = local.secret_values_env_file != "" ? file(local.secret_values_env_file) : try(sops_decrypt_file("${local.repo_root}/infrastructure-secrets.yaml"), "") + secret_values_config = try(yamldecode(local.secret_values_raw), {}) + secret_values_env = try(local.secret_values_config.environments[local.env_name], {}) + + secrets = try(local.secret_values_env.secrets, []) + + secrets_input = [ + for secret in local.secrets : { + name = secret.name + namespace = try(secret.namespace, "") + type = try(secret.type, "opaque") + registry_url = try(secret.registry_url, "") + data = try(secret.data, {}) + depends_on = try(secret.depends_on, {}) + yc_sa_key_names = try(secret.yc_sa_key_names, {}) + custom_keys = try(secret.custom_keys, {}) + constant_keys = try(secret.constant_keys, {}) + random_keys = try(secret.random_keys, {}) + credential_keys = try(secret.credential_keys, {}) + labels = try(secret.labels, {}) + annotations = try(secret.annotations, {}) + ignore_changes = try(secret.lifecycle.ignore_changes, false) + # Sink: k8s (default) or vault. vault_path is the KV name (default /). + sink = try(secret.sink, "k8s") + vault_path = try(secret.vault_path, "") + } + ] +} + +dependency "namespace" { + config_path = "../namespace" + mock_outputs = { namespaces = {}, names = [] } + mock_outputs_allowed_terraform_commands = ["init", "validate", "plan"] +} + +dependency "s3" { + config_path = "../s3" + mock_outputs = { bucket_name = "mock", access_key = "mock", secret_key = "mock", buckets = {}, service_accounts = {} } + mock_outputs_allowed_terraform_commands = ["init", "validate", "plan"] +} + +dependency "database" { + config_path = "../database" + mock_outputs = { database_outputs_map = {} } + mock_outputs_allowed_terraform_commands = ["init", "validate", "plan"] +} + +dependency "rabbitmq" { + config_path = "../rabbitmq" + mock_outputs = { rabbitmq_outputs_map = {} } + mock_outputs_allowed_terraform_commands = ["init", "validate", "plan"] +} + +dependency "kafka_topics" { + config_path = "../kafka-topics" + mock_outputs = { kafka_outputs_map = {} } + mock_outputs_allowed_terraform_commands = ["init", "validate", "plan"] +} + +remote_state { + backend = "s3" + generate = { + path = "backend.tf" + if_exists = "overwrite_terragrunt" + } + config = { + endpoint = get_env("TF_STATE_S3_ENDPOINT", "") + bucket = get_env("TF_STATE_S3_BUCKET", "") + key = "${local.env_name}/secrets/terraform.tfstate" + region = get_env("TF_STATE_S3_REGION", "ru-central1") + access_key = get_env("S3_ACCESS_KEY", get_env("AWS_ACCESS_KEY_ID", "")) + secret_key = get_env("S3_SECRET_KEY", get_env("AWS_SECRET_ACCESS_KEY", "")) + skip_region_validation = true + skip_credentials_validation = true + skip_metadata_api_check = true + skip_bucket_root_access = true + force_path_style = true + } +} + +terraform { + source = "${get_terragrunt_dir()}/../../modules//k8s-secret" +} + +inputs = { + secrets = local.secrets_input + + s3_outputs = { + bucket_name = dependency.s3.outputs.bucket_name + access_key = dependency.s3.outputs.access_key + secret_key = dependency.s3.outputs.secret_key + } + s3_buckets_map = dependency.s3.outputs.buckets + yc_service_accounts_map = try(dependency.s3.outputs.service_accounts, {}) + + database_outputs_map = dependency.database.outputs.database_outputs_map + rabbitmq_outputs_map = try(dependency.rabbitmq.outputs.rabbitmq_outputs_map, {}) + kafka_outputs_map = try(dependency.kafka_topics.outputs.kafka_outputs_map, {}) + valkey_outputs_map = {} + + # In-cluster constants (contour). No managed-YC CA; in-cluster pgbouncer/pg on 5432. + constants = { + s3_endpoint = get_env("MINIO_PUBLIC_ENDPOINT", "http://minio.minio.svc.cluster.local:9000") + postgres_port = "5432" + postgres_ca = "" + valkey_port = "6379" + valkey_ca = "" + } + + # Vault sink target (KV v2). Mount/prefix match the vault-secrets platform layout. + vault_kv_mount = try(local.secret_values_env.vault.kv_mount, "secrets") + vault_path_prefix = try(local.secret_values_env.vault.path_prefix, "vault") + + env_vars = { + DOCKER_REGISTRY_USERNAME = get_env("DOCKER_REGISTRY_USERNAME", "") + DOCKER_REGISTRY_PASSWORD = get_env("DOCKER_REGISTRY_PASSWORD", "") + } + + kubeconfig_path = get_env("KUBECONFIG", "") + kube_context = get_env("KUBE_CONTEXT", "") +} diff --git a/live/terragrunt.hcl b/live/terragrunt.hcl index 2a10116..c38e0ef 100644 --- a/live/terragrunt.hcl +++ b/live/terragrunt.hcl @@ -91,12 +91,8 @@ provider "kafka" { sasl_password = "${get_env("KAFKA_ADMIN_PASSWORD", "")}" } -# In-cluster RabbitMQ management API. Admin from Vault secrets/rabbitmq/auth. -provider "rabbitmq" { - endpoint = "${get_env("RABBITMQ_ENDPOINT", "http://rabbitmq.rabbitmq.svc.cluster.local:15672")}" - username = "${get_env("RABBITMQ_ADMIN_USER", "admin")}" - password = "${get_env("RABBITMQ_ADMIN_PASSWORD", "")}" -} +# RabbitMQ provider is configured INSIDE modules/rabbitmq (from its management_* +# inputs), so it is intentionally NOT generated here to avoid a duplicate config. # In-cluster MinIO S3 API. Admin from Vault secrets/minio/admin. provider "minio" {