add rmq module

This commit is contained in:
Kochetkov S 2026-06-29 15:19:47 +03:00
parent 33dd8e5030
commit cf2e527da8
13 changed files with 504 additions and 10 deletions

View File

@ -30,6 +30,51 @@ environments:
min.insync.replicas: "1" min.insync.replicas: "1"
deletionPolicy: orphan deletionPolicy: orphan
rabbitmq:
management_endpoint: http://default-rabbit-cluster.rabbitmq.svc.cluster.local:15672
amqp_host: default-rabbit-cluster.rabbitmq.svc.cluster.local
amqp_port: 5672
policy:
allow_create: true
allow_delete: false
vhosts:
- name: iac-smoke-prod
users:
- name: iac-smoke-prod
permissions:
- vhost: iac-smoke-prod
user: iac-smoke-prod
configure: "^iac\\.smoke\\..*"
write: "^iac\\.smoke\\..*"
read: "^iac\\.smoke\\..*"
exchanges:
- vhost: iac-smoke-prod
name: iac.smoke.events
type: topic
durable: true
auto_delete: false
queues:
- vhost: iac-smoke-prod
name: iac.smoke.queue
durable: true
auto_delete: false
arguments:
x-queue-type: quorum
bindings:
- vhost: iac-smoke-prod
source: iac.smoke.events
destination: iac.smoke.queue
destination_type: queue
routing_key: iac.smoke.created
policies:
- vhost: iac-smoke-prod
name: iac-smoke-queue-ttl
pattern: "^iac\\.smoke\\..*"
apply_to: queues
priority: 1
definition:
message-ttl: 600000
namespaces: namespaces:
- name: block-analyzer - name: block-analyzer
labels: labels:
@ -190,6 +235,15 @@ environments:
lifecycle: lifecycle:
ignore_changes: true ignore_changes: true
- name: rabbitmq-iac-smoke-secret
namespace: pulse
type: rabbitmq
dependencies:
rabbitmq_vhost: iac-smoke-prod
rabbitmq_user: iac-smoke-prod
lifecycle:
ignore_changes: true
- name: iams-secret - name: iams-secret
namespace: iam namespace: iam
type: opaque type: opaque

View File

@ -0,0 +1,49 @@
include "root" {
path = find_in_parent_folders()
}
include "env" {
path = find_in_parent_folders("env.hcl")
expose = true
merge_strategy = "deep"
}
locals {
repo_root = try(get_repo_root(), "${get_terragrunt_dir()}/../../..")
infra_config = yamldecode(file("${local.repo_root}/infrastructure.yaml"))
env_name = basename(dirname(get_terragrunt_dir()))
env_var_prefix = upper(replace(local.env_name, "-", "_"))
env_config = local.infra_config.environments[local.env_name]
rabbitmq_config = try(local.env_config.rabbitmq, {})
}
terraform {
source = "${get_terragrunt_dir()}/../../../modules//rabbitmq"
}
inputs = {
environment = local.env_name
management_endpoint = get_env("RABBITMQ_${local.env_var_prefix}_MANAGEMENT_ENDPOINT", try(local.rabbitmq_config.management_endpoint, ""))
management_username = get_env("RABBITMQ_${local.env_var_prefix}_MANAGEMENT_USERNAME", "")
management_password = get_env("RABBITMQ_${local.env_var_prefix}_MANAGEMENT_PASSWORD", "")
amqp_host = get_env("RABBITMQ_${local.env_var_prefix}_AMQP_HOST", try(local.rabbitmq_config.amqp_host, "rabbitmq.rabbitmq.svc.cluster.local"))
amqp_port = tonumber(get_env("RABBITMQ_${local.env_var_prefix}_AMQP_PORT", tostring(try(local.rabbitmq_config.amqp_port, 5672))))
rabbitmq_policy = try(local.rabbitmq_config.policy, {})
vhosts = try(local.rabbitmq_config.vhosts, [])
users = try(local.rabbitmq_config.users, [])
permissions = try(local.rabbitmq_config.permissions, [])
exchanges = try(local.rabbitmq_config.exchanges, [])
queues = try(local.rabbitmq_config.queues, [])
bindings = try(local.rabbitmq_config.bindings, [])
policies = try(local.rabbitmq_config.policies, [])
yc_token = get_env("YC_TOKEN", "")
yc_service_account_key_file = get_env("YC_SERVICE_ACCOUNT_KEY_FILE", "")
yc_cloud_id = get_env("YC_CLOUD_ID", "")
yc_folder_id = get_env("YC_PROD_FOLDER_ID", get_env("YC_FOLDER_ID", ""))
kubeconfig_path = get_env("KUBECONFIG", "")
kube_context = get_env("KUBE_CONTEXT", "")
}

