Как перебирать список чисел и использовать ключ как строку в terraform

Вопрос о синтаксисе Terraform. Я пытаюсь свести к минимуму логику создания групп безопасности, просто перебирая список локальных переменных, вот пример

locals {
  egress = {
    udp = [
      53,
      123
    ]
  }
  ingress = {
    tcp = [
      443,
      22,
    ]
  }
}

и ресурс:

resource "aws_security_group_rule" "in_tcp" {
  #for_each         = [ for k, v in local.ingress : local.ingress.k => v ]
  #for_each         = [ for port in local.ingress : port => port ]
  #for_each         = [ for proto in local.ingress : proto ]
  #for_each         = [ for k, v in local.ingress : k ]
  for_each          = local.ingress
  type              = "ingress"
  from_port         = each.value
  to_port           = each.value
  protocol          = tostring(each.key)
  self              = true
  security_group_id = aws_security_group.main.id
}

Я пробовал все, но ничего не работает. Наверное, я не понимаю значения [ for ... ] и { for ... }

На данный момент лучший результат, как указано выше, с использованием простого for_each = local.ingress, но я получаю сообщение об ошибке:

 Error: Incorrect attribute value type
│
│   on sg-rules/main.tf line 38, in resource "aws_security_group_rule" "in_tcp":
│   38:   from_port         = each.value
│     ├────────────────
│     │ each.value is tuple with 2 elements
│
│ Inappropriate value for attribute "from_port": number required.

когда я конвертирую каждое значение в число, используя from_port = tonumber(each.value), я получаю еще одну ошибку:

│ Error: Invalid function argument
│
│   on sg-rules/main.tf line 38, in resource "aws_security_group_rule" "in_tcp":
│   38:   from_port         = tonumber(each.value)
│     ├────────────────
│     │ while calling tonumber(v)
│     │ each.value is tuple with 2 elements
│
│ Invalid value for "v" parameter: cannot convert tuple to number.
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
0
136
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Как насчет того, чтобы изменить local, чтобы он был немного более дружелюбным к циклу...

Что-то вроде этого

locals {
  ingress = {
    443 : "tcp",
    22 : "tcp"
  }
}

resource "aws_security_group_rule" "in_tcp" {
  for_each          = local.ingress
  type              = "ingress"
  from_port         = each.key
  to_port           = each.key
  protocol          = each.value
  self              = true
  security_group_id = "sg-123456"
}

Вывод плана TF теперь выглядит так:

Terraform will perform the following actions:

  # aws_security_group_rule.in_tcp["22"] will be created
  + resource "aws_security_group_rule" "in_tcp" {
      + from_port                = 22
      + id                       = (known after apply)
      + protocol                 = "tcp"
      + security_group_id        = "sg-123456"
      + security_group_rule_id   = (known after apply)
      + self                     = true
      + source_security_group_id = (known after apply)
      + to_port                  = 22
      + type                     = "ingress"
    }

  # aws_security_group_rule.in_tcp["443"] will be created
  + resource "aws_security_group_rule" "in_tcp" {
      + from_port                = 443
      + id                       = (known after apply)
      + protocol                 = "tcp"
      + security_group_id        = "sg-123456"
      + security_group_rule_id   = (known after apply)
      + self                     = true
      + source_security_group_id = (known after apply)
      + to_port                  = 443
      + type                     = "ingress"
    }

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

Это хороший чистый ответ, я добавляю приведенное ниже как дополнение, если структура данных OP не может быть изменена. Ниже приведено выражение, которое может преобразовать существующую структуру данных в структуру, указанную в этом ответе. merge([for proto, ports in local.ingress: {for port in ports: port=> proto}]...)

Chris Doyle 21.11.2022 00:06

Спасибо, это фантастика. Я хотел, чтобы мой код был простым, но ваш все еще прост и требует только одного ресурса. Однако, поскольку terraform принимает только один уникальный ключ, это означает, что я могу назначить порт только одному протоколу. Я не могу создать порт 53 tcp и udp одновременно. Тем не менее порт 53 не является обычным случаем. Спасибо. С другой стороны, я все еще могу использовать вашу структуру, изменить протокол на блок CIDR и оставить его только для одного протокола. Просто мысль.

Clear Sky 21.11.2022 00:31

Первое: local.ingress — это объект с ключом = «tcp» и значением = [443, 22]. Второе: foreach принимает только карту или набор строк. Вам нужна карта, проиндексированная "<proto_port>" следующим образом:

{
  "tcp_22" = {
    "port" = 22
    "proto" = "tcp"
  }
  "tcp_443" = {
    "port" = 443
    "proto" = "tcp"
  }
}

Вот как вы это делаете:

resource "aws_security_group_rule" "in_tcp" {
  for_each          = {
    for p in flatten([
      for proto, ports in local.ingress: [
        for port in ports: { proto = proto, port = port }
      ]
    ]): "${p.proto}_${p.port}" => p
  }
  type              = "ingress"
  from_port         = tonumber(each.value.port)
  to_port           = tonumber(each.value.port)
  protocol          = tostring(each.value.proto)
  self              = true
  security_group_id = aws_security_group.main.id
}

Как насчет использования строк вместо чисел внутри ваших локальных переменных и использования функции «toset» в блоке for_each. Затем вы создаете aws_security_group_rule для каждого протокола соответственно:

locals {
  egress = {
    udp = [
      "53",
      "123"
    ]
  }
  ingress = {
    tcp = [
      "443",
      "22",
    ]
  }
}
resource "aws_security_group_rule" "in_tcp" {
  for_each          = toset(local.ingress.tcp)
  type              = "ingress"
  from_port         = tonumber(each.value)
  to_port           = tonumber(each.value)
  protocol          = "tcp"
  self              = true
  security_group_id = aws_security_group.main.id
}
resource "aws_security_group_rule" "out_udp" {
  for_each          = toset(local.egress.udp)
  type              = "egress"
  from_port         = tonumber(each.value)
  to_port           = tonumber(each.value)
  protocol          = "udp"
  self              = true
  security_group_id = aws_security_group.main.id
}

Другие вопросы по теме