# Утилитарные провайдеры: random, time, null, terraform_data _Провайдеры · TerraformLab Knowledge Base_ **TL;DR:** Провайдеры, которые не управляют облаком, а помогают HCL. `random`, генерация ID/паролей. `time`, задержки и timestamp-метки. `null`, устаревший «нерессурс» для триггеров. `terraform_data`, современная замена `null_resource`, встроена в Terraform. Каждый снимает конкретное ограничение деклеаративного подхода. ## Что такое утилитарный провайдер Большинство провайдеров (`aws`, `kubernetes`, `github`) представляют **внешнюю систему**. API облака или сервиса. Утилитарные провайдеры, это не API, а **функциональность языка**: генератор случайных значений, таймер, триггер. Они дают то, чего нет в HCL встроено. Все они official HashiCorp, версии стабильные, breaking-changes редкие. ## random Генерация значений, детерминированно в пределах state'а, случайно при создании. ```hcl terraform { required_providers { random = { source = "hashicorp/random" version = "~> 3.6" } } } resource "random_id" "suffix" { byte_length = 4 } resource "aws_s3_bucket" "demo" { bucket = "linuxlab-${random_id.suffix.hex}" } ``` `random_id.suffix.hex`, 8-символьный hex (4 байта * 2). Уникален при создании, дальше зафиксирован в state. На каждый последующий plan, тот же. ### Подвиды | Resource | Что генерирует | Главное use-case | |---|---|---| | `random_id` | Bytes → hex/base64 | Уникальные суффиксы имён | | `random_string` | Строка из символов | Имена, пароли (не очень) | | `random_password` | Строка с минимумом классов | Реальные пароли (sensitive) | | `random_pet` | Пара слов «honest-zebra» | Человекочитаемые имена для лабы | | `random_uuid` | UUID v4 | Корреляция, request IDs | | `random_integer` | Число в диапазоне | Случайный порт, индекс | | `random_shuffle` | Перемешивает список | AZ-распределение | ### keepers, перегенерация при изменении ```hcl resource "random_id" "suffix" { byte_length = 4 keepers = { bucket_name = var.bucket_name } } ``` Если `var.bucket_name` поменялся, `random_id` пересоздаётся → новый hex → новый бакет. Связка «при смене N → новый случай». Без keepers значение **никогда** не меняется без явного `taint` / `-replace`. ## time Манипуляции с временем в HCL. ```hcl terraform { required_providers { time = { source = "hashicorp/time" version = "~> 0.12" } } } ``` ### time_static, momentum при создании ```hcl resource "time_static" "created_at" {} resource "aws_s3_bucket" "demo" { bucket = "linuxlab-${formatdate("YYYYMMDD", time_static.created_at.rfc3339)}" } ``` Фиксирует timestamp в state при первом apply. Дальше, не меняется. Полезно для иммутабельных меток (имя бакета содержит дату создания). ### time_sleep, задержка в apply ```hcl resource "aws_iam_role" "app" { name = "app" # ... } resource "time_sleep" "wait_for_iam" { depends_on = [aws_iam_role.app] create_duration = "30s" } resource "aws_eks_cluster" "demo" { depends_on = [time_sleep.wait_for_iam] # ... } ``` IAM-роль создана, но AWS eventually consistent, пока propagation, eks не видит роль. `time_sleep` ждёт 30 секунд между ними. Это **обходной путь**, не решение. Если можно увидеть готовность через data source, лучше так. Если нет, sleep. Но он есть. ### time_rotating ```hcl resource "time_rotating" "cert" { rotation_days = 30 } ``` Атрибут `id` меняется каждые 30 дней. В связке с `random_password` + `keepers` даёт «ротировать пароль раз в месяц». Редко полезно, обычно секрет-менеджеры (Vault) делают это лучше. ## null_resource (устаревший) Старый паттерн, «не-ресурс» из провайдера `null`, чтобы повесить triggers и provisioner: ```hcl resource "null_resource" "bootstrap" { triggers = { version = var.script_version } provisioner "local-exec" { command = "./bootstrap.sh" } } ``` Работает до сих пор, но **не используется в новом коде**. Заменён на `terraform_data`. ## terraform_data (с TF 1.4+) Встроен в Terraform, не нужен отдельный provider. ```hcl resource "terraform_data" "bootstrap" { input = var.script_version triggers_replace = [var.script_version] provisioner "local-exec" { command = "./bootstrap.sh" } } ``` Преимущества: - Не требует `required_providers.null`, встроен. - Может **хранить значение** (`input`): становится доступен через `terraform_data.bootstrap.output`. - `triggers_replace`, список, при изменении любого → ресурс пересоздаётся. ### terraform_data как «buffer» для значения ```hcl variable "image_tag" { type = string default = "latest" } resource "terraform_data" "image_version" { input = var.image_tag } resource "aws_ecs_task_definition" "app" { # ... container_definitions = jsonencode([{ name = "app" image = "myrepo/app:${terraform_data.image_version.output}" }]) lifecycle { replace_triggered_by = [terraform_data.image_version] } } ``` При смене tag, task definition пересоздаётся принудительно через `replace_triggered_by`. Это паттерн «канарейка»: связать lifecycle тяжёлого ресурса с лёгким индикатором. ## Когда какой провайдер | Нужно | Используй | |---|---| | Уникальный суффикс имени | `random_id` | | Сильный пароль | `random_password` | | Метка времени создания | `time_static` | | Подождать пока AWS «дойдёт» | `time_sleep` | | Триггер пересоздания при изменении X | `terraform_data` с `triggers_replace` | | Прогнать `local-exec` при изменении | `terraform_data` с `provisioner` | | Хранить вычисленное значение между resources | `terraform_data.input/output` | ## Подводные камни - **random в LocalStack, тот же что в реальном AWS.** Этот провайдер не зависит от облака. Тесты с LocalStack полноценны для random/time/terraform_data. - **`random_password` НЕ безопаснее `random_string`.** Оба используют crypto/rand. Но `random_password` маркирован sensitive, не печатается в логах. Для секретов **всегда** `random_password`, чтобы пароль не утёк через `terraform output` / plan-логи. - **Значение `random_*` ROTAТE'ится только через `-replace` или keepers.** Просто `apply` его не меняет. Это **фича**, не баг: иначе на каждом apply бакет назвался бы иначе. - **`time_sleep` тратит реальное время apply.** 30s на каждом apply, плохо для CI. Используй только когда альтернатива не работает. - **`null_resource` deprecated, не запрещён.** Старый код можно не мигрировать; новый, пиши на `terraform_data`. Между ними нет разницы в поведении, есть в эстетике и интегрированности. - **`provisioner` на `terraform_data`, last resort.** Idempotency не гарантирована, errors сложно обрабатывать, sandboxing нулевой. Замена, `local-exec` через `external` provider data source (см. [tf-archive-external-http](/terraform/kb/tf-archive-external-http.md)). - **`random` ресурс попадает в state с открытым значением.** Это очевидно для `random_id`, но `random_password` тоже **в state в открытом виде**, sensitive только в выводе. Защищай state. ## Команды ```bash terraform state show random_id.suffix ``` Что внутри: byte_length, hex, b64_url, b64_std, dec. Все формы одного значения. ```bash terraform apply -replace=random_id.suffix ``` Принудительно перегенерировать. Каскадно пересоздаёт всё что от него зависит. ```bash terraform plan | grep terraform_data ``` Видеть triggers_replace в работе: что заставит пересоздать. ## См. также - [Блок provider: кому Terraform будет звонить](/terraform/kb/tf-provider-block.md) - [lifecycle: управляем поведением resource](/terraform/kb/tf-resource-lifecycle.md) - [archive, external, http: данные снаружи в HCL](/terraform/kb/tf-archive-external-http.md)