# archive, external, http: данные снаружи в HCL _Провайдеры · TerraformLab Knowledge Base_ **TL;DR:** Три провайдера для получения данных снаружи Terraform. `archive`, упаковка файлов в zip (lambda code, layers). `external`, вызов любого скрипта с JSON I/O. `http`. GET-запрос к URL, парс ответа. Все три, data sources, читают, не пишут. Полезны где declarative HCL не доходит. ## Зачем HCL, декларативный. Иногда нужно что-то **построить**: упаковать директорию в zip, прочитать JSON из CI-secret-store, спросить current IP у `ifconfig.io`. Три data-провайдера закрывают этот gap, оставаясь в парадигме «всё описано в HCL». ## archive_file Упаковка локальных файлов в zip. Главный кейс, `aws_lambda_function.filename`. ```hcl terraform { required_providers { archive = { source = "hashicorp/archive" version = "~> 2.6" } } } data "archive_file" "lambda" { type = "zip" source_file = "${path.module}/lambda/handler.py" output_path = "${path.module}/lambda.zip" } resource "aws_lambda_function" "demo" { function_name = "demo" filename = data.archive_file.lambda.output_path source_code_hash = data.archive_file.lambda.output_base64sha256 handler = "handler.main" runtime = "python3.12" role = aws_iam_role.lambda.arn } ``` Главное: - `output_base64sha256`, хэш zip-содержимого. Передаётся в Lambda как `source_code_hash`. Если код поменялся → хэш меняется → Lambda замечает → перевыкатывает. Без этого Terraform не увидит изменения в исходниках и не обновит лямбду. ### Целая директория ```hcl data "archive_file" "lambda" { type = "zip" source_dir = "${path.module}/lambda/" output_path = "${path.module}/lambda.zip" excludes = [ "__pycache__", "*.pyc", "tests/**", ] } ``` Удобно для лямбд с requirements/node_modules. ### Inline-источник ```hcl data "archive_file" "config" { type = "zip" output_path = "${path.module}/config.zip" source { content = jsonencode({ feature_flags = { ... } }) filename = "config.json" } } ``` Файл генерируется из HCL значения. Полезно для config-payload'ов которые считаются динамически. ## external Вызов произвольного скрипта. Скрипт принимает JSON в stdin, отдаёт JSON в stdout. ```hcl terraform { required_providers { external = { source = "hashicorp/external" version = "~> 2.3" } } } data "external" "git_info" { program = ["bash", "${path.module}/scripts/git-info.sh"] query = { repo = path.cwd } } output "git_commit" { value = data.external.git_info.result.commit } ``` `scripts/git-info.sh`: ```bash #!/usr/bin/env bash set -euo pipefail eval "$(jq -r '@sh "REPO=\(.repo)"')" COMMIT=$(git -C "$REPO" rev-parse HEAD) BRANCH=$(git -C "$REPO" rev-parse --abbrev-ref HEAD) jq -n --arg c "$COMMIT" --arg b "$BRANCH" '{commit:$c, branch:$b}' ``` Что важно: - Скрипт **должен** вернуть JSON-объект, где все значения, строки. Нельзя число, нельзя nested object. Это ограничение API. - Скрипт должен быть **idempotent**. Terraform может вызвать его на каждом plan. Если скрипт меняет состояние мира, плохо. - Скрипт работает с правами того, кто запустил `terraform`. Используется когда: - Нужно прочитать значение из vault/CI secret manager у которого нет Terraform-provider'а. - Нужно вычислить значение через сложную логику (curl + jq + sed). - Build-time integration: «вытащи docker tag из CI». Анти-кейс: не делай `external` для side-effects. Это data source, не ресурс. ## http GET-запрос к URL, ответ в data source. ```hcl terraform { required_providers { http = { source = "hashicorp/http" version = "~> 3.4" } } } data "http" "my_ip" { url = "https://ifconfig.io" } resource "aws_security_group_rule" "allow_my_ip" { type = "ingress" from_port = 22 to_port = 22 protocol = "tcp" cidr_blocks = ["${chomp(data.http.my_ip.response_body)}/32"] security_group_id = aws_security_group.bastion.id } ``` Аргументы: - `url`, endpoint. - `request_headers`, map, для auth/User-Agent. - `method`, по умолчанию GET, можно POST/PUT/DELETE. - `request_body`, body для POST. Атрибуты: - `response_body`, текст ответа. - `status_code`. HTTP-код. - `response_headers`, map. Валидация `status_code`: ```hcl data "http" "config" { url = "https://config.example.com/feature-flags" lifecycle { postcondition { condition = self.status_code == 200 error_message = "Config server returned ${self.status_code}, expected 200" } } } ``` Если упало, apply остановится с понятным сообщением. См. Урок про precondition/postcondition. ## Когда что использовать | Задача | Provider | |---|---| | Упаковать lambda-код в zip | `archive_file` | | Прочитать значение из произвольного источника (vault, CLI) | `external` | | Получить JSON от REST API | `http` (или `external` + curl) | | Прочитать что-то из git/AWS CLI | `external` | | Динамически узнать current public IP | `http` | | Сгенерить файл из переменной | `archive_file` с `source.content` | ## Подводные камни - **Все три, `data` источники.** Вычисляются на каждом plan. Если `external` зовёт долгий скрипт / `http` ходит на медленный endpoint, плата на каждом plan. Кэшируй на стороне самого скрипта/API. - **`external`-скрипт ловит всё что не int/string.** Возвращает `42` как число. Terraform упадёт. Превращай в строку: `{"count":"42"}`. Парсить число обратно, через `tonumber(data.external.x.result.count)`. - **`http` не следует за redirect-ами по умолчанию.** Включается `follow_redirects = true`. Чтобы поймать первый 301, оставь дефолт. - **`archive_file` использует mtime, не content-hash для idempotency.** Точнее, использует hash содержимого внутри. Это правильно, `terraform plan` стабилен. Но если в zip попадают файлы которые меняются на каждом билде (timestamps в byte-code): на каждом plan будет diff. Чисти такие файлы через `excludes`. - **`http` без auth, для public endpoints.** Если endpoint требует bearer-token, передавай через `request_headers = { Authorization = "Bearer ${var.token}" }`. Токен попадает в state, пометь как sensitive. - **`external`, security risk.** Скрипт исполняется с правами terraform-юзера. Если HCL приходит из недоверенного источника, это remote code execution. Не используй `external` в reusable модулях, которые качают чужие. - **`http.response_body` всегда строка.** JSON-ответ нужно парсить `jsondecode(data.http.x.response_body)`. Не пытайся писать `data.http.x.response_body.field`, это string indexing, не object access. - **LocalStack не нужен для этих провайдеров.** Все три не ходят в AWS. Тесты с ними, чистый HCL. ## Команды ```bash terraform plan ``` archive_file пересоберёт zip если файлы поменялись. external/http запустятся заново. Видишь чтения в начале plan. ```bash terraform refresh ``` Перечитать все data sources. Полезно когда внешний источник изменился, а apply ты делать не готов. ```bash terraform console ``` Можно вытащить data.archive_file.x.output_base64sha256 и сравнить с предыдущим. Дебаг 'почему лямбда пересоздаётся'. ## См. также - [data-блок: читаем то, что уже есть в облаке](/terraform/kb/tf-data-source.md) - [Утилитарные провайдеры: random, time, null, terraform_data](/terraform/kb/tf-utility-providers.md) - [cloudinit provider: user_data для EC2 и не только](/terraform/kb/tf-cloudinit-provider.md) - [Блок resource: главный кирпич Terraform](/terraform/kb/tf-resource-block.md)