# moved блок: переименование без destroy _Рефакторинг · TerraformLab Knowledge Base_ **TL;DR:** `moved { from = ..., to = ... }` в HCL декларативно говорит Terraform: «этот ресурс раньше был по одному адресу, теперь по другому, в облаке тот же». Plan покажет «move», не «destroy + create». Появился в TF 1.1. Замена ручному `terraform state mv`, оставляет след в git, повторяется у всех в команде, видно в diff. ## Зачем moved До TF 1.1 переименование ресурса = `terraform state mv` руками. Это работает, но: - Только один человек запустил, у остальных state'ы разъехались (если local backend) или они должны быстро `pull`. - В git нет следа: HCL поменялся, а **почему** не упал в destroy+create, знает только тот, кто crash'нул терминал командой. - В PR-ревью невозможно увидеть «это рефакторинг» vs «это новый ресурс». С TF 1.1, есть `moved` блок. Декларативный, в HCL, видно в diff, plan показывает как `move` явно. ## Минимальный пример: переименование Было: ```hcl resource "aws_s3_bucket" "logs" { bucket = "linuxlab-logs" } ``` Хочешь: ```hcl resource "aws_s3_bucket" "log_storage" { bucket = "linuxlab-logs" } moved { from = aws_s3_bucket.logs to = aws_s3_bucket.log_storage } ``` Запускаешь plan: ``` # aws_s3_bucket.logs has moved to aws_s3_bucket.log_storage bucket = "linuxlab-logs" ... Plan: 0 to add, 0 to change, 0 to destroy. ``` Apply, ничего в облаке не меняется. State обновлён. Без `moved` блока plan показал бы: ``` - aws_s3_bucket.logs will be destroyed + aws_s3_bucket.log_storage will be created ``` Это destroy+create, потеря данных. Moved предотвращает. ## Кейсы ### Переименование Самый частый. Описан выше. После апплая `moved` блок можно удалить (но многие команды оставляют в HCL как документ, «когда-то это переименовали»). ### Перенос в модуль Был ресурс в root, вынес в модуль: ```hcl # root module "buckets" { source = "./modules/buckets" } moved { from = aws_s3_bucket.logs to = module.buckets.aws_s3_bucket.logs } ``` Внутри модуля: ```hcl # modules/buckets/main.tf resource "aws_s3_bucket" "logs" { bucket = "linuxlab-logs" } ``` `terraform plan` → `0 to add, 0 to change, 0 to destroy`. Ресурс переместился в state из root-уровня в `module.buckets`. ### Из модуля наружу Обратное: разбираешь модуль, ресурс возвращается в root: ```hcl moved { from = module.buckets.aws_s3_bucket.logs to = aws_s3_bucket.logs } ``` ### count → for_each Один из самых полезных кейсов. Раньше: ```hcl resource "aws_s3_bucket" "logs" { count = 3 bucket = "linuxlab-logs-${count.index}" } ``` Адреса в state: `aws_s3_bucket.logs[0]`, `[1]`, `[2]`. Хочешь `for_each` для стабильности ключей: ```hcl variable "log_levels" { type = set(string) default = ["debug", "info", "error"] } resource "aws_s3_bucket" "logs" { for_each = var.log_levels bucket = "linuxlab-logs-${each.key}" } moved { from = aws_s3_bucket.logs[0] to = aws_s3_bucket.logs["debug"] } moved { from = aws_s3_bucket.logs[1] to = aws_s3_bucket.logs["info"] } moved { from = aws_s3_bucket.logs[2] to = aws_s3_bucket.logs["error"] } ``` Важно: имена бакетов меняются! `linuxlab-logs-0` → `linuxlab-logs-debug`. Если бакеты уже созданы с числовыми именами. Нужно либо оставить старые имена в HCL, либо принять `bucket = ` изменение (которое для S3 = destroy+create, потому что имя, immutable). Чаще `count → for_each` делают **до того** как ресурсы созданы. Если уже созданы, оставь имена в HCL такими как в облаке. ## Что moved умеет, что нет | Может | Не может | |---|---| | Переименовать ресурс в state | Изменить тип ресурса (`aws_s3_bucket` → `aws_s3_bucket_v2`) | | Переместить в модуль / из модуля | Сменить provider | | Переключить count ↔ for_each | Изменить provider configuration | | Перенести между разными root'ами | Объединить несколько ресурсов в один | Для смены типа, destroy+create или import. Для смены provider, обычно тоже destroy+create, потому что под капотом это другой API. ## С чем сочетается - **С `removed` блоком** ([tf-removed-block](/terraform/kb/tf-removed-block.md)). `moved` переносит, `removed` забирает из state. Часто рядом в одном PR. - **С `import` блоком** ([tf-state-import](/terraform/kb/tf-state-import.md)): иногда меняешь HCL и одновременно захватываешь существующий ресурс. - **С `lifecycle.ignore_changes`**, moved не отменяет ignore_changes, они продолжают применяться после переноса. ## Подводные камни - **moved не удаляется автоматически.** После apply блок остаётся в HCL. Можно удалить вручную; некоторые команды оставляют в архивных файлах (`refactoring.tf`) для аудита. Безопасно удалять через 1-2 спринта после miграции. - **moved не переносит данные.** Это **state-only** операция. Если у тебя `aws_s3_bucket.logs` со 100GB данных, и ты делаешь `moved`, данные в том же бакете. Меняется только адрес в state. - **Цепочки moved работают.** `A → B`, `B → C` в одном HCL. Terraform разрулит. Но это плохой стиль: при возможности, один прямой `A → C`. - **moved между провайдерами не работает.** `aws.us` и `aws.eu`, разные provider instances. Move между ними = destroy+create (бакет в одном регионе не «переезжает» в другой). - **moved не работает с разными типами.** `aws_s3_bucket.x → aws_s3_bucket_acl.x` , нет. Тип ресурса должен совпадать в from и to. - **moved блок проверяется при init.** Опечатка в имени ресурса, `terraform plan` упадёт ещё до plan'а. - **Идеален для PR-ревью.** Видно в diff: «вот ресурс был тут, теперь тут, это рефакторинг, не destroy». Объясняет интент. Поэтому предпочтительнее ручного `state mv`. ## Команды ```bash terraform plan ``` С moved блоком: покажет 'has moved' вместо destroy+create. ```bash terraform apply ``` Применяет move. В облаке ничего не меняется. State обновляется. ```bash terraform state list ``` Проверка после apply: адрес сменился. ```bash git log -p main.tf ``` След рефакторинга остаётся в git. Кто-нибудь через год спросит 'почему было logs стал log_storage': ответ в diff. ## См. также - [state mv, state rm, state pull/push: ручные операции](/terraform/kb/tf-state-manipulation.md) - [Паттерны рефакторинга: count→for_each, split files, extract module](/terraform/kb/tf-refactor-patterns.md) - [count и for_each: несколько ресурсов из одного блока](/terraform/kb/tf-count-for-each.md)