Рефакторинг
Ресурсы модуля
В общих модулях и конфигурациях с длительным сроком эксплуатации со временем может оказаться, что первоначальная структура модулей и имена ресурсов вам больше не подходят. Например, вы можете решить, что ранее один дочерний модуль имеет смысл разделить на два отдельных модуля и переместить часть существующих ресурсов в новый модуль.
OpenTofu сравнивает предыдущее состояние с новой конфигурацией, устанавливая соответствие по уникальному адресу каждого модуля или ресурса. Поэтому по умолчанию OpenTofu воспринимает перемещение объекта или изменение его имени как намерение уничтожить объект по старому адресу и создать новый объект по новому адресу.
Если добавить блоки moved в конфигурацию, чтобы зафиксировать, где объект ранее перемещался или переименовывался, OpenTofu будет считать, что существующий объект по старому адресу теперь находится по новому адресу.
Синтаксис блока moved
Блок moved не принимает меток и содержит только аргументы from и to:
moved {
from = aws_instance.a
to = aws_instance.b
}В приведенном выше примере зафиксировано, что ресурс, известный сейчас как aws_instance.b, в предыдущей версии этого модуля был известен как aws_instance.a.
Перед созданием нового плана для aws_instance.b OpenTofu сначала проверяет, есть ли в состоянии существующий объект для aws_instance.a. Если такой объект есть, OpenTofu переименовывает его в aws_instance.b, а затем приступает к созданию плана. В результате план будет таким, как если бы объект изначально был создан по адресу aws_instance.b, поэтому при применении не потребуется его уничтожать.
Для адресов from и to используется специальный синтаксис, позволяющий выбирать модули, ресурсы и ресурсы внутри дочерних модулей. Ниже описаны несколько сценариев рефакторинга и подходящий для каждого из них синтаксис адресации.
- Переименование ресурса
- Изменение типа ресурса
- Включение
countилиfor_eachдля ресурса - Переименование вызова модуля
- Включение
countилиfor_eachдля вызова модуля - Разделение одного модуля на несколько
- Удаление блоков
moved
Блок moved нельзя использовать с блоками ephemeral, поскольку они не сохраняются в состоянии.
Переименование ресурса
Рассмотрим пример модуля с конфигурацией ресурса:
resource "aws_instance" "a" {
count = 2
# (resource-type-specific configuration)
}При первом применении этой конфигурации OpenTofu создаст aws_instance.a[0] и aws_instance.a[1].
Если позже вы решите изменить имя этого ресурса, можно изменить метку имени в блоке resource и указать старое имя в блоке moved:
resource "aws_instance" "b" {
count = 2
# (resource-type-specific configuration)
}
moved {
from = aws_instance.a
to = aws_instance.b
}При создании следующего плана для каждой конфигурации, использующей этот модуль, OpenTofu будет считать, что все существующие объекты, относящиеся к aws_instance.a, были созданы для aws_instance.b: aws_instance.a[0] будет считаться aws_instance.b[0], а aws_instance.a[1] — aws_instance.b[1].
Новые экземпляры модуля, в которых никогда не было aws_instance.a, проигнорируют блок moved и предложат создать aws_instance.b[0] и aws_instance.b[1] обычным образом.
В этом примере оба адреса указывают на ресурс целиком, поэтому OpenTofu распознает перемещение всех экземпляров ресурса. То есть оно охватывает и aws_instance.a[0], и aws_instance.a[1], и для этого не нужно указывать каждый из них отдельно.
Изменение типа ресурса
У каждого типа ресурса отдельная схема, поэтому объекты разных типов обычно несовместимы. Однако некоторые провайдеры позволяют изменить тип объекта с одного ресурса на другой. Также можно преобразовать ресурс одного провайдера в ресурс другого, если новый провайдер поддерживает миграцию со старого ресурса. Дополнительную информацию о совместимости ресурсов см. в документации провайдера.
С помощью moved можно изменить имя (а иногда и тип) ресурса, но нельзя использовать moved, чтобы преобразовать управляемый ресурс (блок resource) в ресурс данных (блок data).
Включение count или for_each для ресурса
Рассмотрим пример модуля, содержащего ресурс с единственным экземпляром:
resource "aws_instance" "a" {
# (resource-type-specific configuration)
}При применении этой конфигурации OpenTofu создаст объект, связанный с адресом aws_instance.a.
Позже вы используете for_each с этим ресурсом, чтобы систематически объявить несколько экземпляров. Чтобы сохранить объект, ранее связанный только с aws_instance.a, необходимо добавить блок moved и указать ключ экземпляра, который будет присвоен объекту в новой конфигурации:
locals {
instances = tomap({
big = {
instance_type = "m3.large"
}
small = {
instance_type = "t2.medium"
}
})
}
resource "aws_instance" "a" {
for_each = local.instances
instance_type = each.value.instance_type
# (other resource-type-specific configuration)
}
moved {
from = aws_instance.a
to = aws_instance.a["small"]
}Это не позволит OpenTofu запланировать уничтожение существующего объекта по адресу aws_instance.a. Вместо этого OpenTofu будет считать, что объект изначально был создан как aws_instance.a["small"].
Если хотя бы один из двух адресов содержит ключ экземпляра, как ["small"] в примере выше, OpenTofu понимает, что оба адреса относятся к конкретным экземплярам ресурса, а не к ресурсу в целом. Это означает, что с помощью moved можно переключаться между ключами, а также добавлять и удалять ключи при переключении между count, for_each или ни одним из них.
Ниже приведены другие примеры допустимых блоков moved, аналогичным образом фиксирующих изменения ключей экземпляров ресурса:
# Both old and new configuration used "for_each", but the
# "small" element was renamed to "tiny".
moved {
from = aws_instance.b["small"]
to = aws_instance.b["tiny"]
}
# The old configuration used "count" and the new configuration
# uses "for_each", with the following mappings from
# index to key:
moved {
from = aws_instance.c[0]
to = aws_instance.c["small"]
}
moved {
from = aws_instance.c[1]
to = aws_instance.c["tiny"]
}
# The old configuration used "count", and the new configuration
# uses neither "count" nor "for_each", and you want to keep
# only the object at index 2.
moved {
from = aws_instance.d[2]
to = aws_instance.d
}Если добавить count к существующему ресурсу, который его раньше не использовал, OpenTofu автоматически предложит переместить исходный объект в экземпляр с индексом 0, если только вы явно не укажете этот ресурс в блоке moved. Тем не менее мы рекомендуем явно записывать соответствующий блок moved, чтобы суть изменения была понятна будущим читателям модуля.
Переименование вызова модуля
Переименовать вызов модуля можно так же, как и ресурс. Рассмотрим исходную версию модуля:
module "a" {
source = "../modules/example"
# (module arguments)
}При применении этой конфигурации OpenTofu добавит к адресам всех ресурсов, объявленных в этом модуле, путь модуля module.a. Например, полный адрес ресурса aws_instance.example будет module.a.aws_instance.example.
Если позже вы решите выбрать для вызова этого модуля более подходящее имя, можно изменить метку имени в блоке module и указать старое имя в блоке moved:
module "b" {
source = "../modules/example"
# (module arguments)
}
moved {
from = module.a
to = module.b
}При создании следующего плана для каждой конфигурации, использующей этот модуль, OpenTofu будет считать, что существующие адреса объектов, начинающиеся с module.a, относятся к объектам, созданным в module.b. Адрес module.a.aws_instance.example будет считаться адресом module.b.aws_instance.example.
В этом примере оба адреса указывают на вызов модуля целиком, поэтому OpenTofu распознает перемещение всех экземпляров вызова. Если этот вызов модуля использует count или for_each, изменение затронет все экземпляры, и указывать каждый из них отдельно не потребуется.
Включение count или for_each для вызова модуля
Рассмотрим пример модуля с единственным экземпляром:
module "a" {
source = "../modules/example"
# (module arguments)
}При применении этой конфигурации OpenTofu создаст объекты, адреса которых начинаются с module.a.
В более поздних версиях модуля может потребоваться использовать count с этим ресурсом, чтобы систематически объявить несколько экземпляров. Чтобы сохранить объект, ранее связанный только с aws_instance.a, можно добавить блок moved и указать ключ экземпляра, который будет присвоен объекту в новой конфигурации:
module "a" {
source = "../modules/example"
count = 3
# (module arguments)
}
moved {
from = module.a
to = module.a[2]
}Приведенная выше конфигурация указывает OpenTofu считать все объекты в module.a изначально созданными в module.a[2]. В результате OpenTofu запланирует создание новых объектов только для module.a[0] и module.a[1].
Если хотя бы один из двух адресов содержит ключ экземпляра, как [2] в примере выше, OpenTofu будет считать, что оба адреса относятся к конкретным экземплярам вызова модуля, а не к вызову модуля в целом. Это означает, что с помощью moved можно переключаться между ключами, а также добавлять и удалять ключи при переключении между count, for_each или ни одним из них.
Другие примеры фиксации перемещений экземпляров см. в аналогичном разделе «Включение count и for_each для ресурса».
Разделение одного модуля на несколько
По мере того как модуль расширяется и начинает поддерживать новые требования, он может вырасти настолько, что его потребуется разделить на два отдельных модуля.
Рассмотрим пример модуля:
resource "aws_instance" "a" {
# (other resource-type-specific configuration)
}
resource "aws_instance" "b" {
# (other resource-type-specific configuration)
}
resource "aws_instance" "c" {
# (other resource-type-specific configuration)
}Разделить его на два модуля можно следующим образом:
-
Теперь
aws_instance.aотносится к модулю «x». -
aws_instance.bтакже относится к модулю «x». -
aws_instance.cотносится к модулю «y».
Чтобы выполнить этот рефакторинг, не заменяя существующие объекты, связанные со старыми адресами ресурсов, необходимо:
- Создать модуль «x», скопировав в него два ресурса, которые должны в нем находиться.
- Создать модуль «y», скопировав в него единственный ресурс, который должен в нем находиться.
- Изменить исходный модуль, удалив из него все эти ресурсы и оставив только конфигурацию-прослойку для миграции существующих пользователей.
Новые модули «x» и «y» должны содержать только блоки resource:
# module "x"
resource "aws_instance" "a" {
# (other resource-type-specific configuration)
}
resource "aws_instance" "b" {
# (other resource-type-specific configuration)
}# module "y"
resource "aws_instance" "c" {
# (other resource-type-specific configuration)
}Исходный модуль, теперь служащий только прослойкой для обратной совместимости, вызывает два новых модуля и указывает, что ресурсы переместились в них:
module "x" {
source = "../modules/x"
# ...
}
module "y" {
source = "../modules/y"
# ...
}
moved {
from = aws_instance.a
to = module.x.aws_instance.a
}
moved {
from = aws_instance.b
to = module.x.aws_instance.b
}
moved {
from = aws_instance.c
to = module.y.aws_instance.c
}Когда существующий пользователь исходного модуля обновится до новой версии «прослойки», OpenTofu обнаружит эти три блока moved и будет считать, что объекты, связанные с тремя старыми адресами ресурсов, изначально были созданы внутри двух новых модулей.
Новые пользователи этого семейства модулей могут использовать как объединенный модуль-прослойку, так и два новых модуля по отдельности. Можно сообщить существующим пользователям, что старый модуль объявлен устаревшим и для новых задач им следует использовать два отдельных модуля.
Сценарий рефакторинга с несколькими модулями необычен, поскольку нарушает обычное правило: родительский модуль рассматривает дочерний модуль как «закрытый блок» и не знает, какие именно ресурсы в нем объявлены. Это компромиссное решение предполагает, что все три модуля поддерживаются одной и той же командой и распространяются вместе в одном пакете модулей.
OpenTofu разрешает ссылки на модули в блоках moved относительно экземпляра модуля, в котором они определены. Например, если бы исходный модуль выше уже был дочерним модулем с именем module.original, ссылка на module.x.aws_instance.a разрешалась бы как module.original.module.x.aws_instance.a. Модуль может задавать операторы moved только для собственных объектов и объектов своих дочерних модулей.
Если необходимо сослаться на ресурсы внутри модуля, вызванного с метааргументами count или for_each, следует указать конкретный ключ экземпляра, чтобы сопоставить ресурс с новым местоположением его конфигурации:
moved {
from = aws_instance.example
to = module.new[2].aws_instance.example
}Удаление блоков moved
Со временем в долго существующем модуле может накопиться множество блоков moved.
Удаление блока moved обычно является несовместимым изменением, поскольку конфигурации, ссылающиеся на старый адрес, запланируют удаление существующего объекта вместо его перемещения. Настоятельно рекомендуем сохранять все исторические блоки moved из предыдущих версий модулей, чтобы пользователи любой версии могли обновиться.
Если вы все же решили удалить блоки moved, действуйте осторожно. Удаление блоков moved может быть безопасным, если вы поддерживаете закрытые модули внутри организации и уверены, что все пользователи успешно выполнили tofu apply с новой версией модуля.
Если один и тот же объект нужно переименовать или переместить дважды, рекомендуем задокументировать всю историю с помощью связанных цепочкой блоков moved, где новый блок ссылается на существующий:
moved {
from = aws_instance.a
to = aws_instance.b
}
moved {
from = aws_instance.b
to = aws_instance.c
}Фиксация последовательности перемещений таким образом позволяет успешно обновлять конфигурации как с объектами по адресу aws_instance.a, так и конфигурации с объектами по адресу aws_instance.b. В обоих случаях OpenTofu считает, что существующий объект изначально был создан как aws_instance.c.
Пометка переменных и выходных значений модуля как устаревших
Рефакторинг переменных и выходных значений модуля требует дополнительных изменений со стороны вызывающих модуль конфигураций. Чтобы корректно сообщить об устаревании переменных и выходных значений, авторы модулей могут настроить конфигурацию так, чтобы она предупреждала вызывающий код о предстоящем удалении.
OpenTofu поддерживает атрибут deprecated для блоков переменных и выходных значений. Автор модуля может предложить способ корректного перехода с устаревшего элемента. Если это затрагивает конфигурацию вызывающего модуля, пользователь получит предупреждение.
Из-за внутренней логики вычисления значений предупреждения об устаревании выходных значений модуля не выдаются для таких команд, как tofu validate. OpenTofu выдает все ожидаемые предупреждения об устаревании при выполнении команд tofu plan и tofu apply.
Дополнительную информацию см. в описании атрибута deprecated для переменных и выходных значений модуля.
Copyright (c) The OpenTofu Authors
Copyright (c) 2014 HashiCorp, Inc.
Mozilla Public License, version 2.0
https://opentofu.org/docs/v1.12/language/modules/develop/refactoring/