# Функции коллекций HCL: length, lookup, merge, concat, flatten _Основы Terraform · TerraformLab Knowledge Base_ **TL;DR:** Для list/set/map в HCL есть функции: length (размер), lookup (по ключу с default), merge (объединить map), concat (склеить list), flatten (раскрыть), keys/values. Базовый инструментарий трансформаций. ## `length`, размер коллекции Работает на list, set, map и string: ```hcl length(["a", "b", "c"]) # 3 length(toset(["a", "b", "b"])) # 2 (дубликат убирается) length({a = 1, b = 2}) # 2 length("hello") # 5 ``` Полезно для условий и валидации: ```hcl validation { condition = length(var.azs) >= 2 error_message = "Минимум 2 availability zone." } ``` ## `lookup`, значение по ключу с default ```hcl lookup({a = 1, b = 2}, "a") # 1 lookup({a = 1, b = 2}, "missing") # ошибка lookup({a = 1, b = 2}, "missing", 0) # 0 (третий арг = default) ``` Третий аргумент, fallback, если ключ не найден. Без него `lookup` упадёт. На практике почти всегда дают default. Современный шорткат, `map["key"]`: ```hcl var.tags["Environment"] # как lookup, но без default, упадёт если нет try(var.tags["Environment"], "") # с default через try ``` ## `merge`, объединение map Несколько map'ов в один, последующие перекрывают предыдущие: ```hcl merge( { Project = "demo", Owner = "team" }, { Owner = "platform", Env = "prod" } ) # { # Project = "demo" # Owner = "platform" # перебито # Env = "prod" # } ``` Главный кейс, common_tags + специфичные: ```hcl locals { common_tags = { Project = var.project, ManagedBy = "terraform" } } resource "aws_s3_bucket" "logs" { bucket = "logs-bucket" tags = merge(local.common_tags, { Purpose = "logs" }) } ``` ## `concat`, склейка list ```hcl concat(["a", "b"], ["c", "d"]) # ["a", "b", "c", "d"] concat(var.base_cidrs, var.extra_cidrs) # склейка двух списков CIDR ``` Порядок сохраняется. Дубликаты не убираются (если нужно, заверните в `distinct`). ## `flatten`, раскрыть вложенные list Часто результат `for`-expression или нескольких `concat`, массив массивов. `flatten` делает плоский список: ```hcl flatten([["a", "b"], ["c"], [], ["d", "e"]]) # ["a", "b", "c", "d", "e"] ``` Типичный кейс: собрать все IP всех инстансов всех групп: ```hcl locals { all_private_ips = flatten([ for asg in aws_autoscaling_group.tiers : asg.target_group_arns ]) } ``` Раскрывает только **один уровень**. Если внутри есть ещё вложения, `flatten(flatten(...))`. ## `keys`, `values`, части map ```hcl keys({a = 1, b = 2, c = 3}) # ["a", "b", "c"] (отсортированы по ключу) values({a = 1, b = 2, c = 3}) # [1, 2, 3] (в том же порядке) ``` `keys` всегда возвращает отсортированный список, это удобно для стабильных результатов в plan. ## `contains`, проверка наличия ```hcl contains(["dev", "staging", "prod"], var.env) # true / false ``` Самая частая роль, в validation: ```hcl validation { condition = contains(["dev", "staging", "prod"], var.env) error_message = "env должен быть dev/staging/prod." } ``` Для map нет `contains`. Используйте `contains(keys(map), "k")` или `try(map["k"], null) != null`. ## `distinct`, убрать дубликаты ```hcl distinct(["a", "b", "a", "c", "b"]) # ["a", "b", "c"] ``` Порядок сохраняется (первое вхождение). Альтернатива, `toset(...)`, но `toset` теряет порядок. ## `element` и `slice` ```hcl element(["a", "b", "c"], 1) # "b" element(["a", "b", "c"], 5) # "c", wraps around: 5 % 3 = 2 slice(["a", "b", "c", "d"], 1, 3) # ["b", "c"], [start, end) ``` `element` оборачивается по модулю, это специфика. Если нужна жёсткая ошибка при выходе за границы, используйте `list[index]`. ## `zipmap`, собрать map из двух list ```hcl zipmap(["a", "b", "c"], [1, 2, 3]) # {a = 1, b = 2, c = 3} ``` Длины должны совпадать, иначе ошибка. ## for-expression, мощнее всех функций ```hcl # list → list [for s in ["dev", "prod"] : upper(s)] # ["DEV", "PROD"] # list → map { for s in ["dev", "prod"] : s => "bucket-${s}" } # {dev = "bucket-dev", prod = "bucket-prod"} # с фильтром [for s in var.envs : s if s != "test"] ``` for-expression покрывает большинство трансформаций. Многие из встроенных функций (`keys`, `values`, иногда `merge`) могут быть переписаны через for. ## Подводные камни - **`merge` плоский.** Если значения сами map'ы, они перезаписываются, не сливаются. Для глубокого merge, несколько `merge` или вручную через for-expression. - **`concat` не дедуплицирует.** Если нужно объединить и убрать дубли, `distinct(concat(a, b))`. - **`lookup` без default, взрыв.** Всегда указывайте третий аргумент или используйте `try()`. - **`flatten` идёт на один уровень.** Двойная вложенность, `flatten(flatten(arr))`. - **`keys` сортирует, это хорошо для стабильности.** Если нужен оригинальный порядок ключей map, это in вообще проблема, потому что HCL-map неупорядочен по природе. Используйте список объектов. - **`element` оборачивается, а `list[index]`, нет.** `element(arr, 999)` даст результат по модулю. `arr[999]` упадёт. Для безопасной выборки, `try(arr[index], default)`. - **`contains` строго типизирован.** `contains([1, 2, 3], "1")` упадёт, `"1"` это string, `1` это number. Приведите типы явно. - **`for` с map требует уникальных ключей.** `{ for s in arr : key_func(s) => ... }`, если key_func даст одинаковый ключ для двух элементов, упадёт. Лечится `for ... if cond` или сменой ключа. ## Команды ```bash terraform console ``` Лучший способ изучить функции коллекций. Введи `merge({a=1},{b=2})` и сразу увидишь результат. ```bash echo 'flatten([[1,2],[3,4]])' | terraform console ``` Быстрая проверка в одну строку из shell. ```bash terraform validate ``` Проверка типов в выражениях: упадёт без обращения к облаку, если concat смешал несовместимые типы. ## См. также - [Строковые функции HCL: format, join, replace, lower и другие](/terraform/kb/tf-functions-string.md) - [${...}: подстановка значений в строки](/terraform/kb/tf-interpolation.md) - [locals: вычисляемые внутренние имена](/terraform/kb/tf-locals.md) - [Типы данных в HCL: string, number, list, map, object](/terraform/kb/hcl-types.md) - [count и for_each: несколько ресурсов из одного блока](/terraform/kb/tf-count-for-each.md)