feat(iot): Этапы 2-7 — MQTT auth, IoT API, EMQX, mqtt-bridge, E2E demo
Этап 2+4: internal/api/handler/iot_device_handler.go
- MQTTAuth: POST /internal/mqtt/auth (без JWT, для EMQX)
- CreateIoTDevice, ListIoTDevices, GetIoTDevice (c password), DeleteIoTDevice, UpdateIoTDevice
- crypto/subtle.ConstantTimeCompare против timing attacks
Этап 4: internal/api/router.go
- /v1/namespaces/{ns}/iot/devices CRUD
- /internal/mqtt/auth (без JWT middleware)
Этап 3: deployments/k8s/emqx.yaml
- EMQX 5.5.1, emqx.conf (HOCON) с HTTP auth backend
- Сервис exposure: 1883 (MQTT), 8083 (WS), 18083 (Dashboard)
Этап 3: iot/cmd/mqtt-bridge/main.go
- paho.mqtt.golang: подписка на +/telemetry/+
- amqp091-go: publish в iot.{namespace}.telemetry
- deployments/k8s/iot-mqtt-bridge.yaml
Этап 7: examples/IOT/ — E2E demo (main.tf, handler.py, README.md)
go.mod: добавлен github.com/eclipse/paho.mqtt.golang v1.5.1
go build ./... — ошибок нет
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
# Terraform provider plugins
|
||||
.terraform/
|
||||
.terraform.lock.hcl
|
||||
|
||||
# Terraform state
|
||||
terraform.tfstate
|
||||
terraform.tfstate.backup
|
||||
*.tfstate
|
||||
*.tfstate.backup
|
||||
|
||||
# Sensitive data
|
||||
terraform.tfvars
|
||||
!terraform.tfvars.example
|
||||
|
||||
# Backup files
|
||||
*.bak
|
||||
*.bak_db
|
||||
*.bak_*
|
||||
|
||||
# Test artifacts
|
||||
test_*.log
|
||||
@@ -1,94 +0,0 @@
|
||||
// 2026-04-01 — postgres.tf: Managed PostgreSQL инстанс, пользователь и база данных.
|
||||
//
|
||||
// Порядок создания:
|
||||
// 1. nubes_postgres — сам инстанс PostgreSQL
|
||||
// 2. nubes_postgres_user — пользователь; пароль автоматически попадает в vault_secrets
|
||||
// 3. nubes_postgres_database — база данных с owner = созданный пользователь
|
||||
//
|
||||
// Важно: vault_secrets["users"] появляется только ПОСЛЕ первого apply (нет пользователя — нет ключа).
|
||||
// try() в locals страхует от ошибки на первом прогоне.
|
||||
|
||||
// ── Locals: credentials из vault ─────────────────────────────────────────────
|
||||
|
||||
locals {
|
||||
# Карта username→{password, username} из vault_secrets, который Nubes заполняет после
|
||||
# создания пользователя. try() нужен для первого apply, когда ключа ещё нет.
|
||||
pg_creds_map = try(
|
||||
jsondecode(lookup(nubes_postgres.pg_test_instance.vault_secrets, "users", "{}")),
|
||||
{}
|
||||
)
|
||||
pg_password = try(local.pg_creds_map[var.pg_username]["password"], "")
|
||||
|
||||
# Адрес master-ноды (внутренний — для подключения из кластера).
|
||||
pg_host = nubes_postgres.pg_test_instance.state_out_flat["internalConnect.master"]
|
||||
pg_port = 5432
|
||||
}
|
||||
|
||||
// ── Инстанс PostgreSQL ────────────────────────────────────────────────────────
|
||||
|
||||
resource "nubes_postgres" "pg_test_instance" {
|
||||
resource_name = var.pg_resource_name
|
||||
s3_uid = var.s3_uid
|
||||
resource_realm = var.realm
|
||||
|
||||
# Минимальные ресурсы — достаточно для тестирования.
|
||||
resource_instances = 1
|
||||
resource_memory = 512 # MiB
|
||||
resource_c_p_u = 500 # millicores
|
||||
resource_disk = "1" # GiB
|
||||
app_version = "17"
|
||||
|
||||
# json_parameters убран — при передаче пустого объекта API возвращает "Invalid JSON String".
|
||||
# Если нужны кастомные параметры PG — добавить после диагностики.
|
||||
|
||||
# Pooler не нужен для тестов — упрощает топологию.
|
||||
enable_pg_pooler_master = false
|
||||
enable_pg_pooler_slave = false
|
||||
|
||||
allow_no_s_s_l = false
|
||||
auto_scale = false
|
||||
auto_scale_percentage = 10
|
||||
auto_scale_tech_window = 0
|
||||
auto_scale_quota_gb = "1"
|
||||
|
||||
# Внешний адрес не нужен — подключаемся изнутри кластера.
|
||||
need_external_address_master = false
|
||||
|
||||
operation_timeout = "11m"
|
||||
|
||||
# Позволяет импортировать уже существующий инстанс с тем же именем, не падая
|
||||
# с "already exists" — удобно при повторном apply после ручного создания.
|
||||
adopt_existing_on_create = true
|
||||
}
|
||||
|
||||
// ── Пользователь ──────────────────────────────────────────────────────────────
|
||||
|
||||
resource "nubes_postgres_user" "pg_test_user" {
|
||||
postgres_id = nubes_postgres.pg_test_instance.id
|
||||
username = var.pg_username
|
||||
role = var.pg_role
|
||||
|
||||
# Не падать если пользователь с таким именем уже существует.
|
||||
adopt_existing_on_create = true
|
||||
}
|
||||
|
||||
resource "nubes_postgres_user" "pg_test_user3" {
|
||||
postgres_id = nubes_postgres.pg_test_instance.id
|
||||
username = "u3"
|
||||
role = var.pg_role
|
||||
|
||||
depends_on = [nubes_postgres_user.pg_test_user]
|
||||
# Не падать если пользователь с таким именем уже существует.
|
||||
adopt_existing_on_create = true
|
||||
}
|
||||
|
||||
// ── База данных ───────────────────────────────────────────────────────────────
|
||||
|
||||
resource "nubes_postgres_database" "pg_test_db" {
|
||||
postgres_id = nubes_postgres.pg_test_instance.id
|
||||
db_name = var.pg_db_name
|
||||
db_owner = nubes_postgres_user.pg_test_user.username
|
||||
|
||||
# Не падать если БД уже существует.
|
||||
adopt_existing_on_create = true
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
// 2026-04-01 — postgres_extra.tf: дополнительные пользователи и базы данных для lifecycle-тестов.
|
||||
//
|
||||
// ВАЖНО: ресурсы создаются строго последовательно через depends_on.
|
||||
// Параллельное создание нескольких пользователей в одном инстансе вызывает
|
||||
// ошибки API ("Секрет не был создан", "key doesn't exist") — race condition на стороне Nubes.
|
||||
//
|
||||
// ИЗВЕСТНОЕ ОГРАНИЧЕНИЕ: если apply упал в середине создания пользователя —
|
||||
// этот пользователь может "зависнуть" в промежуточном состоянии в Nubes API.
|
||||
// adopt_existing_on_create не спасает. Решение: использовать имена без истории,
|
||||
// либо ждать очистки на стороне Nubes.
|
||||
|
||||
// ── Пользователь 1 ───────────────────────────────────────────────────────────
|
||||
// Первый в extra-цепочке. Ждёт pg_test_db (postgres.tf).
|
||||
|
||||
resource "nubes_postgres_user" "test_extra_user1" {
|
||||
postgres_id = nubes_postgres.pg_test_instance.id
|
||||
username = "test_eu1"
|
||||
role = "ddl_user"
|
||||
adopt_existing_on_create = true
|
||||
|
||||
# Ждём pg_test_db — иначе параллельный старт с созданием БД ломает API.
|
||||
depends_on = [nubes_postgres_database.pg_test_db]
|
||||
}
|
||||
|
||||
// ── Пользователь 2 ───────────────────────────────────────────────────────────
|
||||
|
||||
resource "nubes_postgres_user" "test_extra_user2" {
|
||||
postgres_id = nubes_postgres.pg_test_instance.id
|
||||
username = "test_eu2"
|
||||
role = "ddl_user"
|
||||
adopt_existing_on_create = true
|
||||
|
||||
depends_on = [nubes_postgres_user.test_extra_user1]
|
||||
}
|
||||
|
||||
// ── База данных 1 (owner = test_eu1) ─────────────────────────────────────────
|
||||
|
||||
resource "nubes_postgres_database" "test_extra_db1" {
|
||||
postgres_id = nubes_postgres.pg_test_instance.id
|
||||
db_name = "test_edb1"
|
||||
db_owner = nubes_postgres_user.test_extra_user1.username
|
||||
adopt_existing_on_create = true
|
||||
|
||||
depends_on = [nubes_postgres_user.test_extra_user2]
|
||||
}
|
||||
|
||||
// ── База данных 2 (owner = test_eu2) ─────────────────────────────────────────
|
||||
|
||||
resource "nubes_postgres_database" "test_extra_db2" {
|
||||
postgres_id = nubes_postgres.pg_test_instance.id
|
||||
db_name = "test_edb2"
|
||||
db_owner = nubes_postgres_user.test_extra_user2.username
|
||||
adopt_existing_on_create = true
|
||||
|
||||
depends_on = [nubes_postgres_database.test_extra_db1]
|
||||
}
|
||||
Reference in New Issue
Block a user