View File

@ -40,6 +40,15 @@ dependency "database" {
skip_outputs = get_env("TG_SKIP_DEPENDENCY_OUTPUTS", "false") == "true" skip_outputs = get_env("TG_SKIP_DEPENDENCY_OUTPUTS", "false") == "true"
} }
dependency "rabbitmq" {
config_path = "../rabbitmq"
mock_outputs = {
rabbitmq_outputs_map = {}
}
mock_outputs_allowed_terraform_commands = ["init", "validate", "plan"]
skip_outputs = get_env("TG_SKIP_DEPENDENCY_OUTPUTS", "false") == "true"
}
dependency "valkey_users" { dependency "valkey_users" {
config_path = "../valkey-users" config_path = "../valkey-users"
mock_outputs = { mock_outputs = {
@ -150,6 +159,7 @@ inputs = {
yc_service_accounts_map = try(dependency.s3.outputs.service_accounts, {}) yc_service_accounts_map = try(dependency.s3.outputs.service_accounts, {})
database_outputs_map = dependency.database.outputs.database_outputs_map database_outputs_map = dependency.database.outputs.database_outputs_map
rabbitmq_outputs_map = dependency.rabbitmq.outputs.rabbitmq_outputs_map
valkey_outputs_map = dependency.valkey_users.outputs.valkey_outputs_map valkey_outputs_map = dependency.valkey_users.outputs.valkey_outputs_map
constants = { constants = {

View File

@ -40,6 +40,10 @@ terraform {
source = "hashicorp/random" source = "hashicorp/random"
version = "~> 3.1" version = "~> 3.1"
} }
rabbitmq = {
source = "cyrilgdn/rabbitmq"
version = "~> 1.8"
}
} }
} }

View File

@ -9,6 +9,7 @@ locals {
"s3" = "Opaque" "s3" = "Opaque"
"valkey" = "Opaque" "valkey" = "Opaque"
"yc_sa" = "Opaque" "yc_sa" = "Opaque"
"rabbitmq" = "Opaque"
"opaque" = "Opaque" "opaque" = "Opaque"
} }
@ -54,6 +55,20 @@ locals {
) )
} }
rabbitmq_outputs_by_secret = {
for name, secret in local.secrets_map : name => try(
var.rabbitmq_outputs_map[
format(
"%s:%s:%s",
try(secret.dependencies.rabbitmq_ref, "default"),
try(secret.dependencies.rabbitmq_vhost, try(secret.dependencies.vhost, "")),
try(secret.dependencies.rabbitmq_user, try(secret.dependencies.user, ""))
)
],
null
)
}
yc_sa_sources = { yc_sa_sources = {
for name, secret in local.secrets_map : name => try( for name, secret in local.secrets_map : name => try(
var.yc_service_accounts_map[try(secret.dependencies.service_account, "")], var.yc_service_accounts_map[try(secret.dependencies.service_account, "")],
@ -155,6 +170,25 @@ locals {
{ {
for key, config in try(secret.random_keys, {}) : key => random_password.secrets["${name}:${key}"].result for key, config in try(secret.random_keys, {}) : key => random_password.secrets["${name}:${key}"].result
} }
) : secret.secret_type == "rabbitmq" && local.rabbitmq_outputs_by_secret[name] != null ? merge(
{
host = local.rabbitmq_outputs_by_secret[name].host
hostname = "${local.rabbitmq_outputs_by_secret[name].host}:${local.rabbitmq_outputs_by_secret[name].port}"
port = local.rabbitmq_outputs_by_secret[name].port
vhost = local.rabbitmq_outputs_by_secret[name].vhost
user = local.rabbitmq_outputs_by_secret[name].username
username = local.rabbitmq_outputs_by_secret[name].username
password = local.rabbitmq_outputs_by_secret[name].password
uri = local.rabbitmq_outputs_by_secret[name].uri
management_endpoint = local.rabbitmq_outputs_by_secret[name].management_endpoint
},
try(secret.custom_keys, {}),
{
for key, constant_name in try(secret.constant_keys, {}) : key => try(var.constants[constant_name], "")
},
{
for key, config in try(secret.random_keys, {}) : key => random_password.secrets["${name}:${key}"].result
}
) : merge( ) : merge(
try(secret.custom_keys, {}), try(secret.custom_keys, {}),
{ {

View File

@ -13,6 +13,9 @@ variable "secrets" {
valkey_user = optional(string, "") valkey_user = optional(string, "")
bucket = optional(string, "") # Имя бакета для s3 секретов bucket = optional(string, "") # Имя бакета для s3 секретов
service_account = optional(string, "") # Имя YC Service Account для yc_sa секретов service_account = optional(string, "") # Имя YC Service Account для yc_sa секретов
rabbitmq_ref = optional(string, "default")
rabbitmq_vhost = optional(string, "")
rabbitmq_user = optional(string, "")
}), {}) }), {})
yc_sa_key_names = optional(object({ yc_sa_key_names = optional(object({
access_key = optional(string, "access_key") access_key = optional(string, "access_key")
@ -90,6 +93,20 @@ variable "valkey_outputs_map" {
default = {} default = {}
} }
variable "rabbitmq_outputs_map" {
description = "Outputs от модуля RabbitMQ, ключ = ref:vhost:user"
type = map(object({
host = string
port = string
vhost = string
username = string
password = string
uri = string
management_endpoint = string
}))
default = {}
}
variable "constants" { variable "constants" {
description = "Константные значения (CA сертификаты, endpoints и т.д.)" description = "Константные значения (CA сертификаты, endpoints и т.д.)"
type = map(string) type = map(string)

View File

@ -5,9 +5,9 @@ resource "kubernetes_secret" "this" {
} }
metadata { metadata {
name = each.value.name name = each.value.name
namespace = each.value.namespace namespace = each.value.namespace
labels = try(each.value.labels, {}) labels = try(each.value.labels, {})
annotations = try(each.value.annotations, {}) annotations = try(each.value.annotations, {})
} }

View File

@ -1,12 +1,12 @@
variable "secrets" { variable "secrets" {
description = "Список секретов для создания" description = "Список секретов для создания"
type = list(object({ type = list(object({
name = string name = string
namespace = string namespace = string
secret_type = string secret_type = string
data = map(string) data = map(string)
labels = optional(map(string)) labels = optional(map(string))
annotations = optional(map(string)) annotations = optional(map(string))
ignore_changes = optional(bool, false) ignore_changes = optional(bool, false)
})) }))
} }

208
modules/rabbitmq/main.tf Normal file
View File

@ -0,0 +1,208 @@
locals {
rabbitmq_enabled = length(var.vhosts) + length(var.users) + length(var.permissions) + length(var.exchanges) + length(var.queues) + length(var.bindings) + length(var.policies) > 0
management_endpoint = local.rabbitmq_enabled ? var.management_endpoint : "http://127.0.0.1:15672"
management_username = local.rabbitmq_enabled ? var.management_username : "noop"
management_password = local.rabbitmq_enabled ? var.management_password : "noop"
vhosts = {
for vhost in var.vhosts : vhost.name => {
name = vhost.name
}
}
users = {
for user in var.users : user.name => {
name = user.name
tags = try(user.tags, [])
password_length = try(tonumber(user.password_length), var.default_user_password_length)
password_special = try(user.password_special, false)
}
}
permissions = {
for permission in var.permissions : "${permission.vhost}:${permission.user}" => {
vhost = permission.vhost
user = permission.user
configure = tostring(try(permission.configure, ""))
write = tostring(try(permission.write, ""))
read = tostring(try(permission.read, ""))
}
}
exchanges = {
for exchange in var.exchanges : "${exchange.vhost}:${exchange.name}" => {
name = exchange.name
vhost = exchange.vhost
type = try(exchange.type, "topic")
durable = try(exchange.durable, true)
auto_delete = try(exchange.auto_delete, false)
arguments = try(exchange.arguments, {})
}
}
queues = {
for queue in var.queues : "${queue.vhost}:${queue.name}" => {
name = queue.name
vhost = queue.vhost
durable = try(queue.durable, true)
auto_delete = try(queue.auto_delete, false)
arguments = try(queue.arguments, {})
}
}
bindings = {
for idx, binding in var.bindings : "${binding.vhost}:${binding.source}:${binding.destination_type}:${binding.destination}:${try(binding.routing_key, "")}:${idx}" => {
vhost = binding.vhost
source = binding.source
destination = binding.destination
destination_type = binding.destination_type
routing_key = try(binding.routing_key, "")
arguments = try(binding.arguments, {})
}
}
policies = {
for policy in var.policies : "${policy.vhost}:${policy.name}" => {
name = policy.name
vhost = policy.vhost
pattern = policy.pattern
apply_to = try(policy.apply_to, "all")
priority = try(tonumber(policy.priority), 0)
definition = policy.definition
}
}
user_vhosts = {
for item in distinct([
for permission in values(local.permissions) : "${permission.vhost}:${permission.user}"
]) : item => {
vhost = split(":", item)[0]
user = split(":", item)[1]
}
}
}
provider "rabbitmq" {
endpoint = local.management_endpoint
username = local.management_username
password = local.management_password
}
resource "rabbitmq_vhost" "this" {
for_each = local.vhosts
name = each.value.name
lifecycle {
prevent_destroy = true
}
}
resource "random_password" "user" {
for_each = local.users
length = each.value.password_length
special = each.value.password_special
upper = true
lower = true
numeric = true
}
resource "rabbitmq_user" "this" {
for_each = local.users
name = each.value.name
password = random_password.user[each.key].result
tags = each.value.tags
lifecycle {
prevent_destroy = true
ignore_changes = [password]
}
}
resource "rabbitmq_permissions" "this" {
for_each = local.permissions
user = rabbitmq_user.this[each.value.user].name
vhost = rabbitmq_vhost.this[each.value.vhost].name
permissions {
configure = each.value.configure
write = each.value.write
read = each.value.read
}
lifecycle {
prevent_destroy = true
}
}
resource "rabbitmq_exchange" "this" {
for_each = local.exchanges
name = each.value.name
vhost = rabbitmq_vhost.this[each.value.vhost].name
settings {
type = each.value.type
durable = each.value.durable
auto_delete = each.value.auto_delete
arguments = each.value.arguments
}
lifecycle {
prevent_destroy = true
}
}
resource "rabbitmq_queue" "this" {
for_each = local.queues
name = each.value.name
vhost = rabbitmq_vhost.this[each.value.vhost].name
settings {
durable = each.value.durable
auto_delete = each.value.auto_delete
arguments = each.value.arguments
}
lifecycle {
prevent_destroy = true
}
}
resource "rabbitmq_binding" "this" {
for_each = local.bindings
source = rabbitmq_exchange.this["${each.value.vhost}:${each.value.source}"].name
vhost = rabbitmq_vhost.this[each.value.vhost].name
destination = each.value.destination
destination_type = each.value.destination_type
routing_key = each.value.routing_key
arguments = each.value.arguments
lifecycle {
prevent_destroy = true
}
}
resource "rabbitmq_policy" "this" {
for_each = local.policies
name = each.value.name
vhost = rabbitmq_vhost.this[each.value.vhost].name
policy {
pattern = each.value.pattern
priority = each.value.priority
apply_to = each.value.apply_to
definition = each.value.definition
}
lifecycle {
prevent_destroy = true
}
}

View File

@ -0,0 +1,25 @@
output "vhosts" {
description = "RabbitMQ vhosts managed by this component."
value = keys(rabbitmq_vhost.this)
}
output "users" {
description = "RabbitMQ users managed by this component."
value = keys(rabbitmq_user.this)
}
output "rabbitmq_outputs_map" {
description = "Map for k8s-secret module. Key format: default:vhost:user."
value = {
for key, item in local.user_vhosts : "default:${item.vhost}:${item.user}" => {
host = var.amqp_host
port = tostring(var.amqp_port)
vhost = item.vhost
username = rabbitmq_user.this[item.user].name
password = random_password.user[item.user].result
uri = "amqp://${rabbitmq_user.this[item.user].name}:${urlencode(random_password.user[item.user].result)}@${var.amqp_host}:${var.amqp_port}/${urlencode(item.vhost)}"
management_endpoint = var.management_endpoint
}
}
sensitive = true
}

View File

@ -0,0 +1,90 @@
variable "environment" {
description = "Target environment name."
type = string
}
variable "management_endpoint" {
description = "RabbitMQ Management API endpoint, for example http://rabbitmq.rabbitmq.svc.cluster.local:15672."
type = string
}
variable "management_username" {
description = "RabbitMQ admin username for Management API."
type = string
sensitive = true
}
variable "management_password" {
description = "RabbitMQ admin password for Management API."
type = string
sensitive = true
}
variable "amqp_host" {
description = "RabbitMQ AMQP host published to generated Kubernetes secrets."
type = string
default = "rabbitmq.rabbitmq.svc.cluster.local"
}
variable "amqp_port" {
description = "RabbitMQ AMQP port published to generated Kubernetes secrets."
type = number
default = 5672
}
variable "rabbitmq_policy" {
description = "Platform policy for RabbitMQ declarations."
type = object({
allow_create = optional(bool, true)
allow_delete = optional(bool, false)
})
default = {}
}
variable "vhosts" {
description = "RabbitMQ vhosts declared in infrastructure.yaml."
type = list(any)
default = []
}
variable "users" {
description = "RabbitMQ users declared in infrastructure.yaml."
type = list(any)
default = []
}
variable "permissions" {
description = "RabbitMQ permissions declared in infrastructure.yaml."
type = list(any)
default = []
}
variable "exchanges" {
description = "RabbitMQ exchanges declared in infrastructure.yaml."
type = list(any)
default = []
}
variable "queues" {
description = "RabbitMQ queues declared in infrastructure.yaml."
type = list(any)
default = []
}
variable "bindings" {
description = "RabbitMQ bindings declared in infrastructure.yaml."
type = list(any)
default = []
}
variable "policies" {
description = "RabbitMQ policies declared in infrastructure.yaml."
type = list(any)
default = []
}
variable "default_user_password_length" {
description = "Default generated RabbitMQ user password length."
type = number
default = 32
}

View File

@ -0,0 +1,3 @@
terraform {
required_version = ">= 1.5.0"
}

View File

@ -38,7 +38,7 @@ find live -name "terragrunt.hcl" -not -path "*/.terragrunt-cache/*" | sort | whi
secrets_dependency_components=() secrets_dependency_components=()
needs_section="" needs_section=""
if [ "$component" = "secrets" ]; then if [ "$component" = "secrets" ]; then
for dep_component in namespace s3 database valkey-users; do for dep_component in namespace s3 database rabbitmq valkey-users; do
if [ -f "live/${env}/${dep_component}/terragrunt.hcl" ]; then if [ -f "live/${env}/${dep_component}/terragrunt.hcl" ]; then
secrets_dependency_components+=("$dep_component") secrets_dependency_components+=("$dep_component")
fi fi