Spec-Zone.ru › OpenTofu 1.9

Рефакторинг

В общих модулях и конфигурациях с длительным сроком существования со временем может потребоваться изменить первоначальную структуру модулей и имена ресурсов. Например, вы можете решить, что ранее единый дочерний модуль лучше разделить на два отдельных модуля и перенести часть существующих ресурсов в новый модуль.

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

Переименование ресурса​

Рассмотрим пример модуля с конфигурацией ресурса:

Блок кода
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, а вместо этого будет рассматривать его так, как будто он изначально был создан как 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 автоматически предлагает переместить исходный объект в экземпляр с индексом ноль, если только вы не укажете этот ресурс явно в блоке 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».

Чтобы выполнить этот рефакторинг, не заменяя существующие объекты, связанные со старыми адресами ресурсов, необходимо:

  1. Создать модуль «x» и скопировать в него два ресурса, которые он должен содержать.
  2. Создать модуль «y» и скопировать в него ресурс, который он должен содержать.
  3. Изменить исходный модуль, удалив из него все эти ресурсы и оставив только конфигурацию-переходник для переноса существующих пользователей.

Новые модули «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.

Copyright (c) The OpenTofu Authors
Copyright (c) 2014 HashiCorp, Inc.
Mozilla Public License, version 2.0
https://opentofu.org/docs/v1.9/language/modules/develop/refactoring/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API