Terraform: инфраструктура на уровне кода — страница 30 из 65

.key и .value для доступа к ключу и соответственно к значению текущего элемента COLLECTION. Стоит отметить, что, когда вы применяете for_each в сочетании со списком, key содержит индекс, value — элемент с этим индексом. В случае с ассоциативным массивом key и value представляют собой одну из его пар типа «ключ — значение».

Соберем все это вместе и динамически сгенерируем блоки tag в ресурсе aws_autoscaling_group с помощью for_each:

resource "aws_autoscaling_group" "example" {

  launch_configuration = aws_launch_configuration.example.name

  vpc_zone_identifier  = data.aws_subnet_ids.default.ids

  target_group_arns    = [aws_lb_target_group.asg.arn]

  health_check_type    = "ELB"

  min_size = var.min_size

  max_size = var.max_size

  tag {

    key                 = "Name"

    value               = var.cluster_name

    propagate_at_launch = true

  }

  dynamic "tag" {

    for_each = var.custom_tags

    content {

      key                 = tag.key

      value               = tag.value

      propagate_at_launch = true

    }

  }

}

Теперь, если выполнить terraformapply, план действий будет выглядеть примерно так:

$ terraform apply

Terraform will perform the following actions:

  # aws_autoscaling_group.example will be updated in-place

  ~ resource "aws_autoscaling_group" "example" {

        (...)

        tag {

            key                 = "Name"

            propagate_at_launch = true

            value               = "webservers-prod"

        }

      + tag {

          + key                 = "Owner"

          + propagate_at_launch = true

          + value               = "team-foo"

        }

      + tag {

          + key                 = "DeployedBy"

          + propagate_at_launch = true

          + value               = "terraform"

        }

    }

Plan: 0 to add, 1 to change, 0 to destroy.

Do you want to perform these actions?

  Terraform will perform the actions described above.

  Only 'yes' will be accepted to approve.

  Enter a value:

Введите yes, чтобы развернуть изменения. В веб-консоли EC2 должны появиться новые теги, как показано на рис. 5.1.

Рис. 5.1. Динамические теги группы автомасштабирования


Циклы на основе выражений for

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

variable "names" {

  description = "A list of names"

  type        = list(string)

  default     = ["neo", "trinity", "morpheus"]

}

Как бы вы перевели все эти имена в верхний регистр? В языке программирования общего назначения, таком как Python, можно было бы написать следующий цикл:

names = ["neo", "trinity", "morpheus"]

upper_case_names = []

for name in names:

    upper_case_names.append(name.upper())

print upper_case_names

# Выводит: ['NEO', 'TRINITY', 'MORPHEUS']

Python позволяет выразить точно такой же код одной строчкой, используя синта­ксис под названием «абстракция списков» (list comprehension):

names = ["neo", "trinity", "morpheus"]

upper_case_names = [name.upper() for name in names]

print upper_case_names

# Выводит: ['NEO', 'TRINITY', 'MORPHEUS']

Python также позволяет отфильтровать итоговый список по заданному выражению:

names = ["neo", "trinity", "morpheus"]

short_upper_case_names = [name.upper() for name in names if len(name) < 5]

print short_upper_case_names

# Выводит: ['NEO']

Terraform предлагает похожие возможности в виде выражения for (не путать с выражением for_each из предыдущего раздела). У него следующий базовый синтаксис:

[for in : ]

LIST — это список, который нужно перебрать, ITEM — имя локальной переменной, которое будет назначено каждому элементу списка, а OUTPUT — выражение, которое каким-то образом преобразует ITEM. Например, вот код Terraform для перевода списка имен в var.names в верхний регистр:

variable "names" {

  description = "A list of names"

  type        = list(string)

  default     = ["neo", "trinity", "morpheus"]

}

output "upper_names" {

  value = [for name in var.names : upper(name)]

}

Если выполнить для этого кода команду terraformapply, получится следующий вывод:

$ terraform apply

Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

Outputs:

