Дело было пятничным вечером, делать было нечего.
Меня давно посещала идея написать свой софт для реализации terraform registry mirror, так как по мнению западных компаний мы живем в "неправильной" стране, поэтому доступ к ресурсам нам можно ограничить. Видимо, opensource это кого надо opensource.
На работе где-то год или полтора назад столкнулись с ситуацией, когда зеркало яндекса (https://terraform-mirror.yandexcloud.net/) было недоступно продолжительное время, а работу работать было нужно сейчас. Для решения данной проблемы был найдет проект https://github.com/straubt1/terraform-network-mirror, который позволил загрузить провайдеров и бинарники, и хостить их через ObjectStorage yandexcloud.
Но у такого способа есть несколько неприятных особенностей:
- Нет фильтра по версиям. По умолчанию update скрипт выгребает все доступные версии и, чтобы как-то ограничить количество загрузок, приходится вручную править список.
- Для загрузки приходится локально подключать vpn и выполнять все необходимые операции.
- Чтобы всё работало автоматически нужно ещё поверх этого набора скриптов накатать свою реализацию: связь с внешним vpn, фильтрацию версии и т.д.
Недавно вышла новость https://habr.com/ru/news/941500/ про добавление в редактор zed ИИ и тогда меня посетило вдохновение на создание pet-проекта.
Писать о том "как с помощью ИИ написать свой сайт без знания языка" я не буду, таких статей на Хабре уже пруд пруди. Поэтому расскажу о сложностях, с которым столкнулся, как не профессиональный разработчик, а как DevOps-инженер, реализовавший тулзу для решения конкретной задачи.
Какое-то время назад я уже баловался с нейросетками для генерации картинок, видео и помощи при дебаге. Поэтому для промта создал готовое тех задание: описание проекта, его архитектура, соответствие 12-factor, go best practice и тому подобное.
Первое, что меня удивило, это то, как ИИ в zed начал писать проект. Сначала он создал базовую структуру, написал базовый код, затем скрипты тестов, попытался собрать, получил кучу ошибок, исправил, повторно создал, запустил, прогнал тесты, снова исправил и в итоге добил до полностью рабочего состояния!
НО! Проект работал не совсем так, как я ожидал. В изначальном описании я указал написать реализацию зеркала, но не указал, что по логике работы это должно быть именно network mirror.
Дальше была череда редактирования, пересборок и длительного дебага. Из основных крупных проблем столкнулся с deadlock и неправильным вычисление хеша для провайдеров.
С решением deadlock помог delve и гугление. В процессе работы загрузчика провайдеров формировался список заданий в основном потоке. И получилась ситуация, когда в основном потоке список переполнялся, а дочерние потоки не могли отправить результаты успешной загрузки. ИИ, к сожалению, смог только предположить наличие этой проблемы, но самостоятельно решить её не смог.
Вторая проблема - вычисление h1 хеша. Предложенный вариант ИИ с вычислением SHA256 архива и энкодом в base64 был неверен. В гугле информации о том, как правильно terraform вычисляет хеш провайдера, тоже не нашлось. Были перепробованы различные варианты, но все они были неверны. Но, как говорится, утро вечера мудренее, на утро субботы меня посетила мысль покопаться в исходниках бинаря terraform. Каково же было мое удивление, когда я обнаружил, что вычисление хеша решается подключением внешнего модуля "golang.org/x/mod/sumdb/dirhash" и одной строчкой:
https://github.com/hashicorp/terraform/blob/main/internal/getproviders/hash.go#L296
s, err := dirhash.HashZip(archivePath, dirhash.Hash1)
В итоге после нескольких итераций лимит на бесплатные промты в zed был исчерпан, я подключил к работе copilot от github, добавил свои правки и проект был успешно выполнен.
Код проекта: https://github.com/kashtan404/tf-mirror
Основные возможности:
- Единый бинарник с выбором режима запуска: загрузка и http сервер
- Фильтрация по провайдерам, версиям, платформам и загрузка бинарей (packer, terraform и т.д.)
- Генерация и обновление метадаты со списком провайдеров, их версий и платформ и аналогично для бинарей
- Для http сервер поддержка TLS
- Для загрузчика поддержка загрузки через http/https/socks5 прокси, что может быть полезно при использовании в закрытых периметрах
- Возможность использования и установки как обычного бинарника, так и в формате докер образа, и установка в кубер через helm
Структура директорий загрузки:
/data/
├── registry.terraform.io
│ └── hashicorp/
│ ├── aws/
│ │ └── linux_amd64.zip
│ | └── index.json
│ | └── 5.0.0.json
│ └── helm/
│ └── ...
├── terraform/
│ └── terraform_1.6.0_linux_amd64.zip
└── .tf-mirror-metadata.json
Таким образом для .terraformrc будет следующий конфиг
provider_installation {
network_mirror {
url = "https://tf-mirror.example.com/"
include = ["registry.terraform.io/*/*"]
}
direct {
exclude = ["registry.terraform.io/*/*"]
}
}
Логика работы загрузчика:
Стартует и определяет конфигруацию
Читает метадату, чтобы понимать, что уже загружено
По заданному фильтру дёргает информацию о версиях, которые существуют в официальном registry, и выводит весь список версий в лог
Выполняет проверки: если версия есть в метадате, не ставит в очередь загрузки; если версия есть в метадате, но локально файла провайдера нет, ставит в очередь загрузки (для бинарников аналогично)
Начинает загрузку провайдеров. Если по результатам п.4 провайдер уже загружен, просто информирует в лог об этом
После завершения загрузки формирует index.json в директориях провайдеров и <version>.json
Выводит суммарную статистику: downloaded, failed, общее время загрузки и размер
Загружает бинарники, пропуская те, что уже загружены
По завершении загрузки уходит в ожидание на указанный период, после которого повторит п.2-п.9
Логика сервера:
Стартует с заданными параметрами
-
Имеет эндпоинты:
/health - для хелсчека
/version - вывод версии
/metrics - базовые метрики в формате prometheus
/ - отдает статику. В браузере можно погулять по директориям и скачивать вручную
Аргументы загрузчика:
--proxy - для задания загрузки через http/https/socks5 прокси
--check-period - период, через который загрузчик запустится повторно
-
--provider-filter - фильтр для провайдеров (comma separated string) имеет следующие варианты (полный формат "namespace/name>version"):
"hashicorp/aws" - загрузит все версии заданного провайдера
"hashicorp/aws>1.0.0" - загрузит >= указанной версии заданного провайдера
"hashicorp/aws,hashicorp/helm" - загрузит все версии заданных провайдеров
можно комбинировать: "hashicorp/aws>1.0.0,hashicorp/helm,hashicorp/null>2.1.0"
-
--platform-filter - фильтр платформ (comma separated string; применяется также и для бинарников):
"linux_amd64,darwin_arm64" - загрузит соответствующие платформы
--max-attempts - число попыток загрузки провайдера
--download-timeout - таймаут загрузки
--download-binaries - фильтр для загрузки банирников (опциональный, если его не задавать, то этот метод просто пропускается)
--download-path - директория загрузки
Чуть больше про фильтры:
Если не задать --provider-filter - загрузчик найдёт ВСЕ имеющиеся в официальном реджистри провайдеры и начнёт их загрузку. Это будет ОЧЕНЬ долго и ОЧЕНЬ много весить. Поэтому такой вариант крайне не рекомендую.
Если не задать --platform-filter - загрузка будет для всех имеющихся платформ
Если необходимо загружать только бинарники, можно использовать такой трюк:
В provider-filter указываем какую-нибудь белиберду. Загрузчик ругнётся в логе, что не нашёл такой провайдер, и загрузит только указанные бинарники.
--provider-filter "ibelieveicanfly/ibelieveicantouchthesky" --platform-filter "linux_amd64,darwin_arm64" --download-binaries "consul>1.21.3,nomad>1.6.0"

Аргументы сервера:
--listen-host
--listen-port
--hostname
--enable-tls - включает поддержку https. Для добавления этого аргумента нужно задать пути для сертификата и ключа в следующих двух параметрах. Параметр опционален, но terraform не будет скачивать по протоколу http, поэтому для полноценной работы указывать необходимо. В случае деплоя в кубер и использования ingress, аргумент можно не добавлять, так как tls будет терминироваться на ingress.
--tls-crt - путь до сертификата
--tls-key - путь до ключа
--data-path - какую директорию сервить. Тот же путь, что и download-path у загрузчика
Все аргументы можно задавать аналогично через переменные окружения. Также можно комбинировать - что-то задать через аргументы, что-то через переменные окружения.
Примеры запуска в docker:
# downloader mode
docker run --rm -v $(pwd)/data:/data docker.io/ademidovx/tf-mirror:latest \
--mode downloader --download-path /data --provider-filter=hashicorp/aws
# server mode without TLS (Only for preview. Terraform decline download providers by http)
docker run --rm -p 8080:8080 -v $(pwd)/data:/data docker.io/ademidovx/tf-mirror:latest \
--mode server --data-path /data --listen-port 8080
# server mode with TLS
docker run --rm -p 443:443 -v $(pwd)/tls:/tls -v $(pwd)/data:/data docker.io/ademidovx/tf-mirror:latest \
--mode server --data-path /data --listen-host 0.0.0.0 --listen-port 443 --enable-tls --tls-crt /tls/tls.crt --tls-key /tls/tls.key --hostname tf-mirror.local
Пример установки в k8s:
helm install tf-mirror oci://registry-1.docker.io/ademidovx/tf-mirror --version 0.1.0 --values values.yaml
Пример values.yaml
kind: Deployment
server:
port: 8080
env:
- name: TF_MIRROR_MODE
value: server
- name: DATA_PATH
value: /data
- name: LISTEN_HOST
value: "0.0.0.0"
- name: LISTEN_PORT
value: "8080"
# или с аргументами
# args:
# - --mode=server
# - --data-path=/data
# - --listen-host=0.0.0.0
# - --listen-port=8080
downloader:
env:
- name: TF_MIRROR_MODE
value: downloader
- name: PROXY
value: "http://proxy:8080"
- name: CHECK_PERIOD
value: "24"
- name: DOWNLOAD_PATH
value: /data
- name: PROVIDER_FILTER
value: "hashicorp/template>2.2.0,hashicorp/helm"
- name: PLATFORM_FILTER
value: "linux_amd64,darwin_amd64"
- name: MAX_ATTEMPTS
value: "5"
- name: DOWNLOAD_TIMEOUT
value: "180"
- name: DOWNLOAD_BINARIES
value: "consul>1.21.3,nomad>1.6.0"
# или с аргументами
# args:
# - --mode=downloader
# - --download-path=/data
# - --proxy=http://proxy:8080 # if needed
# - --check-period=24
# - --provider-filter=hashicorp/template>2.2.0,hashicorp/helm
# - --platform-filter=linux_amd64,darwin_amd64
# - --max-attempts=5
# - --download-timeout=180
# - --download-binaries=consul>1.21.3,nomad>1.6.0
ingress:
enabled: true
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/ssl-redirect: "true"
hosts:
- host: tf-mirror.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: tf-mirror-tls
# data presistence
data:
persistentvolume: true
accessMode: ReadWriteOnce
size: 10Gi
storageClassName: "exampleClassname"
Примеры логов:
--mode downloader
[INFO] 2025/09/06 18:48:10 Starting Terraform Registry Mirror
[INFO] 2025/09/06 18:48:10 Version: dev
[INFO] 2025/09/06 18:48:10 Mode: downloader
[INFO] 2025/09/06 18:48:10 Downloader Configuration:
[INFO] 2025/09/06 18:48:10 Download path: /data
[INFO] 2025/09/06 18:48:10 Check period: 24 hours
[INFO] 2025/09/06 18:48:10 Proxy: socks5://58.35.146.82:9999
[INFO] 2025/09/06 18:48:10 Provider filter: hashicorp/template>2.2.0
[INFO] 2025/09/06 18:48:10 Platform filter: linux_amd64,darwin_amd64
[INFO] 2025/09/06 18:48:10 Provider filter enabled: hashicorp/template (1 providers)
[INFO] 2025/09/06 18:48:10 Platform filter enabled: linux_amd64, darwin_amd64 (2 platforms)
[INFO] 2025/09/06 18:48:10 Starting Terraform provider downloader service
[INFO] 2025/09/06 18:48:10 Download path: /data
[INFO] 2025/09/06 18:48:10 Check period: 24h0m0s
[INFO] 2025/09/06 18:48:10 Using filtered provider search for specified providers
[INFO] 2025/09/06 18:48:10 Checking provider: hashicorp/template
[INFO] 2025/09/06 18:48:13 Provider filter applied: 1 providers found
[INFO] 2025/09/06 18:48:13 Platform filter applied: 2 platforms selected
[INFO] 2025/09/06 18:48:13 Processing provider: hashicorp/template
[INFO] 2025/09/06 18:48:13 Found 8 versions for hashicorp/template: [1.0.0 2.2.0 2.0.0 2.1.0 2.1.1 0.1.0 2.1.2 0.1.1]
[INFO] 2025/09/06 18:48:13 Provider already exists on disk: hashicorp/template 2.2.0 linux_amd64 (skipping)
[INFO] 2025/09/06 18:48:13 Provider already exists on disk: hashicorp/template 2.2.0 darwin_amd64 (skipping)
[INFO] 2025/09/06 18:48:13 Queued 0 download jobs, skipped 2 existing files
[INFO] 2025/09/06 18:48:13 All results received: resultsSent=0, totalJobs=0
[INFO] 2025/09/06 18:48:13 Download session completed: 0 downloaded, 0 skipped (already exist), 0 failed, 2 pre-filtered, total time: 0s, total size: 0.00 MB
[INFO] 2025/09/06 18:48:13 [worker-1] Jobs channel closed, worker exiting, resultsSentByWorker=0
[INFO] 2025/09/06 18:48:13 [worker-0] Jobs channel closed, worker exiting, resultsSentByWorker=0
[INFO] 2025/09/06 18:48:13 [worker-3] Jobs channel closed, worker exiting, resultsSentByWorker=0
[INFO] 2025/09/06 18:48:13 [worker-4] Jobs channel closed, worker exiting, resultsSentByWorker=0
[INFO] 2025/09/06 18:48:13 [worker-2] Jobs channel closed, worker exiting, resultsSentByWorker=0
[INFO] 2025/09/06 18:48:14 Generated index.json for hashicorp/template
[INFO] 2025/09/06 18:48:14 Starting download of HashiCorp binaries from releases.hashicorp.com
[INFO] 2025/09/06 18:48:14 Processing tool: packer (min version: 1.13.1)
[INFO] 2025/09/06 18:48:17 Downloading: https://releases.hashicorp.com/packer/1.14.1/packer_1.14.1_linux_amd64.zip
[INFO] 2025/09/06 18:48:21 Success: /data/packer/packer_1.14.1_linux_amd64.zip
[INFO] 2025/09/06 18:48:21 Downloading: https://releases.hashicorp.com/packer/1.14.1/packer_1.14.1_darwin_amd64.zip
[INFO] 2025/09/06 18:48:23 Success: /data/packer/packer_1.14.1_darwin_amd64.zip
[INFO] 2025/09/06 18:48:23 Downloading: https://releases.hashicorp.com/packer/1.14.0/packer_1.14.0_linux_amd64.zip
[INFO] 2025/09/06 18:48:25 Success: /data/packer/packer_1.14.0_linux_amd64.zip
[INFO] 2025/09/06 18:48:25 Downloading: https://releases.hashicorp.com/packer/1.14.0/packer_1.14.0_darwin_amd64.zip
[INFO] 2025/09/06 18:48:28 Success: /data/packer/packer_1.14.0_darwin_amd64.zip
[INFO] 2025/09/06 18:48:28 Downloading: https://releases.hashicorp.com/packer/1.13.1/packer_1.13.1_linux_amd64.zip
[INFO] 2025/09/06 18:48:31 Success: /data/packer/packer_1.13.1_linux_amd64.zip
[INFO] 2025/09/06 18:48:31 Downloading: https://releases.hashicorp.com/packer/1.13.1/packer_1.13.1_darwin_amd64.zip
[INFO] 2025/09/06 18:48:35 Success: /data/packer/packer_1.13.1_darwin_amd64.zip
[INFO] 2025/09/06 18:48:35 Processing tool: consul (min version: 1.21.3)
[INFO] 2025/09/06 18:48:35 Downloading: https://releases.hashicorp.com/consul/1.21.4/consul_1.21.4_linux_amd64.zip
[INFO] 2025/09/06 18:48:49 Success: /data/consul/consul_1.21.4_linux_amd64.zip
[INFO] 2025/09/06 18:48:49 Downloading: https://releases.hashicorp.com/consul/1.21.4/consul_1.21.4_darwin_amd64.zip
[INFO] 2025/09/06 18:49:19 Success: /data/consul/consul_1.21.4_darwin_amd64.zip
[INFO] 2025/09/06 18:49:19 Downloading: https://releases.hashicorp.com/consul/1.21.3/consul_1.21.3_linux_amd64.zip
[INFO] 2025/09/06 18:49:51 Success: /data/consul/consul_1.21.3_linux_amd64.zip
[INFO] 2025/09/06 18:49:51 Downloading: https://releases.hashicorp.com/consul/1.21.3/consul_1.21.3_darwin_amd64.zip
[INFO] 2025/09/06 18:50:11 Success: /data/consul/consul_1.21.3_darwin_amd64.zip
[INFO] 2025/09/06 18:50:11 HashiCorp binaries download completed
[INFO] 2025/09/06 18:50:11 downloadProviders: function exited
Фигурирующий в логе прокси взять из публичных списков в интернете. На момент загрузки он работать.
--mode server
[INFO] 2025/09/06 18:48:13 Starting Terraform Registry Mirror
[INFO] 2025/09/06 18:48:13 Version: dev
[INFO] 2025/09/06 18:48:13 Mode: server
[INFO] 2025/09/06 18:48:13 Server Configuration:
[INFO] 2025/09/06 18:48:13 Listen address: 0.0.0.0:8443
[INFO] 2025/09/06 18:48:13 Data path: /data
[INFO] 2025/09/06 18:48:13 Hostname: tf-mirror.local
[INFO] 2025/09/06 18:48:13 TLS enabled: yes
[INFO] 2025/09/06 18:48:13 Certificate: /tls/tls.crt
[INFO] 2025/09/06 18:48:13 Private key: /tls/tls.key
[INFO] 2025/09/06 18:48:13 Starting HTTPS server on 0.0.0.0:8443
[INFO] 2025/09/06 18:48:46 GET /health 200 167.3µs 10.244.0.1:41684
[INFO] 2025/09/06 18:48:51 GET /health 200 41.665µs 10.244.0.1:41686
[INFO] 2025/09/06 18:48:56 GET /health 200 34.164µs 10.244.0.1:59164
[INFO] 2025/09/06 18:49:01 GET /health 200 43.134µs 10.244.0.1:59170
[INFO] 2025/09/06 18:49:06 GET /health 200 34.194µs 10.244.0.1:37750
[INFO] 2025/09/06 18:49:11 GET /health 200 53.042µs 10.244.0.1:37752
Подводя итог могу сказать, что ИИ-ассистенты отлично подходят для быстрого решения подобных DevOps задач. Но, как мне кажется, им пока ещё далеко до того, чтобы писать хороший код для продакшн сервисов. А в качестве вспомогательного инструмента это бесспорно хорошая штука.
Использованные модели:
- Claude Sonnet 4
- GPT-4.1
Ссылки:
- проект на github: https://github.com/kashtan404/tf-mirror
- docker image и helm chart на докерхабе - https://hub.docker.com/r/ademidovx/tf-mirror