upper_names = [

  "NEO",

  "TRINITY",

  "MORPHEUS",

]

Как и с абстракциями списков в Python, вы можете задать выражение для фильтрации полученного результата:

variable "names" {

  description = "A list of names"

  type        = list(string)

  default     = ["neo", "trinity", "morpheus"]

}

output "short_upper_names" {

  value = [for name in var.names : upper(name) if length(name) < 5]

}

Выполнив terraformapply для этого кода, вы получите следующее:

short_upper_names = [

  "NEO",

]

Выражение for в Terraform также поддерживает циклический перебор ассоциативных массивов с использованием такого синтаксиса:

[for , in : ]

MAP — это ассоциативный массив, который нужно перебрать, KEY и VALUE — имена локальных переменных, которые назначаются каждой паре «ключ — значение» в MAP, а OUTPUT — выражение, которое каким-то образом преобразует KEY и VALUE. Например:

variable "hero_thousand_faces" {

  description = "map"

  type        = map(string)

  default     = {

    neo      = "hero"

    trinity  = "love interest"

    morpheus = "mentor"

  }

}

output "bios" {

  value = [for name, role in var.hero_thousand_faces : "${name} is the ${role}"]

}

Если выполнить terraformapply для этого кода, получится следующее:

map_example = [

  "morpheus is the mentor",

  "neo is the hero",

  "trinity is the love interest",

]

Выражение for может вернуть ассоциативный массив вместо списка, используя следующий синтаксис:

# Циклический перебор списков

[for in : =>]

# Циклический перебор ассоциативных массивов

{for , in : =>}

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

variable "hero_thousand_faces" {

  description = "map"

  type        = map(string)

  default     = {

    neo      = "hero"

    trinity  = "love interest"

    morpheus = "mentor"

  }

}

output "upper_roles" {

  value = {for name, role in var.hero_thousand_faces : upper(name) => upper(role)}

}

При выполнении этот код возвращает такой вывод:

upper_roles = {

  "MORPHEUS" = "MENTOR"

  "NEO" = "HERO"

  "TRINITY" = "LOVE INTEREST"

}


Циклы с использованием строковой директивы for

Ранее в этой книге вы узнали о строковой интерполяции, которая позволяет ссылаться на код Terraform внутри строки:

"Hello, ${var.name}"

С помощью строковых директив подобный синтаксис можно использовать и для управляющих конструкций (вроде циклов for и выражения if), но вместо знака доллара (${…}) перед фигурными скобками указывается знак процента (%{…}).

Строковая директива for имеет следующий синтаксис:

%{ for in }%{ endfor }

COLLECTION — список или ассоциативный массив, который нужно перебрать, ITEM — имя локальной переменной, которое назначается каждому элементу COLLECTION, а BODY — это то, что выводится на каждой итерации (здесь можно ссылаться на ITEM). Например:

variable "names" {

  description = "Names to render"

  type        = list(string)

  default     = ["neo", "trinity", "morpheus"]

}

output "for_directive" {

  value = <

%{ for name in var.names }

  ${name}

%{ endfor }

EOF

}

Выполнив terraformapply, вы получите следующий вывод:

$ terraform apply

Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

Outputs:

for_directive =

  neo

  trinity

  morpheus

Обратите внимание на дополнительные символы перехода на новую строку. В строковой директиве можно указать маркер ~, чтобы удалить все пробельные символы (пробелы и перенос строки) перед ней (если маркер находится в начале директивы) или после нее (если маркер находится в конце директивы):

output "for_directive_strip_marker" {

  value = <

%{~ for name in var.names }

  ${name}

%{~ endfor }

EOF

}

Эта обновленная версия дает такой вывод:

for_directive_strip_marker =

  neo

  trinity

  morpheus


Условные выражения

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

Параметр count для условных ресурсов.

• Выраженияfor_eachиfor для условных ресурсов и их вложенных блоков.

• Строковая директива if для условных выражений внутри строк.

Рассмотрим их все по очереди.


Условные выражения с использованием параметра count