From 24796f4b4fd4e06c2b0e945f8a2f07b63096f103 Mon Sep 17 00:00:00 2001 From: Repinoid Date: Thu, 6 Aug 2026 12:11:06 +0300 Subject: [PATCH 01/10] =?UTF-8?q?chore:=20=D1=83=D0=B4=D0=B0=D0=BB=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20=D0=B7=D0=B0=D0=BA=D0=BE=D0=BC=D0=BC=D0=B8=D1=87?= =?UTF-8?q?=D0=B5=D0=BD=D0=BD=D1=8B=D0=B9=20=5F=5Fpycache=5F=5F=20(=D0=BA?= =?UTF-8?q?=D1=8D=D1=88=20Python)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- site/__pycache__/app.cpython-312.pyc | Bin 17340 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 site/__pycache__/app.cpython-312.pyc diff --git a/site/__pycache__/app.cpython-312.pyc b/site/__pycache__/app.cpython-312.pyc deleted file mode 100644 index 1de3418a19abe2559f438c04d07832525141f347..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17340 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 From c235db00de12fbecac759b5db62535f3e1b82859 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CNaeel=E2=80=9D?= Date: Thu, 6 Aug 2026 21:49:45 +0400 Subject: [PATCH 02/10] =?UTF-8?q?doc:=20HISTORY/FULL-2026-08-06.md=20?= =?UTF-8?q?=E2=80=94=20=D0=B2=D1=81=D1=91=20=D1=87=D1=82=D0=BE=20=D0=B4?= =?UTF-8?q?=D0=B5=D0=BB=D0=B0=D0=BB=D0=B8,=20=D0=BE=D0=B1=D1=81=D1=83?= =?UTF-8?q?=D0=B6=D0=B4=D0=B0=D0=BB=D0=B8,=20=D0=BF=D0=BB=D0=B0=D0=BD=20?= =?UTF-8?q?=D0=BD=D0=B0=20=D0=B1=D1=83=D0=B4=D1=83=D1=89=D0=B5=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HISTORY/FULL-2026-08-06.md | 171 +++++++++++++++++++++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 HISTORY/FULL-2026-08-06.md diff --git a/HISTORY/FULL-2026-08-06.md b/HISTORY/FULL-2026-08-06.md new file mode 100644 index 0000000..e54fc8e --- /dev/null +++ b/HISTORY/FULL-2026-08-06.md @@ -0,0 +1,171 @@ +# tf_registry — ПОЛНАЯ история (2026-08-06) + +## Итог: что работает сейчас + +| Сервис | Тип | Домен | Статус | +|---|---|---|---| +| `registry` | pythonk8s | `registry.pythonk8s.dev.nubes.ru` | Flask (не обновлён, висит старая версия) | +| `go-registry` | containerk8s | `go-registry.containerk8s.dev.nubes.ru` | Go ✅ (полностью работает) | + +### Что проверено на Go: +- `GET /` — HTML с версией v1.0.0 ✅ +- `GET /.well-known/terraform.json` ✅ +- `GET /v1/providers/nubes-test/nubes/versions` — 17 версий ✅ +- `GET /v1/providers/.../download/linux/amd64` — shasum + 2 GPG ключа ✅ +- Платформы: darwin, linux, windows ✅ +- GPG: `3EC4673EB798238A` (основной) + `CB3A0DF161ECC416` (legacy) ✅ + +--- + +## Хронология + +### Этап 1: Flask (pythonk8s) +1. Создан `site/app.py` — 7 роутов, структура `site/` (требование Nubes) +2. `requirements.txt`: flask, boto3 +3. Первый деплой — ошибка `Init:Error` (gitea приватный) +4. Репо сделан публичным → деплой успешен +5. API не работал: `versions: []`, `shasum: ""` +6. **Причина:** S3-ключи под `registry.kube5s.ru`, а Flask ищет под `registry.pythonk8s.dev.nubes.ru` +7. Добавлен `S3_PREFIX` в код Flask +8. GPG — в исходниках 1 ключ, живой деплой отдаёт 2 → заменён на 2 ключа +9. Добавлен darwin в платформы + +### Этап 2: Go-контейнер (containerk8s) +1. Обнаружен тип сервиса `containerk8s` — generic HTTP контейнер +2. Создана ветка `go-container` +3. Правки Go: порт `:5000` (вместо `:8080`), `PORT` из ENV +4. GPG: 2 ключа в `gpg_key.go` (без импорта registrykeys) +5. Платформы: добавлен darwin +6. Dockerfile: multi-stage (golang:1.24-alpine → alpine:3.21) +7. Gitea Docker registry: `docker login gitea.services.ngcloud.ru -u Nail` ✅ +8. Образ: `gitea.services.ngcloud.ru/nail/tf_registry:latest` +9. S3_PREFIX: хардкод `registry.kube5s.ru` (с TODO-комментарием) +10. VERSION: `const VERSION = "1.0.0"`, на главной странице + +### Ошибки при деплое containerk8s +1. `docker build` — `repository name must be lowercase` → `nail/tf_registry` +2. `minio-go/v7` требует Go >= 1.25 → `GOTOOLCHAIN=auto` +3. modify не даёт менять `jsonEnv` → пришлось хардкодить `S3_PREFIX` + +--- + +## Проблема S3_PREFIX + +**Суть:** S3-объекты хранятся с префиксом `registry.kube5s.ru/...` (старый Go-реестр). +Новый реестр имеет домен `go-registry.containerk8s.dev.nubes.ru` — не совпадает. + +**Текущее решение (временное):** хардкод `s3Prefix = "registry.kube5s.ru"` в `main.go`. +```go +// ── TEMPORARY HARDCODE ────────────────────────────────────────────────── +// TODO: REMOVE after devops adds jsonEnv editing to containerk8s "modify". +// Then read from ENV: s3Prefix = os.Getenv("S3_PREFIX") +// And set in UI jsonEnv: "S3_PREFIX": "registry.kube5s.ru" +// ───────────────────────────────────────────────────────────────────────── +``` + +**Правильные решения (на будущее):** + +Путь 1 — скопировать S3-объекты под новый домен: +``` +mc cp s3/.../registry.kube5s.ru/ s3/.../go-registry.containerk8s.dev.nubes.ru/ --recursive +``` + +Путь 2 — убрать домен из S3-путей вообще: +``` +Вместо: {hostname}/{ns}/{name}/{ver}/... +Сделать: {ns}/{name}/{ver}/... +``` + +После любого из путей — `s3Prefix = hostname`, хардкод не нужен. + +--- + +## Структура репозитория + +### Ветка `master` +``` +tf_registry/ +├── site/ # Flask (НЕ ИСПОЛЬЗУЕТСЯ, мёртвый код) +│ └── app.py +├── requirements.txt # Для Flask (НЕ ИСПОЛЬЗУЕТСЯ) +├── server/ # Go-код (LEGACY, без правок) +├── operator/ # Go operator (LEGACY) +├── k8s/ # Манифесты (LEGACY) +├── charts/ # Helm (LEGACY) +├── registrykeys/ # Старый GPG (LEGACY) +├── secrets/ +│ ├── env.txt # S3-креды + jsonEnv (в .gitignore) +│ └── llm.key # Токены (Gitea, LLM) +├── HISTORY/ # Документация +│ ├── README-NEW-CHAT.md +│ ├── plan-sonnet-full.md +│ ├── 2026-08-06-plan-review.md +│ └── 2026-08-06-first-deploy.md +├── Dockerfile # Для Go-контейнера +└── .gitignore # secrets/, __pycache__/ +``` + +### Ветка `go-container` (АКТУАЛЬНАЯ) +``` +server/ +├── main.go # Порт 5000, S3_PREFIX hardcode, VERSION, darwin +├── gpg_key.go # 2 GPG ключа (primary + legacy) +├── docs.go # Без изменений +└── gpg/ # Без изменений +Dockerfile # golang:1.24-alpine → alpine:3.21 +``` + +--- + +## CI/CD + +### Сборка и пуш образа +```bash +docker build -t gitea.services.ngcloud.ru/nail/tf_registry:latest . +docker push gitea.services.ngcloud.ru/nail/tf_registry:latest +``` + +### Деплой +- Тип: `containerk8s` (Простой HTTP контейнер) +- `registryPath`: `gitea.services.ngcloud.ru/nail/tf_registry:latest` +- `resourceRealm`: `iot-naeel` +- `jsonEnv`: в UI, креды в `secrets/env.txt` + +### Что можно менять в modify +- ТОЛЬКО: CPU, Memory, количество узлов +- jsonEnv и registryPath — НЕЛЬЗЯ (надо просить devops добавить) + +--- + +## Что дальше + +1. **Devops** — добавить редактирование `jsonEnv` в modify для containerk8s +2. **S3** — решить что делать с префиксом (копировать или убрать домен) +3. **Убрать хардкод** — `s3Prefix` из ENV `S3_PREFIX` +4. **Домен** — пересоздать с именем `registry` (вместо `go-registry`) +5. **Удалить Flask** — `site/app.py`, `requirements.txt` из master (мёртвый код) +6. **Удалить старые Go-файлы** — `operator/`, `k8s/`, `charts/`, `registrykeys/` + +--- + +## Креды и доступы + +| Что | Где | +|---|---| +| S3 креды | `secrets/env.txt`, K8s Secret `s3-credentials` | +| Gitea токен | `secrets/llm.key` (строка `gitea`) | +| Docker login | `gitea.services.ngcloud.ru`, user `Nail`, токен `1999993c...` | +| ВМ | `ssh naeel@5.172.178.213` | +| Кубер | только через ВМ | +| Go-реестр (старый) | `https://registry.kube5s.ru` | +| Flask-реестр | `https://registry.pythonk8s.dev.nubes.ru` | +| Go-реестр (новый) | `https://go-registry.containerk8s.dev.nubes.ru` | +| Gitea репо | `https://gitea.services.ngcloud.ru/Nail/tf_registry.git` | + +--- + +## Правила +- После каждого коммита → сразу push +- Перед деплоем → `docker build && docker push` +- `containerk8s` берёт образ из `registryPath`, НЕ клонирует git +- Gitea Docker registry требует lowercase-имён: `nail/tf_registry`, не `Nail/...` From efa7c5e0857cf33a4a71f9a88129a59bf2ac8ce6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CNaeel=E2=80=9D?= Date: Thu, 6 Aug 2026 22:04:26 +0400 Subject: [PATCH 03/10] =?UTF-8?q?doc:=20HISTORY/ARCHITECTURE.md=20?= =?UTF-8?q?=E2=80=94=20=D0=B0=D1=80=D1=85=D0=B8=D1=82=D0=B5=D0=BA=D1=82?= =?UTF-8?q?=D1=83=D1=80=D0=B0,=20S3,=20GPG,=20=D1=81=D0=B1=D0=BE=D1=80?= =?UTF-8?q?=D0=BA=D0=B0=20=D0=BF=D1=80=D0=BE=D0=B2=D0=B0=D0=B9=D0=B4=D0=B5?= =?UTF-8?q?=D1=80=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HISTORY/ARCHITECTURE.md | 168 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 HISTORY/ARCHITECTURE.md diff --git a/HISTORY/ARCHITECTURE.md b/HISTORY/ARCHITECTURE.md new file mode 100644 index 0000000..eadb804 --- /dev/null +++ b/HISTORY/ARCHITECTURE.md @@ -0,0 +1,168 @@ +# tf_registry — Архитектура и рабочий процесс + +## Что такое этот реестр + +Собственный реестр Terraform-провайдеров по протоколу HashiCorp Provider Registry Protocol. +Позволяет публиковать и распространять кастомные Terraform-провайдеры без HashiCorp Registry. + +**Текущий URL:** `https://go-registry.containerk8s.dev.nubes.ru` + +--- + +## Как это работает + +### Загрузка провайдера (пользователь) + +```hcl +terraform { + required_providers { + nubes = { + source = "go-registry.containerk8s.dev.nubes.ru/nubes-test/nubes" + version = "5.1.16" + } + } +} +``` + +Terraform сам делает: +1. `GET /.well-known/terraform.json` → узнаёт API-путь +2. `GET /v1/providers/nubes-test/nubes/versions` → список версий и платформ +3. `GET /v1/providers/.../download/linux/amd64` → JSON с URL для скачивания +4. Скачивает zip, проверяет SHA256, проверяет GPG-подпись +5. Всё — провайдер установлен + +Никакой аутентификации не нужно. + +--- + +## S3-структура + +Все данные хранятся в S3 (`terraform-registry`). Никакой БД нет. + +``` +{префикс}/{namespace}/{name}/{version}/ +├── terraform-provider-{name}_{ver}_{os}_{arch}.zip — бинарник провайдера +├── terraform-provider-{name}_{ver}_SHA256SUMS — контрольные суммы +└── terraform-provider-{name}_{ver}_SHA256SUMS.sig — GPG-подпись сумм + +docs/{namespace}/{name}/{version}/ +├── index.html — документация провайдера +└── ... — любые статические файлы +``` + +**Важно:** префикс (первая часть пути в S3) сейчас — `registry.kube5s.ru` (временный хардкод). +Планируется либо сменить на свой домен, либо убрать префикс совсем. + +--- + +## GPG-подпись + +### Зачем +Terraform требует, чтобы SHA256SUMS были подписаны GPG. Это гарантирует, что провайдер не подменён. + +### Как работает +1. **Приватный ключ** — хранится у того, кто заливает провайдер (`secrets/private_key.asc`) +2. **Публичный ключ** — вшит в код реестра (`gpg_key.go` → `/v1/providers/.../download` отдаёт его) +3. Terraform при `init` сверяет подпись SHA256SUMS с публичным ключом + +### Текущие ключи +| Key ID | Роль | +|---|---| +| `3EC4673EB798238A` | Основной (текущий) | +| `CB3A0DF161ECC416` | Legacy (старый) | + +Оба отдаются в `signing_keys.gpg_public_keys` — Terraform пробует каждый. + +--- + +## Как залить новый провайдер + +### Скрипт `build-provider.sh` + +Находится в `server/build-provider.sh`. Делает: +1. Компилирует Go-провайдер под linux/windows/darwin × amd64 +2. Упаковывает в zip +3. Генерирует SHA256SUMS +4. Импортирует приватный GPG-ключ из `secrets/private_key.asc` +5. Подписывает SHA256SUMS +6. Заливает zip + SHA256SUMS + .sig в S3 + +### Требования +```bash +# Переменные окружения +S3_ENDPOINT=s3.msk-1.ngcloud.ru +S3_ACCESS_KEY=... +S3_SECRET_KEY=... +S3_BUCKET=terraform-registry + +# GPG +secrets/private_key.asc # приватный ключ для подписи + +# Исходники провайдера +universal_rebuild/ # директория с Go-кодом провайдера +``` + +### Запуск +```bash +VERSION=5.1.17 ./server/build-provider.sh +``` + +--- + +## Документация + +Эндпоинт `/docs/{ns}/{name}/{ver}/...` отдаёт статику из S3: +``` +docs/{ns}/{name}/{ver}/index.html +``` + +Работает как статический веб-сервер: HTML, CSS, JS, изображения — всё, что положишь в `docs/` в S3. +Логика поиска (1:1 с Go-кодом): +1. Точный путь (`docs/ns/name/ver/style.css`) +2. Если нет расширения — `{путь}/index.html` +3. Fallback — `docs/ns/name/ver/index.html` + +--- + +## Эндпоинты + +| Метод | URL | Ответ | +|---|---|---| +| GET | `/` | HTML с версией | +| GET | `/.well-known/terraform.json` | `{"providers.v1":"/v1/providers/"}` | +| GET | `/v1/providers/{ns}/{name}/versions` | JSON: id, versions[], warnings | +| GET | `/v1/providers/{ns}/{name}/{ver}/download/{os}/{arch}` | JSON: download_url, shasum, GPG ключи | +| GET | `/v1/proxy?bucket=...&key=...` | Стриминг файла из S3 | +| GET | `/docs/{ns}/{name}/{ver}/...` | Статика из S3 | +| GET | `/healthz`, `/readyz` | `"ok"` 200 | + +--- + +## CI/CD + +```bash +# Сборка образа +docker build -t gitea.services.ngcloud.ru/nail/tf_registry:latest . + +# Пуш в Gitea Docker Registry +docker push gitea.services.ngcloud.ru/nail/tf_registry:latest + +# Редеплой — через UI modify (меняет только CPU/memory/replicas, подхватывает :latest) +``` + +--- + +## Платформы + +Детектятся по имени zip-файла: +- `_darwin_amd64.zip` → macOS Intel +- `_linux_amd64.zip` → Linux +- `_windows_amd64.zip` → Windows + +--- + +## Текущие проблемы + +1. **S3_PREFIX** — хардкод `registry.kube5s.ru` (TODO в коде) +2. **modify** — нельзя менять `jsonEnv` в UI (надо просить devops) +3. **Домен** — `go-registry` вместо `registry` (можно пересоздать) From 01e0245139c14122fa38296bfbf6f26c609df562 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CNaeel=E2=80=9D?= Date: Fri, 7 Aug 2026 08:12:53 +0400 Subject: [PATCH 04/10] =?UTF-8?q?doc:=20HOWTO-PUBLISH.md=20=E2=80=94=20?= =?UTF-8?q?=D0=B8=D0=BD=D1=81=D1=82=D1=80=D1=83=D0=BA=D1=86=D0=B8=D1=8F=20?= =?UTF-8?q?=D0=BF=D0=BE=20=D0=BF=D1=83=D0=B1=D0=BB=D0=B8=D0=BA=D0=B0=D1=86?= =?UTF-8?q?=D0=B8=D0=B8=20=D0=BF=D1=80=D0=BE=D0=B2=D0=B0=D0=B9=D0=B4=D0=B5?= =?UTF-8?q?=D1=80=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HISTORY/HOWTO-PUBLISH.md | 104 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 HISTORY/HOWTO-PUBLISH.md diff --git a/HISTORY/HOWTO-PUBLISH.md b/HISTORY/HOWTO-PUBLISH.md new file mode 100644 index 0000000..6703cda --- /dev/null +++ b/HISTORY/HOWTO-PUBLISH.md @@ -0,0 +1,104 @@ +# Инструкция: как опубликовать провайдер в реестре + +Реестр: `https://go-registry.containerk8s.dev.nubes.ru` + +## 1. Структура S3 + +``` +{prefix}/{namespace}/{name}/{version}/ +├── terraform-provider-{name}_{version}_{os}_{arch}.zip ← бинарник +├── terraform-provider-{name}_{version}_SHA256SUMS ← контрольные суммы +└── terraform-provider-{name}_{version}_SHA256SUMS.sig ← GPG-подпись +``` + +Где: +- `{prefix}` — сейчас `registry.kube5s.ru` (будет сменён) +- `{namespace}` — `nubes-test`, `nubes-dev`, `nubes` (PROD) +- `{name}` — `nubes` +- `{os}_{arch}` — `linux_amd64`, `windows_amd64`, `darwin_amd64` + +Пример: +``` +registry.kube5s.ru/nubes-test/nubes/5.1.17/terraform-provider-nubes_5.1.17_linux_amd64.zip +``` + +## 2. Как загрузить в S3 + +### Нужны креды +``` +S3_ENDPOINT=s3.msk-1.ngcloud.ru +S3_ACCESS_KEY=... +S3_SECRET_KEY=... +S3_BUCKET=terraform-registry +``` + +### Утилита `mc` (MinIO Client) +```bash +mc alias set nubes-s3 https://s3.msk-1.ngcloud.ru $S3_ACCESS_KEY $S3_SECRET_KEY + +# Загрузка +mc cp ./terraform-provider-nubes_5.1.17_linux_amd64.zip \ + nubes-s3/terraform-registry/registry.kube5s.ru/nubes-test/nubes/5.1.17/ +mc cp ./SHA256SUMS nubes-s3/terraform-registry/registry.kube5s.ru/nubes-test/nubes/5.1.17/ +mc cp ./SHA256SUMS.sig nubes-s3/terraform-registry/registry.kube5s.ru/nubes-test/nubes/5.1.17/ +``` + +### Или `awscli` +```bash +aws s3 cp ... s3://terraform-registry/registry.kube5s.ru/nubes-test/nubes/5.1.17/ \ + --endpoint-url https://s3.msk-1.ngcloud.ru +``` + +## 3. GPG-подпись (обязательно) + +Terraform требует подпись SHA256SUMS. + +### Приватный ключ +Хранится в `secrets/private_key.asc`. Публичный ключ (KEY ID: `3EC4673EB798238A`) вшит в реестр. + +### Подписать +```bash +gpg --import secrets/private_key.asc +gpg --detach-sign --armor SHA256SUMS +# → SHA256SUMS.asc (переименовать в SHA256SUMS.sig) +``` + +## 4. Документация + +``` +docs/{namespace}/{name}/{version}/index.html +docs/{namespace}/{name}/{version}/...любые файлы... +``` + +Заливаешь в S3 — автоматом доступна по: +``` +https://go-registry.containerk8s.dev.nubes.ru/docs/{ns}/{name}/{ver}/ +``` + +## 5. Использование пользователем + +```hcl +terraform { + required_providers { + nubes = { + source = "go-registry.containerk8s.dev.nubes.ru/nubes-test/nubes" + version = "5.1.17" + } + } +} +``` + +Terraform сам найдёт реестр, скачает провайдер, проверит SHA256 и GPG-подпись. + +## 6. Скрипт автоматизации + +В репозитории `tf_registry` есть скрипт `server/build-provider.sh` — он делает всё сам: +- Компилирует провайдер под linux/windows/darwin × amd64 +- Упаковывает в zip +- Генерирует SHA256SUMS +- Подписывает GPG +- Заливает в S3 + +```bash +VERSION=5.1.17 NAMESPACE=nubes-test ./server/build-provider.sh +``` From d841ffcbacdcd3a222ac1f428b6a3de8b7203b03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CNaeel=E2=80=9D?= Date: Sat, 8 Aug 2026 22:25:48 +0400 Subject: [PATCH 05/10] =?UTF-8?q?tests:=20smoke.sh=20=E2=80=94=209=20?= =?UTF-8?q?=D1=82=D0=B5=D1=81=D1=82=D0=BE=D0=B2=20=D0=B4=D0=BB=D1=8F=20?= =?UTF-8?q?=D1=80=D0=B5=D0=B5=D1=81=D1=82=D1=80=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/smoke.sh | 87 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100755 tests/smoke.sh diff --git a/tests/smoke.sh b/tests/smoke.sh new file mode 100755 index 0000000..27b99d1 --- /dev/null +++ b/tests/smoke.sh @@ -0,0 +1,87 @@ +#!/usr/bin/env bash +# Smoke tests for Terraform Registry +# Usage: bash tests/smoke.sh [DOMAIN] +set -euo pipefail + +DOMAIN="${1:-https://go-registry.containerk8s.dev.nubes.ru}" +PASS=0 +FAIL=0 + +check() { + local name="$1" + if eval "$2" &>/dev/null; then + echo "✅ $name" + ((PASS++)) + else + echo "❌ $name" + ((FAIL++)) + fi +} + +echo "=== Registry Smoke Tests: $DOMAIN ===" +echo + +# 1. Root page +check "GET /" \ + "curl -s --max-time 5 '$DOMAIN/' | grep -q ONLINE" + +# 2. Healthz +check "GET /healthz → ok" \ + "test 'ok' = \"\$(curl -s --max-time 5 '$DOMAIN/healthz')\"" + +# 3. Readyz +check "GET /readyz → ok" \ + "test 'ok' = \"\$(curl -s --max-time 5 '$DOMAIN/readyz')\"" + +# 4. Discovery +check "GET /.well-known/terraform.json" \ + "curl -s --max-time 5 '$DOMAIN/.well-known/terraform.json' | grep -q '/v1/providers/'" + +# 5. Versions +check "GET /v1/providers/nubes-test/nubes/versions" \ + "curl -s --max-time 10 '$DOMAIN/v1/providers/nubes-test/nubes/versions' | \ + python3 -c \" +import sys,json +d=json.load(sys.stdin) +vs=[v['version'] for v in d['versions']] +assert d['id']=='nubes-test/nubes' +assert len(vs)>=1 +assert vs==sorted(vs), 'not sorted' +for v in d['versions']: + p=[x['os'] for x in v['platforms']] + assert 'linux' in p and 'darwin' in p and 'windows' in p +print('OK') +\"" + +# 6. Download metadata +check "GET /v1/providers/.../5.1.17/download/linux/amd64" \ + "curl -s --max-time 10 '$DOMAIN/v1/providers/nubes-test/nubes/5.1.17/download/linux/amd64' | \ + python3 -c \" +import sys,json +d=json.load(sys.stdin) +assert d['os']=='linux' and d['arch']=='amd64' +assert len(d['shasum'])==64 +assert 'download_url' in d +keys=d['signing_keys']['gpg_public_keys'] +assert len(keys)==2 +assert keys[0]['key_id']=='3EC4673EB798238A' +assert keys[1]['key_id']=='CB3A0DF161ECC416' +print('OK') +\"" + +# 7. Proxy download +check "GET /v1/proxy — download zip" \ + "url=\$(curl -s --max-time 5 '$DOMAIN/v1/providers/nubes-test/nubes/5.1.17/download/linux/amd64' | python3 -c \"import sys,json; print(json.load(sys.stdin)['download_url'])\") && \ + test '200' = \"\$(curl -s --max-time 15 -o /dev/null -w '%{http_code}' \"\$url\")\"" + +# 8. 404 for unknown provider +check "GET unknown → 404" \ + "test '404' = \"\$(curl -s --max-time 5 -o /dev/null -w '%{http_code}' '$DOMAIN/v1/providers/nosuch/noversion/versions')\"" + +# 9. Missing key param → 400 +check "GET /v1/proxy без key → 400" \ + "test '400' = \"\$(curl -s --max-time 5 -o /dev/null -w '%{http_code}' '$DOMAIN/v1/proxy')\"" + +echo +echo "=== $PASS PASS, $FAIL FAIL ===" +exit $FAIL From 2a9a7311eff7fc362336c6b40364900893f4ef20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CNaeel=E2=80=9D?= Date: Sat, 8 Aug 2026 22:37:45 +0400 Subject: [PATCH 06/10] =?UTF-8?q?doc:=20ceph-s3-setup.md=20=E2=80=94=20sub?= =?UTF-8?q?user=20reader,=20=D0=BA=D0=BE=D0=BD=D1=84=D0=B8=D0=B3=D1=83?= =?UTF-8?q?=D1=80=D0=B0=D1=86=D0=B8=D1=8F=20Ceph?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HISTORY/ceph-s3-setup.md | 59 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 HISTORY/ceph-s3-setup.md diff --git a/HISTORY/ceph-s3-setup.md b/HISTORY/ceph-s3-setup.md new file mode 100644 index 0000000..9bb32ea --- /dev/null +++ b/HISTORY/ceph-s3-setup.md @@ -0,0 +1,59 @@ +# Ceph S3 — конфигурация для реестра + +## Инстанс S3 + +| Поле | Значение | +|---|---| +| userName | `naeels3` | +| instanceUid | `332cdb0d-34bf-43bf-864d-4adcc3b556fb` | +| urlConnect | `http://ceph.tst.nubes.ru/` | +| resourceRealm | `ceph.tst.nubes.ru` | + +## Пользователи (users) + +| user_id | Назначение | +|---|---| +| `3709` | Основной владелец | +| `3709:super` | Subuser с повышенными правами | +| `3709:technicalS3Backup` | Subuser для бэкапов | +| `3709:reader` | **(планируется)** Subuser для реестра — только read+list | + +## Subuser `3709:reader` для реестра + +### Права +- `read` — чтение объектов +- `list` — список объектов (нужно для `listVersions`) +- **Без** `write` и `delete` — реестр не может менять данные + +### Креды (после создания) +```json +{ + "S3_ACCESS_KEY": "...", + "S3_SECRET_KEY": "..." +} +``` +Вставить в `jsonEnv` инстанса реестра. + +### Конфигурация реестра под Ceph + +Dockerfile ENV: +```dockerfile +ENV S3_ENDPOINT=ceph.tst.nubes.ru \ + S3_BUCKET=terraform-registry \ + S3_PREFIX=go-registry.containerk8s.dev.nubes.ru \ + REGISTRY_HOSTNAME=go-registry.containerk8s.dev.nubes.ru \ + PORT=5000 +``` + +### Различия с текущим S3 (MinIO) + +| | Старый (MinIO) | Новый (Ceph) | +|---|---|---| +| ENDPOINT | `s3.msk-1.ngcloud.ru` | `ceph.tst.nubes.ru` | +| Креды | Статические | Subuser `3709:reader` | +| Протокол | HTTPS | HTTP (судя по `urlConnect`) | +| Безопасность | Приватный MinIO | Ceph с разделением прав | + +### ⚠️ Если HTTP +В Go-коде сейчас `Secure: true` — для HTTP надо поменять на `Secure: false`. +Лучше через ENV: `S3_USE_SSL=true/false` (сейчас захардкожено). From 25b50ce59b18800b6a6d2a91419fcaeca65c9a7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CNaeel=E2=80=9D?= Date: Sat, 8 Aug 2026 23:17:17 +0400 Subject: [PATCH 07/10] =?UTF-8?q?clean:=20=D1=83=D0=B4=D0=B0=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D1=8B=20go-registry=20=D0=B8=20kube5s=20=D0=B8=D0=B7=20?= =?UTF-8?q?=D0=B2=D1=81=D0=B5=D0=B3=D0=BE=20=D0=BA=D0=BE=D0=B4=D0=B0=20(te?= =?UTF-8?q?sts,=20k8s)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- k8s/overlays/dev/ingress-patch.yaml | 6 +++--- site/app.py | 2 +- tests/smoke.sh | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/k8s/overlays/dev/ingress-patch.yaml b/k8s/overlays/dev/ingress-patch.yaml index 35227da..fffd645 100644 --- a/k8s/overlays/dev/ingress-patch.yaml +++ b/k8s/overlays/dev/ingress-patch.yaml @@ -8,10 +8,10 @@ metadata: spec: tls: - hosts: - - terr.kube5s.ru - secretName: terr-kube5s-ru-tls + - registry.containerk8s.dev.nubes.ru + secretName: registry-containerk8s-tls rules: - - host: terr.kube5s.ru + - host: registry.containerk8s.dev.nubes.ru http: paths: - path: / diff --git a/site/app.py b/site/app.py index 414d3e1..1faaa35 100644 --- a/site/app.py +++ b/site/app.py @@ -13,7 +13,7 @@ from botocore.config import Config # Config (from ENV, matching Go defaults) # --------------------------------------------------------------------------- HOSTNAME = os.getenv("REGISTRY_HOSTNAME", "localhost:8080") -S3_PREFIX = "registry.kube5s.ru" # S3 keys stored under old Go hostname +S3_PREFIX = os.getenv("S3_PREFIX", "registry.containerk8s.dev.nubes.ru") S3_ENDPOINT = os.getenv("S3_ENDPOINT", "") S3_ACCESS_KEY = os.getenv("S3_ACCESS_KEY", "") S3_SECRET_KEY = os.getenv("S3_SECRET_KEY", "") diff --git a/tests/smoke.sh b/tests/smoke.sh index 27b99d1..4e4e52b 100755 --- a/tests/smoke.sh +++ b/tests/smoke.sh @@ -3,7 +3,7 @@ # Usage: bash tests/smoke.sh [DOMAIN] set -euo pipefail -DOMAIN="${1:-https://go-registry.containerk8s.dev.nubes.ru}" +DOMAIN="${1:-https://registry.containerk8s.dev.nubes.ru}" PASS=0 FAIL=0 From 13e57aa0969584924ece99cfec378ddc68d52980 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CNaeel=E2=80=9D?= Date: Sat, 8 Aug 2026 23:22:59 +0400 Subject: [PATCH 08/10] =?UTF-8?q?doc:=20HOWTO-UPLOAD.md=20=E2=80=94=20?= =?UTF-8?q?=D0=BF=D0=BE=D0=BB=D0=BD=D0=B0=D1=8F=20=D0=B8=D0=BD=D1=81=D1=82?= =?UTF-8?q?=D1=80=D1=83=D0=BA=D1=86=D0=B8=D1=8F=20=D0=BF=D0=BE=20=D0=B7?= =?UTF-8?q?=D0=B0=D0=BB=D0=B8=D0=B2=D0=BA=D0=B5=20=D0=BF=D1=80=D0=BE=D0=B2?= =?UTF-8?q?=D0=B0=D0=B9=D0=B4=D0=B5=D1=80=D0=B0=20(=D1=81=20=D1=83=D1=87?= =?UTF-8?q?=D1=91=D1=82=D0=BE=D0=BC=20=D0=B4=D0=B8=D0=B0=D0=BB=D0=BE=D0=B3?= =?UTF-8?q?=D0=B0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HISTORY/HOWTO-UPLOAD.md | 119 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 HISTORY/HOWTO-UPLOAD.md diff --git a/HISTORY/HOWTO-UPLOAD.md b/HISTORY/HOWTO-UPLOAD.md new file mode 100644 index 0000000..0673773 --- /dev/null +++ b/HISTORY/HOWTO-UPLOAD.md @@ -0,0 +1,119 @@ +# Как залить провайдер в реестр + +## Конфигурация + +### Реестр +- URL: `https://registry.containerk8s.dev.nubes.ru` +- S3: Ceph `http://ceph.tst.nubes.ru` +- Бакет: `terraform-registry` +- S3-префикс: `registry.containerk8s.dev.nubes.ru` + +### Креды + +| Subuser | Назначение | Права | +|---|---|---| +| `3709:reader` | Реестр (только читает) | read + list | +| `3709:super` | Заливка провайдеров | write | + +``` +Reader: AAYTNYSZIM08DDWV3TK1 / xQVd9CeGXACPIXTBTH4KV6DIHEAyySOFSTrS17Lc +Super: 63A90SRCBP51XQ5TX8OB / euEJDZkueh0JHGP1B5jlAnAKriB8kYBR0t466GQ9 +``` + +## Структура S3 + +``` +terraform-registry/ +└── registry.containerk8s.dev.nubes.ru/ + └── {namespace}/ + └── {provider}/ + └── {version}/ + ├── terraform-provider-{name}_{ver}_linux_amd64.zip + ├── terraform-provider-{name}_{ver}_windows_amd64.zip + ├── terraform-provider-{name}_{ver}_darwin_amd64.zip + ├── terraform-provider-{name}_{ver}_SHA256SUMS + └── terraform-provider-{name}_{ver}_SHA256SUMS.sig +``` + +## Заливка (build-provider.sh) + +### ⚠️ Важно: S3_ENDPOINT с http:// +Ceph тестовый — HTTP, не HTTPS. Если задать `S3_ENDPOINT=ceph.tst.nubes.ru`, скрипт добавит `https://` → упадёт. +**Всегда:** `S3_ENDPOINT=http://ceph.tst.nubes.ru` (с префиксом `http://`). + +### Команды + +```bash +# 1. Настроить mc (один раз, креды SUPER) +mc alias set ceph http://ceph.tst.nubes.ru \ + 63A90SRCBP51XQ5TX8OB \ + euEJDZkueh0JHGP1B5jlAnAKriB8kYBR0t466GQ9 + +# 2. Создать бакет (если ещё нет) +mc mb ceph/terraform-registry + +# 3. Переменные для сборки +export ROOT_DIR=~/tf_provider +export PROVIDER_DIR=~/tf_provider/provider +export REGISTRY_HOSTNAME=registry.containerk8s.dev.nubes.ru +export NAMESPACE=nubes-test # nubes-test | nubes-dev | nubes +export PROVIDER_NAME=nubes +export S3_ENDPOINT=http://ceph.tst.nubes.ru # ← http:// обязательно! +export S3_ACCESS_KEY=63A90SRCBP51XQ5TX8OB # super +export S3_SECRET_KEY=euEJDZkueh0JHGP1B5jlAnAKriB8kYBR0t466GQ9 # super +export S3_BUCKET=terraform-registry + +# 4. Запуск +cd ~/tf_provider +bash ~/tf_registry/server/build-provider.sh 5.1.17 +``` + +### Что делает скрипт +1. Компилирует Go → linux/windows/darwin × amd64 +2. Упаковывает в zip +3. Генерирует SHA256SUMS +4. Подписывает GPG (ключ `secrets/private_key.asc`) +5. Заливает в S3 + +### Требования +- `~/tf_provider/secrets/private_key.asc` — приватный GPG-ключ +- `~/tf_provider/provider/` — исходники Go-провайдера +- `mc` (MinIO Client) установлен +- `go` установлен + +## Проверка после заливки + +```bash +# API +curl https://registry.containerk8s.dev.nubes.ru/v1/providers/nubes-test/nubes/versions + +# Полный тест +bash ~/tf_registry/tests/smoke.sh +``` + +## Namespace'ы + +| Namespace | Назначение | +|---|---| +| `nubes-test` | Тестовый стенд | +| `nubes-dev` | Dev-стенд | +| `nubes` | Прод | + +Terraform-конфиг пользователя: +```hcl +terraform { + required_providers { + nubes = { + source = "registry.containerk8s.dev.nubes.ru/nubes-test/nubes" + version = "5.1.17" + } + } +} +``` + +## Уроки из диалога с чатом сборки + +1. **S3_ENDPOINT** — всегда с протоколом (`http://` или `https://`). Без протокола скрипт форсит `https://`. +2. **Бакет** — может не существовать на новом Ceph. Создать через `mc mb`. +3. **Префикс** — должен совпадать с `REGISTRY_HOSTNAME`. Если домен поменялся — обновить и в скрипте, и в Dockerfile реестра. +4. **Креды** — для заливки нужны super (write), для реестра — reader (read+list). From f3df103bb3b49a7855898d00845f69a899471b87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CNaeel=E2=80=9D?= Date: Sun, 9 Aug 2026 07:21:26 +0400 Subject: [PATCH 09/10] =?UTF-8?q?clean:=20=D1=83=D0=B4=D0=B0=D0=BB=D1=91?= =?UTF-8?q?=D0=BD=20Flask,=20k8s,=20charts,=20operator,=20registrykeys,=20?= =?UTF-8?q?scripts=20(=D0=B0=D1=80=D1=85=D0=B8=D0=B2=20=D0=B2=20with-flask?= =?UTF-8?q?-legacy)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- charts/Chart.yaml | 13 - charts/templates/_helpers.tpl | 42 -- charts/templates/build-script-configmap.yaml | 61 --- charts/templates/crd.yaml | 79 ---- charts/templates/docs-server-deployment.yaml | 52 --- charts/templates/ingress.yaml | 37 -- charts/templates/namespace.yaml | 7 - charts/templates/networkpolicy.yaml | 31 -- charts/templates/operator-deployment.yaml | 33 -- charts/templates/pdb.yaml | 14 - charts/templates/rbac.yaml | 41 -- .../templates/registry-server-deployment.yaml | 64 --- charts/templates/registry-server-service.yaml | 14 - charts/values-dev.yaml | 6 - charts/values-prod.yaml | 14 - charts/values.yaml | 71 --- k8s/README.md | 30 -- k8s/base/deployment.yaml | 19 - k8s/base/ingress.yaml | 16 - k8s/base/kustomization.yaml | 6 - k8s/base/service.yaml | 11 - .../dev/clusterissuer-selfsigned-dev.yaml | 6 - .../dev/clusterissuer-staging-dev.yaml | 14 - k8s/overlays/dev/image-patch.yaml | 10 - k8s/overlays/dev/ingress-patch.yaml | 23 - k8s/overlays/dev/kustomization.yaml | 8 - k8s/registry-deployment-new.yaml | 42 -- k8s/registry-ingress-new.yaml | 26 -- operator/.dockerignore | 47 -- operator/Makefile | 23 - operator/README.md | 44 -- operator/build/Dockerfile.operator | 18 - operator/build/Dockerfile.registry | 20 - operator/cmd/registry/docs.go | 116 ----- operator/cmd/registry/gpg_key.go | 8 - operator/cmd/registry/main.go | 281 ------------ operator/example-release.yaml | 11 - operator/go.mod | 37 -- operator/go.sum | 58 --- operator/manifests/00-namespace.yaml | 6 - operator/manifests/00-rbac.yaml | 35 -- operator/manifests/02-crd.yaml | 77 ---- operator/manifests/03-build-script.yaml | 75 ---- .../manifests/04-operator-deployment.yaml | 27 -- operator/manifests/05-registry-server.yaml | 81 ---- .../manifests/06-registry-server-docs.yaml | 42 -- operator/registrykeys/go.mod | 5 - operator/registrykeys/public_key.go | 59 --- registrykeys/go.mod | 5 - registrykeys/public_key.go | 67 --- requirements.txt | 2 - scripts/build-and-push-registry-server.sh | 19 - site/app.py | 411 ------------------ 53 files changed, 2364 deletions(-) delete mode 100644 charts/Chart.yaml delete mode 100644 charts/templates/_helpers.tpl delete mode 100644 charts/templates/build-script-configmap.yaml delete mode 100644 charts/templates/crd.yaml delete mode 100644 charts/templates/docs-server-deployment.yaml delete mode 100644 charts/templates/ingress.yaml delete mode 100644 charts/templates/namespace.yaml delete mode 100644 charts/templates/networkpolicy.yaml delete mode 100644 charts/templates/operator-deployment.yaml delete mode 100644 charts/templates/pdb.yaml delete mode 100644 charts/templates/rbac.yaml delete mode 100644 charts/templates/registry-server-deployment.yaml delete mode 100644 charts/templates/registry-server-service.yaml delete mode 100644 charts/values-dev.yaml delete mode 100644 charts/values-prod.yaml delete mode 100644 charts/values.yaml delete mode 100644 k8s/README.md delete mode 100644 k8s/base/deployment.yaml delete mode 100644 k8s/base/ingress.yaml delete mode 100644 k8s/base/kustomization.yaml delete mode 100644 k8s/base/service.yaml delete mode 100644 k8s/overlays/dev/clusterissuer-selfsigned-dev.yaml delete mode 100644 k8s/overlays/dev/clusterissuer-staging-dev.yaml delete mode 100644 k8s/overlays/dev/image-patch.yaml delete mode 100644 k8s/overlays/dev/ingress-patch.yaml delete mode 100644 k8s/overlays/dev/kustomization.yaml delete mode 100644 k8s/registry-deployment-new.yaml delete mode 100644 k8s/registry-ingress-new.yaml delete mode 100644 operator/.dockerignore delete mode 100644 operator/Makefile delete mode 100644 operator/README.md delete mode 100644 operator/build/Dockerfile.operator delete mode 100644 operator/build/Dockerfile.registry delete mode 100644 operator/cmd/registry/docs.go delete mode 100644 operator/cmd/registry/gpg_key.go delete mode 100644 operator/cmd/registry/main.go delete mode 100644 operator/example-release.yaml delete mode 100644 operator/go.mod delete mode 100644 operator/go.sum delete mode 100644 operator/manifests/00-namespace.yaml delete mode 100644 operator/manifests/00-rbac.yaml delete mode 100644 operator/manifests/02-crd.yaml delete mode 100644 operator/manifests/03-build-script.yaml delete mode 100644 operator/manifests/04-operator-deployment.yaml delete mode 100644 operator/manifests/05-registry-server.yaml delete mode 100644 operator/manifests/06-registry-server-docs.yaml delete mode 100644 operator/registrykeys/go.mod delete mode 100644 operator/registrykeys/public_key.go delete mode 100644 registrykeys/go.mod delete mode 100644 registrykeys/public_key.go delete mode 100644 requirements.txt delete mode 100755 scripts/build-and-push-registry-server.sh delete mode 100644 site/app.py diff --git a/charts/Chart.yaml b/charts/Chart.yaml deleted file mode 100644 index c80a9e6..0000000 --- a/charts/Chart.yaml +++ /dev/null @@ -1,13 +0,0 @@ -apiVersion: v2 -name: terraform-registry -description: Helm chart for Terraform Provider Registry (registry-server + operator) -type: application -version: 0.1.0 -appVersion: "1.0.0" -keywords: - - terraform - - registry - - nubes -home: https://terra.k8c.ru -maintainers: - - name: Naeel diff --git a/charts/templates/_helpers.tpl b/charts/templates/_helpers.tpl deleted file mode 100644 index 28c25bb..0000000 --- a/charts/templates/_helpers.tpl +++ /dev/null @@ -1,42 +0,0 @@ -{{/* -Expand the name of the chart. -*/}} -{{- define "terraform-registry.name" -}} -{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} -{{- end }} - -{{/* -Create a default fully qualified app name. -*/}} -{{- define "terraform-registry.fullname" -}} -{{- if .Values.fullnameOverride }} -{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} -{{- else }} -{{- $name := default .Chart.Name .Values.nameOverride }} -{{- printf "%s" $name | trunc 63 | trimSuffix "-" }} -{{- end }} -{{- end }} - -{{/* -Create chart label. -*/}} -{{- define "terraform-registry.chart" -}} -{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} -{{- end }} - -{{/* -Common labels. -*/}} -{{- define "terraform-registry.labels" -}} -helm.sh/chart: {{ include "terraform-registry.chart" . }} -{{ include "terraform-registry.selectorLabels" . }} -app.kubernetes.io/managed-by: {{ .Release.Service }} -{{- end }} - -{{/* -Selector labels. -*/}} -{{- define "terraform-registry.selectorLabels" -}} -app.kubernetes.io/name: {{ include "terraform-registry.name" . }} -app.kubernetes.io/instance: {{ .Release.Name }} -{{- end }} diff --git a/charts/templates/build-script-configmap.yaml b/charts/templates/build-script-configmap.yaml deleted file mode 100644 index 2c2e982..0000000 --- a/charts/templates/build-script-configmap.yaml +++ /dev/null @@ -1,61 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: builder-script - namespace: {{ .Values.global.namespace }} - labels: - {{- include "terraform-registry.labels" . | nindent 4 }} -data: - build.sh: | - #!/bin/sh - set -e - - echo ">>> Starting Build for $PROVIDER_NAME $VERSION" - - # 1. Setup Environment - # Inputs: GIT_REPO, GIT_REF, PROVIDER_NAME, VERSION - WORK_DIR="/workspace" - ARTIFACTS_DIR="/artifacts" - mkdir -p $WORK_DIR $ARTIFACTS_DIR - - # 2. Clone Repository - echo ">>> Cloning $GIT_REPO ($GIT_REF)..." - git clone $GIT_REPO $WORK_DIR/src - cd $WORK_DIR/src - git checkout $GIT_REF - - # 3. Build for Platforms - echo ">>> Building binaries..." - - # Linux amd64 - echo "--> linux/amd64" - GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o terraform-provider-${PROVIDER_NAME}_v${VERSION} - zip ${ARTIFACTS_DIR}/terraform-provider-${PROVIDER_NAME}_${VERSION}_linux_amd64.zip terraform-provider-${PROVIDER_NAME}_v${VERSION} - rm terraform-provider-${PROVIDER_NAME}_v${VERSION} - - # Windows amd64 - echo "--> windows/amd64" - GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -o terraform-provider-${PROVIDER_NAME}_v${VERSION}.exe - zip ${ARTIFACTS_DIR}/terraform-provider-${PROVIDER_NAME}_${VERSION}_windows_amd64.zip terraform-provider-${PROVIDER_NAME}_v${VERSION}.exe - rm terraform-provider-${PROVIDER_NAME}_v${VERSION}.exe - - # 4. Checksums - cd $ARTIFACTS_DIR - sha256sum *.zip > terraform-provider-${PROVIDER_NAME}_${VERSION}_SHA256SUMS - - # 5. GPG Sign - touch terraform-provider-${PROVIDER_NAME}_${VERSION}_SHA256SUMS.sig - - # 6. Upload to S3 - if [ -z "$REGISTRY_HOSTNAME" ]; then - REGISTRY_HOSTNAME="{{ .Values.global.registryHostname }}" - fi - - TARGET_PATH="terraform-providers/$REGISTRY_HOSTNAME/${NAMESPACE}/${PROVIDER_NAME}/${VERSION}" - - echo ">>> Uploading to S3 path: $TARGET_PATH" - - mc alias set nubes_s3 https://$S3_ENDPOINT $S3_ACCESS_KEY $S3_SECRET_KEY - mc cp --recursive $ARTIFACTS_DIR/ nubes_s3/$TARGET_PATH/ - - echo ">>> Build Complete!" diff --git a/charts/templates/crd.yaml b/charts/templates/crd.yaml deleted file mode 100644 index e3f473a..0000000 --- a/charts/templates/crd.yaml +++ /dev/null @@ -1,79 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - name: providerreleases.terra.core.nubes.ru - annotations: - app.kubernetes.io/managed-by: Helm -spec: - group: terra.core.nubes.ru - versions: - - name: v1alpha1 - served: true - storage: true - subresources: - status: {} - schema: - openAPIV3Schema: - type: object - properties: - spec: - type: object - required: - - providerName - - version - - gitRepo - - gitRef - properties: - providerName: - type: string - description: "Name of the provider (e.g. 'mycloud')" - namespace: - type: string - default: "mycloud" - description: "Terraform registry namespace (e.g. 'nubes')" - version: - type: string - description: "Semantic version without v (e.g. '1.0.1')" - gitRepo: - type: string - description: "Git repository URL to clone" - gitRef: - type: string - description: "Git tag, commit SHA or branch" - platforms: - type: array - items: - type: string - default: ["linux_amd64", "windows_amd64"] - description: "Target platforms for build" - status: - type: object - properties: - phase: - type: string - description: "Current phase: Pending, Building, Publishing, Ready, Failed" - downloadUrl: - type: string - conditions: - type: array - items: - type: object - properties: - type: - type: string - status: - type: string - lastTransitionTime: - type: string - format: date-time - reason: - type: string - message: - type: string - scope: Namespaced - names: - plural: providerreleases - singular: providerrelease - kind: TerraformProviderRelease - shortNames: - - tpr diff --git a/charts/templates/docs-server-deployment.yaml b/charts/templates/docs-server-deployment.yaml deleted file mode 100644 index 68d3f67..0000000 --- a/charts/templates/docs-server-deployment.yaml +++ /dev/null @@ -1,52 +0,0 @@ -{{- if .Values.docs.enabled }} -apiVersion: apps/v1 -kind: Deployment -metadata: - name: registry-server-docs - namespace: {{ .Values.global.namespace }} - labels: - app: registry-server-docs - {{- include "terraform-registry.labels" . | nindent 4 }} -spec: - replicas: 1 - selector: - matchLabels: - app: registry-server-docs - template: - metadata: - labels: - app: registry-server-docs - spec: - containers: - - name: registry-server - image: "{{ .Values.docs.image.repository }}:{{ .Values.docs.image.tag }}" - imagePullPolicy: {{ .Values.docs.image.pullPolicy }} - env: - - name: REGISTRY_HOSTNAME - value: {{ .Values.global.registryHostname | quote }} - - name: S3_ENDPOINT - value: {{ .Values.s3.endpoint | quote }} - - name: S3_ACCESS_KEY - valueFrom: - secretKeyRef: - name: {{ .Values.s3.existingSecret }} - key: {{ .Values.s3.accessKeyField }} - - name: S3_SECRET_KEY - valueFrom: - secretKeyRef: - name: {{ .Values.s3.existingSecret }} - key: {{ .Values.s3.secretKeyField }} - - name: S3_BUCKET - value: {{ .Values.s3.bucket | quote }} - - name: S3_USE_SSL - value: {{ .Values.s3.useSSL | quote }} - ports: - - containerPort: 8080 - resources: - requests: - cpu: 50m - memory: 64Mi - limits: - cpu: 500m - memory: 128Mi -{{- end }} diff --git a/charts/templates/ingress.yaml b/charts/templates/ingress.yaml deleted file mode 100644 index e6b48b0..0000000 --- a/charts/templates/ingress.yaml +++ /dev/null @@ -1,37 +0,0 @@ -{{- if .Values.ingress.enabled }} -# WARNING: Do NOT apply to existing clusters without confirming TLS issuer. -# LetsEncrypt rate limits apply. For testing, use a self-signed issuer. -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: registry-ingress - namespace: {{ .Values.global.namespace }} - annotations: - {{- if .Values.ingress.tls.enabled }} - cert-manager.io/cluster-issuer: {{ .Values.ingress.tls.issuer | quote }} - {{- end }} - {{- range $key, $value := .Values.ingress.annotations }} - {{ $key }}: {{ $value | quote }} - {{- end }} - labels: - {{- include "terraform-registry.labels" . | nindent 4 }} -spec: - ingressClassName: {{ .Values.ingress.className | quote }} - {{- if .Values.ingress.tls.enabled }} - tls: - - hosts: - - {{ .Values.ingress.host | quote }} - secretName: {{ .Values.ingress.tls.secretName }} - {{- end }} - rules: - - host: {{ .Values.ingress.host | quote }} - http: - paths: - - path: / - pathType: Prefix - backend: - service: - name: registry-server - port: - number: {{ .Values.registry.service.port }} -{{- end }} diff --git a/charts/templates/namespace.yaml b/charts/templates/namespace.yaml deleted file mode 100644 index 3e84644..0000000 --- a/charts/templates/namespace.yaml +++ /dev/null @@ -1,7 +0,0 @@ -apiVersion: v1 -kind: Namespace -metadata: - name: {{ .Values.global.namespace }} - labels: - name: {{ .Values.global.namespace }} - {{- include "terraform-registry.labels" . | nindent 4 }} diff --git a/charts/templates/networkpolicy.yaml b/charts/templates/networkpolicy.yaml deleted file mode 100644 index 76d8967..0000000 --- a/charts/templates/networkpolicy.yaml +++ /dev/null @@ -1,31 +0,0 @@ -{{- if .Values.networkPolicy.enabled }} -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - name: {{ include "terraform-registry.fullname" . }}-netpol - namespace: {{ .Values.global.namespace }} - labels: - {{- include "terraform-registry.labels" . | nindent 4 }} -spec: - podSelector: {} - policyTypes: - - Ingress - - Egress - ingress: - - from: - - namespaceSelector: - matchLabels: - kubernetes.io/metadata.name: ingress-nginx - ports: - - port: {{ .Values.registry.service.targetPort }} - protocol: TCP - egress: - - to: [] - ports: - - port: 443 # S3, Vault - protocol: TCP - - port: 53 # DNS - protocol: UDP - - port: 53 - protocol: TCP -{{- end }} diff --git a/charts/templates/operator-deployment.yaml b/charts/templates/operator-deployment.yaml deleted file mode 100644 index 4f7cd21..0000000 --- a/charts/templates/operator-deployment.yaml +++ /dev/null @@ -1,33 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: terraform-operator - namespace: {{ .Values.global.namespace }} - labels: - app: terraform-operator - {{- include "terraform-registry.labels" . | nindent 4 }} -spec: - replicas: {{ .Values.operator.replicas }} - selector: - matchLabels: - app: terraform-operator - template: - metadata: - labels: - app: terraform-operator - spec: - serviceAccountName: terraform-operator - containers: - - name: operator - image: "{{ .Values.operator.image.repository }}:{{ .Values.operator.image.tag }}" - imagePullPolicy: IfNotPresent - env: - - name: REGISTRY_HOSTNAME - value: {{ .Values.global.registryHostname | quote }} - resources: - requests: - cpu: {{ .Values.operator.resources.requests.cpu }} - memory: {{ .Values.operator.resources.requests.memory }} - limits: - cpu: {{ .Values.operator.resources.limits.cpu }} - memory: {{ .Values.operator.resources.limits.memory }} diff --git a/charts/templates/pdb.yaml b/charts/templates/pdb.yaml deleted file mode 100644 index 93a19f8..0000000 --- a/charts/templates/pdb.yaml +++ /dev/null @@ -1,14 +0,0 @@ -{{- if .Values.pdb.enabled }} -apiVersion: policy/v1 -kind: PodDisruptionBudget -metadata: - name: {{ include "terraform-registry.fullname" . }}-registry - namespace: {{ .Values.global.namespace }} - labels: - {{- include "terraform-registry.labels" . | nindent 4 }} -spec: - minAvailable: {{ .Values.pdb.minAvailable }} - selector: - matchLabels: - app: registry-server -{{- end }} diff --git a/charts/templates/rbac.yaml b/charts/templates/rbac.yaml deleted file mode 100644 index 0c317b2..0000000 --- a/charts/templates/rbac.yaml +++ /dev/null @@ -1,41 +0,0 @@ -apiVersion: v1 -kind: ServiceAccount -metadata: - name: terraform-operator - namespace: {{ .Values.global.namespace }} - labels: - {{- include "terraform-registry.labels" . | nindent 4 }} ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - name: terraform-operator - namespace: {{ .Values.global.namespace }} - labels: - {{- include "terraform-registry.labels" . | nindent 4 }} -rules: -- apiGroups: ["terra.core.nubes.ru"] - resources: ["providerreleases", "providerreleases/status"] - verbs: ["get", "list", "watch", "update", "patch"] -- apiGroups: ["batch"] - resources: ["jobs"] - verbs: ["create", "get", "list", "watch", "delete"] -- apiGroups: [""] - resources: ["pods", "pods/log"] - verbs: ["get", "list", "watch"] ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: terraform-operator - namespace: {{ .Values.global.namespace }} - labels: - {{- include "terraform-registry.labels" . | nindent 4 }} -subjects: -- kind: ServiceAccount - name: terraform-operator - namespace: {{ .Values.global.namespace }} -roleRef: - kind: Role - name: terraform-operator - apiGroup: rbac.authorization.k8s.io diff --git a/charts/templates/registry-server-deployment.yaml b/charts/templates/registry-server-deployment.yaml deleted file mode 100644 index 8f5fcaf..0000000 --- a/charts/templates/registry-server-deployment.yaml +++ /dev/null @@ -1,64 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: registry-server - namespace: {{ .Values.global.namespace }} - labels: - app: registry-server - {{- include "terraform-registry.labels" . | nindent 4 }} -spec: - replicas: {{ .Values.registry.replicas }} - selector: - matchLabels: - app: registry-server - template: - metadata: - labels: - app: registry-server - spec: - containers: - - name: registry-server - image: "{{ .Values.registry.image.repository }}:{{ .Values.registry.image.tag }}" - imagePullPolicy: {{ .Values.registry.image.pullPolicy }} - ports: - - containerPort: {{ .Values.registry.service.targetPort }} - env: - - name: REGISTRY_HOSTNAME - value: {{ .Values.global.registryHostname | quote }} - - name: S3_ENDPOINT - value: {{ .Values.s3.endpoint | quote }} - - name: S3_ACCESS_KEY - valueFrom: - secretKeyRef: - name: {{ .Values.s3.existingSecret }} - key: {{ .Values.s3.accessKeyField }} - - name: S3_SECRET_KEY - valueFrom: - secretKeyRef: - name: {{ .Values.s3.existingSecret }} - key: {{ .Values.s3.secretKeyField }} - - name: S3_BUCKET - value: {{ .Values.s3.bucket | quote }} - - name: S3_USE_SSL - value: {{ .Values.s3.useSSL | quote }} - resources: - requests: - cpu: {{ .Values.registry.resources.requests.cpu }} - memory: {{ .Values.registry.resources.requests.memory }} - limits: - cpu: {{ .Values.registry.resources.limits.cpu }} - memory: {{ .Values.registry.resources.limits.memory }} - {{- if .Values.registry.healthcheck.enabled }} - livenessProbe: - httpGet: - path: {{ .Values.registry.healthcheck.path }} - port: {{ .Values.registry.healthcheck.port }} - initialDelaySeconds: 15 - periodSeconds: 20 - readinessProbe: - httpGet: - path: {{ .Values.registry.healthcheck.path }} - port: {{ .Values.registry.healthcheck.port }} - initialDelaySeconds: 5 - periodSeconds: 10 - {{- end }} diff --git a/charts/templates/registry-server-service.yaml b/charts/templates/registry-server-service.yaml deleted file mode 100644 index a8253a7..0000000 --- a/charts/templates/registry-server-service.yaml +++ /dev/null @@ -1,14 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: registry-server - namespace: {{ .Values.global.namespace }} - labels: - app: registry-server - {{- include "terraform-registry.labels" . | nindent 4 }} -spec: - selector: - app: registry-server - ports: - - port: {{ .Values.registry.service.port }} - targetPort: {{ .Values.registry.service.targetPort }} diff --git a/charts/values-dev.yaml b/charts/values-dev.yaml deleted file mode 100644 index 81ba511..0000000 --- a/charts/values-dev.yaml +++ /dev/null @@ -1,6 +0,0 @@ -global: - registryHostname: "terra.k8c.ru" -registry: - replicas: 1 -pdb: - enabled: false diff --git a/charts/values-prod.yaml b/charts/values-prod.yaml deleted file mode 100644 index c7e8062..0000000 --- a/charts/values-prod.yaml +++ /dev/null @@ -1,14 +0,0 @@ -global: - registryHostname: "registry.nubes.ru" # Целевой домен -registry: - image: - repository: "pearlharbor.registryk8s.services.ngcloud.ru/terraform/registry-server" - replicas: 2 -operator: - image: - repository: "pearlharbor.registryk8s.services.ngcloud.ru/terraform/registry-operator" -pdb: - enabled: true - minAvailable: 1 -networkPolicy: - enabled: true diff --git a/charts/values.yaml b/charts/values.yaml deleted file mode 100644 index 3892218..0000000 --- a/charts/values.yaml +++ /dev/null @@ -1,71 +0,0 @@ -global: - registryHostname: "terra.k8c.ru" # Переопределяется при миграции - namespace: "terraform-registry" - -registry: - image: - repository: "naeel/terraform-registry-server" # → Harbor при миграции - tag: "latest" - pullPolicy: IfNotPresent - replicas: 1 # → 2 в prod - resources: - requests: - cpu: 100m - memory: 128Mi - limits: - cpu: 500m - memory: 256Mi - service: - port: 80 - targetPort: 8080 - healthcheck: - enabled: true - path: /healthz - port: 8080 - -operator: - image: - repository: "naeel/terraform-registry-operator" - tag: "latest" - replicas: 1 - resources: - requests: - cpu: 50m - memory: 64Mi - limits: - cpu: 500m - memory: 128Mi - -docs: - enabled: false - image: - repository: "naeel/terraform-registry-server" - tag: "docs-dev-fixed" - pullPolicy: IfNotPresent - -ingress: - enabled: true - className: "nginx" - host: "terra.k8c.ru" # Переопределяется - tls: - enabled: true - issuer: "letsencrypt-prod" # НЕ ТРОГАТЬ LetsEncrypt issuer! - secretName: "registry-tls" - annotations: - nginx.ingress.kubernetes.io/proxy-body-size: "100m" - -s3: - endpoint: "s3.msk-1.ngcloud.ru" - bucket: "terraform-registry" - useSSL: true - # credentials через existingSecret - existingSecret: "s3-credentials" - accessKeyField: "access-key" - secretKeyField: "secret-key" - -pdb: - enabled: false # → true в prod - minAvailable: 1 - -networkPolicy: - enabled: false # → true при миграции diff --git a/k8s/README.md b/k8s/README.md deleted file mode 100644 index 3e63ea1..0000000 --- a/k8s/README.md +++ /dev/null @@ -1,30 +0,0 @@ -# k8s 🔧 - -Кратко: Kubernetes‑манифесты для namespace `terra` — развёртывание `registry-server`, `terraform-operator`, Ingress, TLS и связанные ресурсы (S3, CRD, builder ConfigMap). - -## Важные файлы -- `00-namespace.yaml` — namespace `terra` -- `00-rbac.yaml` — RBAC для оператора -- `02-crd.yaml` — CRD для операторов (ProviderRelease) -- `03-build-script.yaml` — ConfigMap для сборщика Job'ов -- `04-operator-deployment.yaml` — Deployment для оператора -- `05-registry-server.yaml`, `06-registry-server-docs.yaml` — Deployment, Service и Ingress для `registry-server` - -## Как перенести на новый домен (коротко) ✅ -1. **Ingress**: в манифесте (обычно `05-*` или `registry-ingress`) заменить `host: terra.k8c.ru` на новый домен и убедиться в корректной аннотации `cert-manager.io/cluster-issuer` (или задать другой `secretName`). -2. **Certificate**: cert-manager автоматически выпустит/обновит TLS; проверьте `kubectl -n terra get certificate registry-tls -o yaml` и дождитесь `status.conditions[*].type == Ready`. -3. **Deployment env**: заменить `REGISTRY_HOSTNAME` в `04-operator-deployment.yaml` и в `registry-server` на новый домен. -4. **DNS**: добавить A/CNAME запись для нового домена, указывающую на IP балансировщика (`kubectl -n terra get ingress registry-ingress -o jsonpath='{.status.loadBalancer.ingress[0].ip}'`). -5. **Secrets & storage**: используйте `s3-credentials` (S3_ENDPOINT/S3_ACCESS_KEY/S3_SECRET_KEY) и убедитесь в доступности S3. -6. **Apply & Verify**: - - `kubectl apply -f manifests/` - - `kubectl -n terra describe ingress registry-ingress` - - `kubectl -n terra get certificate registry-tls` - - `curl -vk https:///docs////` - -## Проверки (read-only) -- `kubectl -n terra get ingress,svc,deploy,po,secret` -- `kubectl -n terra get certificate registry-tls -o yaml` -- `kubectl -n terra logs ` - -> Примечание: это инструкция для операции вручную/CI; я ничего не меняю в кластере автоматически. \ No newline at end of file diff --git a/k8s/base/deployment.yaml b/k8s/base/deployment.yaml deleted file mode 100644 index a51fc96..0000000 --- a/k8s/base/deployment.yaml +++ /dev/null @@ -1,19 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: my-deployment -spec: - replicas: 1 - selector: - matchLabels: - app: my-app - template: - metadata: - labels: - app: my-app - spec: - containers: - - name: my-container - image: my-image:latest - ports: - - containerPort: 80 diff --git a/k8s/base/ingress.yaml b/k8s/base/ingress.yaml deleted file mode 100644 index a11eb92..0000000 --- a/k8s/base/ingress.yaml +++ /dev/null @@ -1,16 +0,0 @@ -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: my-ingress -spec: - rules: - - host: example.local - http: - paths: - - path: / - pathType: Prefix - backend: - service: - name: my-service - port: - number: 80 diff --git a/k8s/base/kustomization.yaml b/k8s/base/kustomization.yaml deleted file mode 100644 index 94bdff3..0000000 --- a/k8s/base/kustomization.yaml +++ /dev/null @@ -1,6 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: - - deployment.yaml - - service.yaml - - ingress.yaml diff --git a/k8s/base/service.yaml b/k8s/base/service.yaml deleted file mode 100644 index 979f93e..0000000 --- a/k8s/base/service.yaml +++ /dev/null @@ -1,11 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: my-service -spec: - selector: - app: my-app - ports: - - protocol: TCP - port: 80 - targetPort: 80 diff --git a/k8s/overlays/dev/clusterissuer-selfsigned-dev.yaml b/k8s/overlays/dev/clusterissuer-selfsigned-dev.yaml deleted file mode 100644 index ed747d2..0000000 --- a/k8s/overlays/dev/clusterissuer-selfsigned-dev.yaml +++ /dev/null @@ -1,6 +0,0 @@ -apiVersion: cert-manager.io/v1 -kind: ClusterIssuer -metadata: - name: selfsigned-dev -spec: - selfSigned: {} diff --git a/k8s/overlays/dev/clusterissuer-staging-dev.yaml b/k8s/overlays/dev/clusterissuer-staging-dev.yaml deleted file mode 100644 index 9dc1d03..0000000 --- a/k8s/overlays/dev/clusterissuer-staging-dev.yaml +++ /dev/null @@ -1,14 +0,0 @@ -apiVersion: cert-manager.io/v1 -kind: ClusterIssuer -metadata: - name: letsencrypt-staging-dev -spec: - acme: - server: https://acme-staging-v02.api.letsencrypt.org/directory - email: devops@example.com - privateKeySecretRef: - name: letsencrypt-staging-dev - solvers: - - http01: - ingress: - class: nginx diff --git a/k8s/overlays/dev/image-patch.yaml b/k8s/overlays/dev/image-patch.yaml deleted file mode 100644 index 92179bc..0000000 --- a/k8s/overlays/dev/image-patch.yaml +++ /dev/null @@ -1,10 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: my-deployment -spec: - template: - spec: - containers: - - name: my-container - image: nginx:stable diff --git a/k8s/overlays/dev/ingress-patch.yaml b/k8s/overlays/dev/ingress-patch.yaml deleted file mode 100644 index fffd645..0000000 --- a/k8s/overlays/dev/ingress-patch.yaml +++ /dev/null @@ -1,23 +0,0 @@ -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: my-ingress - annotations: - cert-manager.io/cluster-issuer: selfsigned-dev - kubernetes.io/ingress.class: nginx -spec: - tls: - - hosts: - - registry.containerk8s.dev.nubes.ru - secretName: registry-containerk8s-tls - rules: - - host: registry.containerk8s.dev.nubes.ru - http: - paths: - - path: / - pathType: Prefix - backend: - service: - name: my-service - port: - number: 80 diff --git a/k8s/overlays/dev/kustomization.yaml b/k8s/overlays/dev/kustomization.yaml deleted file mode 100644 index e73bce9..0000000 --- a/k8s/overlays/dev/kustomization.yaml +++ /dev/null @@ -1,8 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: - - ../../base -namespace: terra-dev -patchesStrategicMerge: - - ingress-patch.yaml - - image-patch.yaml diff --git a/k8s/registry-deployment-new.yaml b/k8s/registry-deployment-new.yaml deleted file mode 100644 index 81d2b34..0000000 --- a/k8s/registry-deployment-new.yaml +++ /dev/null @@ -1,42 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: registry-server - namespace: terra - labels: - app: registry-server -spec: - replicas: 1 - selector: - matchLabels: - app: registry-server - template: - metadata: - labels: - app: registry-server - spec: - containers: - - name: registry-server - image: naeel/terraform-registry-server:docs-dev - imagePullPolicy: Always - ports: - - containerPort: 8080 - env: - - name: REGISTRY_HOSTNAME - value: "terra.k8c.ru" - - name: S3_ENDPOINT - value: "s3.msk-1.ngcloud.ru" - - name: S3_ACCESS_KEY - valueFrom: - secretKeyRef: - name: s3-credentials - key: access-key - - name: S3_SECRET_KEY - valueFrom: - secretKeyRef: - name: s3-credentials - key: secret-key - - name: S3_BUCKET - value: "terraform-registry" - - name: S3_USE_SSL - value: "true" diff --git a/k8s/registry-ingress-new.yaml b/k8s/registry-ingress-new.yaml deleted file mode 100644 index 0eb800e..0000000 --- a/k8s/registry-ingress-new.yaml +++ /dev/null @@ -1,26 +0,0 @@ -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: registry-ingress - namespace: terra - annotations: - cert-manager.io/cluster-issuer: "letsencrypt-prod" - nginx.ingress.kubernetes.io/proxy-body-size: "100m" - nginx.ingress.kubernetes.io/rewrite-target: "/$2" -spec: - ingressClassName: nginx - tls: - - hosts: - - terra.k8c.ru - secretName: registry-tls - rules: - - host: terra.k8c.ru - http: - paths: - - path: "/()(.*)" - pathType: ImplementationSpecific - backend: - service: - name: registry-server - port: - number: 80 diff --git a/operator/.dockerignore b/operator/.dockerignore deleted file mode 100644 index 8654f06..0000000 --- a/operator/.dockerignore +++ /dev/null @@ -1,47 +0,0 @@ -# Exclude files not needed in Docker build context for operator -.git -.gitignore -.dockerignore - -# Docs / markdown -*.md -docs/ - -# Other services / infra -server/ -scripts/ -charts/ -k8s/ - -# Secrets & keys -secrets/ -*.pem -*.key -*.asc -*.gpg -*.pgp - -# Artefacts / temp -artifacts/ -*.har -HAR/ -registry-server-build/ -universal_rebuild/ -tools/ -devops/ - -# IDE / OS -.idea/ -.vscode/ -*.swp -*.swo -*~ -.DS_Store - -# Go test/coverage -*.test -*.out -coverage.out - -# Logs -*.log diff --git a/operator/Makefile b/operator/Makefile deleted file mode 100644 index dd2e6ab..0000000 --- a/operator/Makefile +++ /dev/null @@ -1,23 +0,0 @@ -IMAGE_REPO ?= naeel -TAG ?= latest -PLATFORM ?= linux/amd64 - -.PHONY: build-all push-all build-operator push-operator build-registry push-registry tidy - -tidy: - go mod tidy - -build-all: build-operator build-registry -push-all: push-operator push-registry - -build-operator: - docker build --platform $(PLATFORM) -t $(IMAGE_REPO)/terraform-registry-operator:$(TAG) -f build/Dockerfile.operator . - -push-operator: - docker push $(IMAGE_REPO)/terraform-registry-operator:$(TAG) - -build-registry: - docker build --platform $(PLATFORM) -t $(IMAGE_REPO)/terraform-registry-server:$(TAG) -f build/Dockerfile.registry . - -push-registry: - docker push $(IMAGE_REPO)/terraform-registry-server:$(TAG) diff --git a/operator/README.md b/operator/README.md deleted file mode 100644 index 2f0db79..0000000 --- a/operator/README.md +++ /dev/null @@ -1,44 +0,0 @@ -# Terra Operator Manifests - -## Minimal Setup for Namespace `terra` - -### 1. Structure -- **Namespace**: `terra` -- **Storage**: внешний S3 (Nubes Cloud). -- **CRD**: `TerraformProviderRelease` definitions. - -### 2. How to apply -```bash -kubectl apply -f operator/manifests/00-namespace.yaml -kubectl apply -f operator/manifests/02-crd.yaml -kubectl apply -f operator/manifests/04-operator-deployment.yaml -kubectl apply -f operator/manifests/05-registry-server.yaml -``` - -### 3. Usage -Create a release request: -```yaml -apiVersion: terra.core.nubes.ru/v1alpha1 -kind: TerraformProviderRelease -metadata: - name: mycloud-v0-1-0 - namespace: terra -spec: - providerName: "mycloud" - version: "0.1.0" - gitRepo: "https://github.com/Start-Ops/terraform-provider-mycloud.git" - gitRef: "v0.1.0" -``` - ---- - -## Простыми словами: что это и зачем - -Это набор манифестов и небольшой сервис, который автоматизирует сборку Terraform‑провайдеров. -Идея простая: я описываю в CRD, какой провайдер и какую версию собрать, а оператор сам запускает Job, -собирает бинарники и складывает их в хранилище. Дальше registry‑server отдает эти артефакты Terraform‑клиентам. - -Зачем это нужно: -- чтобы не собирать провайдеры руками; -- чтобы хранить артефакты в одном месте; -- чтобы Terraform мог скачивать провайдеры по обычному источнику. diff --git a/operator/build/Dockerfile.operator b/operator/build/Dockerfile.operator deleted file mode 100644 index adb710d..0000000 --- a/operator/build/Dockerfile.operator +++ /dev/null @@ -1,18 +0,0 @@ -# Build Stage -FROM golang:1.24-alpine AS builder - -WORKDIR /app -COPY go.mod go.sum ./ -RUN go mod download - -COPY cmd ./cmd -# Build statically -RUN CGO_ENABLED=0 GOOS=linux go build -o /operator cmd/main.go - -# Run Stage -FROM alpine:3.19 -WORKDIR /app -COPY --from=builder /operator /app/operator -USER 1000:1000 - -CMD ["/app/operator"] diff --git a/operator/build/Dockerfile.registry b/operator/build/Dockerfile.registry deleted file mode 100644 index 28eb052..0000000 --- a/operator/build/Dockerfile.registry +++ /dev/null @@ -1,20 +0,0 @@ -# Build Stage -FROM golang:1.24-alpine AS builder - -WORKDIR /app -COPY go.mod go.sum ./ -COPY registrykeys ./registrykeys -RUN go mod download -COPY cmd/registry ./cmd/registry -RUN CGO_ENABLED=0 GOOS=linux go build -o /registry-server ./cmd/registry - -# Run Stage -FROM alpine:3.19 -WORKDIR /app -COPY --from=builder /registry-server /app/registry-server -RUN apk add --no-cache ca-certificates - -USER 1000:1000 -EXPOSE 8080 - -CMD ["/app/registry-server"] diff --git a/operator/cmd/registry/docs.go b/operator/cmd/registry/docs.go deleted file mode 100644 index ee111b4..0000000 --- a/operator/cmd/registry/docs.go +++ /dev/null @@ -1,116 +0,0 @@ -package main - -import ( - "context" - "fmt" - "io" - "log" - "mime" - "net/http" - "path/filepath" - "strings" - - s3 "github.com/minio/minio-go/v7" -) - -// parseDocsRequestPath parses paths like: -// /docs////... (rest may be empty) -func parseDocsRequestPath(p string) (namespace, name, version, rest string, err error) { -p = strings.TrimPrefix(p, "/") -p = strings.TrimPrefix(p, "docs/") -parts := strings.SplitN(p, "/", 4) -if len(parts) < 3 { -err = fmt.Errorf("invalid docs path: %s", p) -return -} -namespace = parts[0] -name = parts[1] -version = parts[2] -if len(parts) == 3 { -rest = "" -} else { -rest = parts[3] -} -return -} - -// docsObjectKey builds the S3 key for a docs object given parsed parts. -func docsObjectKey(namespace, name, version, pathPart string) string { -clean := strings.TrimPrefix(pathPart, "/") -if clean == "" { -return fmt.Sprintf("docs/%s/%s/%s/index.html", namespace, name, version) -} -return fmt.Sprintf("docs/%s/%s/%s/%s", namespace, name, version, clean) -} - -// tryCandidateKeys returns a list of keys to attempt for a given request path. -// Order matters: exact path first, then /index.html, then top-level index. -func tryCandidateKeys(namespace, name, version, rest string) []string { -keys := []string{} -if rest == "" { -keys = append(keys, docsObjectKey(namespace, name, version, "index.html")) -return keys -} -// exact -keys = append(keys, docsObjectKey(namespace, name, version, rest)) -// if it looks like a directory or has no extension, try index under it -if strings.HasSuffix(rest, "/") || filepath.Ext(rest) == "" { -keys = append(keys, docsObjectKey(namespace, name, version, strings.TrimSuffix(rest, "/")+"/index.html")) -} -// finally, try root index -keys = append(keys, docsObjectKey(namespace, name, version, "index.html")) -return keys -} - -// docsHandler serves static documentation files from S3 (public-facing via Ingress). -func docsHandler(w http.ResponseWriter, r *http.Request) { -ns, name, ver, rest, err := parseDocsRequestPath(r.URL.Path) -if err != nil { -http.Error(w, "Bad docs path", http.StatusBadRequest) -return -} - -ctx := context.Background() -candidates := tryCandidateKeys(ns, name, ver, rest) -log.Printf("Docs candidates (host=%s): %v", hostname, candidates) - -var lastErr error -for _, key := range candidates { - obj, err := s3Client.GetObject(ctx, bucketName, key, s3.GetObjectOptions{}) -if err != nil { -lastErr = err -continue -} -stat, err := obj.Stat() -if err != nil { -lastErr = err -_ = obj.Close() -continue -} - -// Determine content-type -ext := filepath.Ext(key) -ctype := mime.TypeByExtension(ext) -if ctype == "" { -// fallback for HTML -if ext == ".html" || strings.HasSuffix(key, "index.html") { -ctype = "text/html; charset=utf-8" -} else { -ctype = "application/octet-stream" -} -} - -w.Header().Set("Content-Type", ctype) -w.Header().Set("Content-Length", fmt.Sprintf("%d", stat.Size)) -w.Header().Set("Last-Modified", stat.LastModified.Format(http.TimeFormat)) - -if _, err := io.Copy(w, obj); err != nil { -log.Printf("Error streaming object %s: %v", key, err) -} -_ = obj.Close() -return -} - -log.Printf("Docs not found in candidates: %v, lastErr: %v", candidates, lastErr) -http.Error(w, "Documentation not found", http.StatusNotFound) -} diff --git a/operator/cmd/registry/gpg_key.go b/operator/cmd/registry/gpg_key.go deleted file mode 100644 index 246892a..0000000 --- a/operator/cmd/registry/gpg_key.go +++ /dev/null @@ -1,8 +0,0 @@ -package main - -import registrykeys "terraform-registry-keys" - -var gpgPublicKey = GPGPublicKey{ - KeyID: registrykeys.KeyID, - ASCIIArmor: registrykeys.ASCIIArmor, -} diff --git a/operator/cmd/registry/main.go b/operator/cmd/registry/main.go deleted file mode 100644 index 042ff3d..0000000 --- a/operator/cmd/registry/main.go +++ /dev/null @@ -1,281 +0,0 @@ -package main - -import ( - "bufio" - "context" - "encoding/json" - "fmt" - "io" - "log" - "net/http" - "net/url" - "os" - "strings" - - s3 "github.com/minio/minio-go/v7" - "github.com/minio/minio-go/v7/pkg/credentials" -) - -var ( - s3Client *s3.Client - bucketName = "terraform-registry" // Default - hostname = os.Getenv("REGISTRY_HOSTNAME") -) - -// Terraform Registry Protocol Structs -type Discovery struct { - ProvidersV1 string `json:"providers.v1"` -} - -type VersionList struct { - ID string `json:"id"` - Versions []Version `json:"versions"` - Warnings []string `json:"warnings"` -} - -type Version struct { - Version string `json:"version"` - Protocols []string `json:"protocols"` - Platforms []Platform `json:"platforms"` -} - -type Platform struct { - OS string `json:"os"` - Arch string `json:"arch"` -} - -type DownloadResponse struct { - Protocols []string `json:"protocols"` - OS string `json:"os"` - Arch string `json:"arch"` - Filename string `json:"filename"` - DownloadURL string `json:"download_url"` - ShasumsURL string `json:"shasums_url"` - ShasumsSignatureURL string `json:"shasums_signature_url"` - Shasum string `json:"shasum"` - SigningKeys SigningKeys `json:"signing_keys"` -} - -type SigningKeys struct { - GPGPublicKeys []GPGPublicKey `json:"gpg_public_keys"` -} - -type GPGPublicKey struct { - KeyID string `json:"key_id"` - ASCIIArmor string `json:"ascii_armor"` -} - -func main() { - // 1. Init S3 Connection - if hostname == "" { - hostname = "localhost:8080" - } - - endpoint := os.Getenv("S3_ENDPOINT") - accessKeyID := os.Getenv("S3_ACCESS_KEY") - secretAccessKey := os.Getenv("S3_SECRET_KEY") - - if os.Getenv("S3_BUCKET") != "" { - bucketName = os.Getenv("S3_BUCKET") - } - - var err error - s3Client, err = s3.New(endpoint, &s3.Options{ - Creds: credentials.NewStaticV4(accessKeyID, secretAccessKey, ""), - Secure: true, // Force secure for cloud S3 - }) - if err != nil { - log.Fatalln(err) - } - - // 2. HTTP Handlers - http.HandleFunc("/.well-known/terraform.json", discoveryHandler) - http.HandleFunc("/v1/providers/", router) - http.HandleFunc("/v1/proxy", proxyHandler) - http.HandleFunc("/docs/", docsHandler) - http.Handle("/", http.HandlerFunc(rootHandler)) - - log.Printf("Starting Registry Service on :8080 (Bucket: %s, Endpoint: %s)\n", bucketName, endpoint) - log.Fatal(http.ListenAndServe(":8080", nil)) -} - -func rootHandler(w http.ResponseWriter, r *http.Request) { - if r.URL.Path != "/" { - http.NotFound(w, r) - return - } - w.Header().Set("Content-Type", "text/html; charset=utf-8") - fmt.Fprint(w, ` - - -Terra Registry - -

Terra Registry & Documentation Server

-

Status: ONLINE

-
-

Powered by Nubes Cloud S3 Storage

- - -`) -} - -func proxyHandler(w http.ResponseWriter, r *http.Request) { - bucket := r.URL.Query().Get("bucket") - key := r.URL.Query().Get("key") - - if bucket == "" || key == "" { - http.Error(w, "Missing bucket or key params", http.StatusBadRequest) - return - } - - obj, err := s3Client.GetObject(context.Background(), bucket, key, s3.GetObjectOptions{}) - if err != nil { - log.Printf("Error getting object %s/%s: %v", bucket, key, err) - http.Error(w, "File not found", http.StatusNotFound) - return - } - defer obj.Close() - - stat, err := obj.Stat() - if err != nil { - log.Printf("Error stating object %s/%s: %v", bucket, key, err) - http.Error(w, "File not found or not accessible", http.StatusNotFound) - return - } - - w.Header().Set("Content-Length", fmt.Sprintf("%d", stat.Size)) - w.Header().Set("Content-Type", "application/octet-stream") - w.Header().Set("Last-Modified", stat.LastModified.Format(http.TimeFormat)) - - if _, err := io.Copy(w, obj); err != nil { - log.Printf("Error streaming object: %v", err) - } -} - -func discoveryHandler(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(Discovery{ProvidersV1: "/v1/providers/"}) -} - -func router(w http.ResponseWriter, r *http.Request) { - path := strings.TrimPrefix(r.URL.Path, "/v1/providers/") - parts := strings.Split(path, "/") - - if len(parts) == 3 && parts[2] == "versions" { - listVersions(w, r, parts[0], parts[1]) - return - } - - if len(parts) == 6 && parts[3] == "download" { - downloadVersion(w, r, parts[0], parts[1], parts[2], parts[4], parts[5]) - return - } - - http.Error(w, "Not Found", http.StatusNotFound) -} - -func listVersions(w http.ResponseWriter, r *http.Request, namespace, pType string) { - prefix := fmt.Sprintf("%s/%s/%s/", hostname, namespace, pType) - ctx := context.Background() - versions := []Version{} - seenVersions := map[string]*Version{} - - objectCh := s3Client.ListObjects(ctx, bucketName, s3.ListObjectsOptions{ - Prefix: prefix, - Recursive: true, - }) - - for object := range objectCh { - if object.Err != nil { - continue - } - parts := strings.Split(object.Key, "/") - if len(parts) < 5 { - continue - } - verStr := parts[3] - fileName := parts[4] - - if _, ok := seenVersions[verStr]; !ok { - seenVersions[verStr] = &Version{ - Version: verStr, - Protocols: []string{"5.0"}, - Platforms: []Platform{}, - } - } - - if strings.Contains(fileName, "_linux_amd64.zip") { - seenVersions[verStr].Platforms = append(seenVersions[verStr].Platforms, Platform{OS: "linux", Arch: "amd64"}) - } - if strings.Contains(fileName, "_windows_amd64.zip") { - seenVersions[verStr].Platforms = append(seenVersions[verStr].Platforms, Platform{OS: "windows", Arch: "amd64"}) - } - if strings.Contains(fileName, "_darwin_amd64.zip") { - seenVersions[verStr].Platforms = append(seenVersions[verStr].Platforms, Platform{OS: "darwin", Arch: "amd64"}) - } - if strings.Contains(fileName, "_darwin_arm64.zip") { - seenVersions[verStr].Platforms = append(seenVersions[verStr].Platforms, Platform{OS: "darwin", Arch: "arm64"}) - } - } - - for _, v := range seenVersions { - versions = append(versions, *v) - } - - resp := VersionList{ - ID: fmt.Sprintf("%s/%s/%s", hostname, namespace, pType), - Versions: versions, - } - - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(resp) -} - -func downloadVersion(w http.ResponseWriter, r *http.Request, namespace, pType, version, osType, arch string) { - basePath := fmt.Sprintf("%s/%s/%s/%s", hostname, namespace, pType, version) - filename := fmt.Sprintf("terraform-provider-%s_%s_%s_%s.zip", pType, version, osType, arch) - fullKey := fmt.Sprintf("%s/%s", basePath, filename) - shasumsKey := fmt.Sprintf("%s/terraform-provider-%s_%s_SHA256SUMS", basePath, pType, version) - sigKey := fmt.Sprintf("%s/terraform-provider-%s_%s_SHA256SUMS.sig", basePath, pType, version) - - var shasumValue string - shasumsObj, err := s3Client.GetObject(context.Background(), bucketName, shasumsKey, s3.GetObjectOptions{}) - if err == nil { - defer shasumsObj.Close() - scanner := bufio.NewScanner(shasumsObj) - for scanner.Scan() { - line := scanner.Text() - if strings.Contains(line, filename) { - fields := strings.Fields(line) - if len(fields) >= 1 { - shasumValue = fields[0] - } - break - } - } - } - - baseURL := "https://" + hostname - downloadLink := fmt.Sprintf("%s/v1/proxy?bucket=%s&key=%s", baseURL, bucketName, url.QueryEscape(fullKey)) - shasumsLink := fmt.Sprintf("%s/v1/proxy?bucket=%s&key=%s", baseURL, bucketName, url.QueryEscape(shasumsKey)) - sigLink := fmt.Sprintf("%s/v1/proxy?bucket=%s&key=%s", baseURL, bucketName, url.QueryEscape(sigKey)) - - gpgKey := gpgPublicKey - - resp := DownloadResponse{ - Protocols: []string{"5.0"}, - OS: osType, - Arch: arch, - Filename: filename, - DownloadURL: downloadLink, - ShasumsURL: shasumsLink, - ShasumsSignatureURL: sigLink, - Shasum: shasumValue, - SigningKeys: SigningKeys{ - GPGPublicKeys: []GPGPublicKey{gpgKey}, - }, - } - - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(resp) -} diff --git a/operator/example-release.yaml b/operator/example-release.yaml deleted file mode 100644 index a2858ca..0000000 --- a/operator/example-release.yaml +++ /dev/null @@ -1,11 +0,0 @@ -apiVersion: terra.core.nubes.ru/v1alpha1 -kind: TerraformProviderRelease -metadata: - name: scaffolding-v0-0-1 - namespace: terra -spec: - providerName: "scaffolding" - namespace: "hashicorp" - version: "0.0.1" - gitRepo: "https://github.com/hashicorp/terraform-provider-scaffolding.git" - gitRef: "main" diff --git a/operator/go.mod b/operator/go.mod deleted file mode 100644 index bd795c6..0000000 --- a/operator/go.mod +++ /dev/null @@ -1,37 +0,0 @@ -module terraform-provider-operator - -go 1.24.0 - -toolchain go1.24.12 - -require ( - github.com/minio/minio-go/v7 v7.0.98 - terraform-registry-keys v0.0.0 -) - -require ( - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/dustin/go-humanize v1.0.1 // indirect - github.com/go-ini/ini v1.67.0 // indirect - github.com/google/uuid v1.6.0 // indirect - github.com/klauspost/compress v1.18.2 // indirect - github.com/klauspost/cpuid/v2 v2.2.11 // indirect - github.com/klauspost/crc32 v1.3.0 // indirect - github.com/kr/pretty v0.3.1 // indirect - github.com/minio/crc64nvme v1.1.1 // indirect - github.com/minio/md5-simd v1.1.2 // indirect - github.com/philhofer/fwd v1.2.0 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/rogpeppe/go-internal v1.10.0 // indirect - github.com/rs/xid v1.6.0 // indirect - github.com/tinylib/msgp v1.6.1 // indirect - go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/crypto v0.46.0 // indirect - golang.org/x/net v0.48.0 // indirect - golang.org/x/sys v0.39.0 // indirect - golang.org/x/text v0.32.0 // indirect - gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect -) - -replace terraform-registry-keys => ./registrykeys diff --git a/operator/go.sum b/operator/go.sum deleted file mode 100644 index 8542c10..0000000 --- a/operator/go.sum +++ /dev/null @@ -1,58 +0,0 @@ -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= -github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= -github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk= -github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= -github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.11 h1:0OwqZRYI2rFrjS4kvkDnqJkKHdHaRnCm68/DY4OxRzU= -github.com/klauspost/cpuid/v2 v2.2.11/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= -github.com/klauspost/crc32 v1.3.0 h1:sSmTt3gUt81RP655XGZPElI0PelVTZ6YwCRnPSupoFM= -github.com/klauspost/crc32 v1.3.0/go.mod h1:D7kQaZhnkX/Y0tstFGf8VUzv2UofNGqCjnC3zdHB0Hw= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/minio/crc64nvme v1.1.1 h1:8dwx/Pz49suywbO+auHCBpCtlW1OfpcLN7wYgVR6wAI= -github.com/minio/crc64nvme v1.1.1/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg= -github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= -github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= -github.com/minio/minio-go/v7 v7.0.98 h1:MeAVKjLVz+XJ28zFcuYyImNSAh8Mq725uNW4beRisi0= -github.com/minio/minio-go/v7 v7.0.98/go.mod h1:cY0Y+W7yozf0mdIclrttzo1Iiu7mEf9y7nk2uXqMOvM= -github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM= -github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= -github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= -github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/tinylib/msgp v1.6.1 h1:ESRv8eL3u+DNHUoSAAQRE50Hm162zqAnBoGv9PzScPY= -github.com/tinylib/msgp v1.6.1/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA= -go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= -go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= -golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= -golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= -golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= -golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= -golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= -golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/operator/manifests/00-namespace.yaml b/operator/manifests/00-namespace.yaml deleted file mode 100644 index 5d0c7df..0000000 --- a/operator/manifests/00-namespace.yaml +++ /dev/null @@ -1,6 +0,0 @@ -apiVersion: v1 -kind: Namespace -metadata: - name: terra - labels: - name: terra diff --git a/operator/manifests/00-rbac.yaml b/operator/manifests/00-rbac.yaml deleted file mode 100644 index 8a806c8..0000000 --- a/operator/manifests/00-rbac.yaml +++ /dev/null @@ -1,35 +0,0 @@ -apiVersion: v1 -kind: ServiceAccount -metadata: - name: terraform-operator - namespace: terra ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - name: terraform-operator - namespace: terra -rules: -- apiGroups: ["terra.core.nubes.ru"] - resources: ["providerreleases", "providerreleases/status"] - verbs: ["get", "list", "watch", "update", "patch"] -- apiGroups: ["batch"] - resources: ["jobs"] - verbs: ["create", "get", "list", "watch", "delete"] -- apiGroups: [""] - resources: ["pods", "pods/log"] - verbs: ["get", "list", "watch"] ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: terraform-operator - namespace: terra -subjects: -- kind: ServiceAccount - name: terraform-operator - namespace: terra -roleRef: - kind: Role - name: terraform-operator - apiGroup: rbac.authorization.k8s.io diff --git a/operator/manifests/02-crd.yaml b/operator/manifests/02-crd.yaml deleted file mode 100644 index 295f21f..0000000 --- a/operator/manifests/02-crd.yaml +++ /dev/null @@ -1,77 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - name: providerreleases.terra.core.nubes.ru -spec: - group: terra.core.nubes.ru - versions: - - name: v1alpha1 - served: true - storage: true - subresources: - status: {} - schema: - openAPIV3Schema: - type: object - properties: - spec: - type: object - required: - - providerName - - version - - gitRepo - - gitRef - properties: - providerName: - type: string - description: "Name of the provider (e.g. 'mycloud')" - namespace: - type: string - default: "mycloud" - description: "Terraform registry namespace (e.g. 'nubes')" - version: - type: string - description: "Semantic version without v (e.g. '1.0.1')" - gitRepo: - type: string - description: "Git repository URL to clone" - gitRef: - type: string - description: "Git tag, commit SHA or branch" - platforms: - type: array - items: - type: string - default: ["linux_amd64", "windows_amd64"] - description: "Target platforms for build" - status: - type: object - properties: - phase: - type: string - description: "Current phase: Pending, Building, Publishing, Ready, Failed" - downloadUrl: - type: string - conditions: - type: array - items: - type: object - properties: - type: - type: string - status: - type: string - lastTransitionTime: - type: string - format: date-time - reason: - type: string - message: - type: string - scope: Namespaced - names: - plural: providerreleases - singular: providerrelease - kind: TerraformProviderRelease - shortNames: - - tpr diff --git a/operator/manifests/03-build-script.yaml b/operator/manifests/03-build-script.yaml deleted file mode 100644 index a10e8af..0000000 --- a/operator/manifests/03-build-script.yaml +++ /dev/null @@ -1,75 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: builder-script - namespace: terra -data: - build.sh: | - #!/bin/sh - set -e - - echo ">>> Starting Build for $PROVIDER_NAME $VERSION" - - # 1. Setup Environment - # Inputs: GIT_REPO, GIT_REF, PROVIDER_NAME, VERSION - WORK_DIR="/workspace" - ARTIFACTS_DIR="/artifacts" - mkdir -p $WORK_DIR $ARTIFACTS_DIR - - # 2. Clone Repository - echo ">>> Cloning $GIT_REPO ($GIT_REF)..." - git clone $GIT_REPO $WORK_DIR/src - cd $WORK_DIR/src - git checkout $GIT_REF - - # 3. Build for Platforms - # Hardcoded platforms for MVP: linux/amd64, windows/amd64 - # Naming convention: terraform-provider-NAME_vVERSION_OS_ARCH.zip - - echo ">>> Building binaries..." - - # Linux amd64 - echo "--> linux/amd64" - GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o terraform-provider-${PROVIDER_NAME}_v${VERSION} - zip ${ARTIFACTS_DIR}/terraform-provider-${PROVIDER_NAME}_${VERSION}_linux_amd64.zip terraform-provider-${PROVIDER_NAME}_v${VERSION} - rm terraform-provider-${PROVIDER_NAME}_v${VERSION} - - # Windows amd64 - echo "--> windows/amd64" - GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -o terraform-provider-${PROVIDER_NAME}_v${VERSION}.exe - zip ${ARTIFACTS_DIR}/terraform-provider-${PROVIDER_NAME}_${VERSION}_windows_amd64.zip terraform-provider-${PROVIDER_NAME}_v${VERSION}.exe - rm terraform-provider-${PROVIDER_NAME}_v${VERSION}.exe - - # 4. Checksums - cd $ARTIFACTS_DIR - sha256sum *.zip > terraform-provider-${PROVIDER_NAME}_${VERSION}_SHA256SUMS - - # 5. GPG Sign (Mock for MVP - empty sig or skip if client allows, but Terraform strict. - # For now we will generate a dummy signature to satisfy file existence, real signing requires GPG private key in env) - # Ideally: gpg --detach-sign --armor terraform-provider-..._SHA256SUMS - touch terraform-provider-${PROVIDER_NAME}_${VERSION}_SHA256SUMS.sig - - # 6. Upload to S3 - # Layout: /terraform-providers/hostname/namespace/name/version/ - # We will use 'REGISTRY_HOSTNAME' env var if set, else default. - - if [ -z "$REGISTRY_HOSTNAME" ]; then - REGISTRY_HOSTNAME="terra.k8c.ru" - fi - - TARGET_PATH="terraform-providers/$REGISTRY_HOSTNAME/${NAMESPACE}/${PROVIDER_NAME}/${VERSION}" - - echo ">>> Uploading to S3 path: $TARGET_PATH" - - # Configure mc using S3 credentials (S3_ENDPOINT, S3_ACCESS_KEY, S3_SECRET_KEY) - mc alias set nubes_s3 https://$S3_ENDPOINT $S3_ACCESS_KEY $S3_SECRET_KEY - - # Upload files - mc cp --recursive $ARTIFACTS_DIR/ nubes_s3/$TARGET_PATH/ - - # 7. Generate shasums file for download (Terraform download protocol requires a checksums.txt usually or just the sums file) - # We are following the provider registry protocol loosely. - # The most important part for discovery is the 'index.json' or 'versions' endpoint, but we are static hosting. - # We rely on the directory structure. - - echo ">>> Build Complete!" diff --git a/operator/manifests/04-operator-deployment.yaml b/operator/manifests/04-operator-deployment.yaml deleted file mode 100644 index 23a0872..0000000 --- a/operator/manifests/04-operator-deployment.yaml +++ /dev/null @@ -1,27 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: terraform-operator - namespace: terra -spec: - replicas: 1 - selector: - matchLabels: - app: terraform-operator - template: - metadata: - labels: - app: terraform-operator - spec: - serviceAccountName: terraform-operator - containers: - - name: operator - image: naeel/terraform-registry-operator:latest - imagePullPolicy: Always - env: - - name: REGISTRY_HOSTNAME - value: "terra.k8c.ru" - resources: - limits: - memory: "128Mi" - cpu: "500m" diff --git a/operator/manifests/05-registry-server.yaml b/operator/manifests/05-registry-server.yaml deleted file mode 100644 index b1f36f5..0000000 --- a/operator/manifests/05-registry-server.yaml +++ /dev/null @@ -1,81 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: registry-server - namespace: terra - labels: - app: registry-server -spec: - replicas: 1 - selector: - matchLabels: - app: registry-server - template: - metadata: - labels: - app: registry-server - spec: - containers: - - name: registry-server - image: naeel/terraform-registry-server:gpg-key-20260208 - imagePullPolicy: Always - env: - - name: REGISTRY_HOSTNAME - value: "terra.k8c.ru" - - name: S3_ENDPOINT - value: "s3.msk-1.ngcloud.ru" - - name: S3_ACCESS_KEY - valueFrom: - secretKeyRef: - key: access-key - name: s3-credentials - - name: S3_SECRET_KEY - valueFrom: - secretKeyRef: - key: secret-key - name: s3-credentials - - name: S3_BUCKET - value: "terraform-registry" - - name: S3_USE_SSL - value: "true" - ports: - - containerPort: 8080 - ---- -apiVersion: v1 -kind: Service -metadata: - name: registry-server - namespace: terra -spec: - selector: - app: registry-server - ports: - - port: 80 - targetPort: 8080 ---- -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: registry-ingress - namespace: terra - annotations: - nginx.ingress.kubernetes.io/rewrite-target: / - cert-manager.io/cluster-issuer: letsencrypt-prod -spec: - ingressClassName: nginx - tls: - - hosts: - - terra.k8c.ru - secretName: registry-tls - rules: - - host: terra.k8c.ru - http: - paths: - - path: / - pathType: Prefix - backend: - service: - name: registry-server - port: - number: 80 diff --git a/operator/manifests/06-registry-server-docs.yaml b/operator/manifests/06-registry-server-docs.yaml deleted file mode 100644 index 52de092..0000000 --- a/operator/manifests/06-registry-server-docs.yaml +++ /dev/null @@ -1,42 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: registry-server - namespace: terra - labels: - app: registry-server -spec: - replicas: 1 - selector: - matchLabels: - app: registry-server - template: - metadata: - labels: - app: registry-server - spec: - containers: - - name: registry-server - image: naeel/terraform-registry-server:docs-dev-fixed - imagePullPolicy: Always - env: - - name: REGISTRY_HOSTNAME - value: "terra.k8c.ru" - - name: S3_ENDPOINT - value: "s3.msk-1.ngcloud.ru" - - name: S3_ACCESS_KEY - valueFrom: - secretKeyRef: - key: access-key - name: s3-credentials - - name: S3_SECRET_KEY - valueFrom: - secretKeyRef: - key: secret-key - name: s3-credentials - - name: S3_BUCKET - value: "terraform-registry" - - name: S3_USE_SSL - value: "true" - ports: - - containerPort: 8080 diff --git a/operator/registrykeys/go.mod b/operator/registrykeys/go.mod deleted file mode 100644 index f29132e..0000000 --- a/operator/registrykeys/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -module terraform-registry-keys - -go 1.24.0 - -toolchain go1.24.12 diff --git a/operator/registrykeys/public_key.go b/operator/registrykeys/public_key.go deleted file mode 100644 index 2718aff..0000000 --- a/operator/registrykeys/public_key.go +++ /dev/null @@ -1,59 +0,0 @@ -package registrykeys - -const KeyID = "3EC4673EB798238A" - -const ASCIIArmor = `-----BEGIN PGP PUBLIC KEY BLOCK----- - -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 -6cZ0NQ== -=3Ea2 ------END PGP PUBLIC KEY BLOCK----- -` diff --git a/registrykeys/go.mod b/registrykeys/go.mod deleted file mode 100644 index f29132e..0000000 --- a/registrykeys/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -module terraform-registry-keys - -go 1.24.0 - -toolchain go1.24.12 diff --git a/registrykeys/public_key.go b/registrykeys/public_key.go deleted file mode 100644 index c4ca056..0000000 --- a/registrykeys/public_key.go +++ /dev/null @@ -1,67 +0,0 @@ -package registrykeys - -const KeyID = "CB3A0DF161ECC416" - -const ASCIIArmor = `-----BEGIN PGP PUBLIC KEY BLOCK----- - -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 -=q8Tw ------END PGP PUBLIC KEY BLOCK-----` diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index aa55d98..0000000 --- a/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -flask -boto3 diff --git a/scripts/build-and-push-registry-server.sh b/scripts/build-and-push-registry-server.sh deleted file mode 100755 index 74c7e3e..0000000 --- a/scripts/build-and-push-registry-server.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# Build and optionally push the registry-server image with docs handler -# Usage: ./scripts/build-and-push-registry-server.sh -IMAGE_REPO=${1:-naeel} -TAG=${2:-docs-dev} - -cd operator -make build-registry IMAGE_REPO=${IMAGE_REPO} TAG=${TAG} - -echo "Built image: ${IMAGE_REPO}/terraform-registry-server:${TAG}" - -echo "To push: docker push ${IMAGE_REPO}/terraform-registry-server:${TAG}" - -echo "To deploy to cluster (example):" -echo " kubectl apply -f operator/manifests/06-registry-server-docs.yaml" - -echo "and edit the file to replace IMAGE_REPO/TAG with your registry and tag before applying." diff --git a/site/app.py b/site/app.py deleted file mode 100644 index 1faaa35..0000000 --- a/site/app.py +++ /dev/null @@ -1,411 +0,0 @@ -VERSION = "1.0.0" - -import os -import mimetypes -import urllib.parse - -from flask import Flask, Response, jsonify, request, stream_with_context - -import boto3 -from botocore.config import Config - -# --------------------------------------------------------------------------- -# Config (from ENV, matching Go defaults) -# --------------------------------------------------------------------------- -HOSTNAME = os.getenv("REGISTRY_HOSTNAME", "localhost:8080") -S3_PREFIX = os.getenv("S3_PREFIX", "registry.containerk8s.dev.nubes.ru") -S3_ENDPOINT = os.getenv("S3_ENDPOINT", "") -S3_ACCESS_KEY = os.getenv("S3_ACCESS_KEY", "") -S3_SECRET_KEY = os.getenv("S3_SECRET_KEY", "") -S3_BUCKET = os.getenv("S3_BUCKET", "terraform-registry") - -# --------------------------------------------------------------------------- -# GPG public keys (from LIVE Go registry — TWO keys: primary + legacy) -# --------------------------------------------------------------------------- -GPG_PRIMARY_KEY_ID = "3EC4673EB798238A" -GPG_PRIMARY_ASCII_ARMOR = """-----BEGIN PGP PUBLIC KEY BLOCK----- - -mQINBGl2EqgBEAClEcif3Xy4rfZnh7HtZrj1K2mEufWVMCV01D75/5SSlfoi9Xxf -4mKojrqF47sfGLNYZkcigodJx7dLcHD0Dx23nKU3AuAdmPhLdl2HRHCljTZ4ZEe7 -RLYp2KhGWDUn7dX79eB4KhUOXmMVdbi7e5VKWg4vQI8UdeCIvsLEbJ8+jrBislFX -4skMWu+59loxXaYKmgJ0EN+x3Z1eSlzYrYZwSaATgS+bajmWXQhHjK/F76IGxep0 -O26sOfM3p/oIbhMUnYRcG3tK5/bc2YQccWQlh5O1l+Qa2os6vDERsEWG3yv74QgZ -lsadvBArI4Wz6PKdZT8pQBoWrXMherSqo2iSs2U3gZMbk29Gmgdor/vy/gF14Mds -N020Kg6xUjMRqQLkl3VZzNHdGhi2gQdTMktigoImthqpuSUDIeIGZjATyg7ZmsTa -YS1yAtmJiPzoC+IqmC28PGk+eZ8rJQEq2ipraLY+RQc1GV1sRniv6nj6+C2OlgYf -vX6ViOr+QqO+oTujM10hyCkZCX0gCCnnSJHZa4lxzkBr6BiTUapd7JZNtofT8H2m -xnebSgG85bGzPFL1tzZYAm5QaspwkgFT2g97XvyEN+JVAXkiJTkbnMWW33cnyDOC -/kPFkYkhzKceW+pGkYYPbMpta/Bm7yhITJ79UMdhN214XM6fV0325rZPdQARAQAB -tCNOdWJlcyBQcm92aWRlciA8bnViZXNAdGVycmEuazhjLnJ1PokCfQQTAQgAcQWC -aXYSqAMLCQcJED7EZz63mCOKNRQAAAAAABwAEHNhbHRAbm90YXRpb25zLm9wZW5w -Z3Bqcy5vcmc9Meal6wZYOa4GSbmo/rRcAhUIAxYAAgIZAQKbAwIeARYhBIZv2T1F -bcqADyRIQT7EZz63mCOKAADkkQ//WRUo1yq+1boJ1tmkiRfhRWmg9PXhEPVg/gCC -nAm21yv2BTx7TrFbSMliUF3G8egQx6ZSaUiZUUIW9+x6V0CddR0w+eGNzFSyqf9q -scC3T6qW/k+m6Hqr9upGEFrpz9dXHWi6FCU5sjou5cAk/JUinzpaaiU6JPsXVnlE -QwtvlJEftsDLBZ0pxrni9LbMykR2v8rThZahCHFU9J6cEs5/IdfBmh2erjaDzj4M -g7FjDTk3h86ovgVWvaBzTs4FZHdI1BhtQK3IO31kVqZj3RtrdWy6o0O5bSVv3cxO -ofnvEW2is9OduvgkOq9EeuQXkD/kuE2adxmH4RAUEeDXCSknk82FdsSmFok66VgG -Kb8XXiIAtjsqyxwNe4Y+yr+gYACdfVn/C3b+5hlQWtBAlVJRjfnn3FWhmiSBAJtF -2swVSk4s0oFM1hEmBNLG45CnATOVPGI1LR3AToYg2gPCb/BXExDE0hKJd7ZaV0aS -NSpJTtG2nrH8qFi8Y9WM41klwmE650idUN9SoecuUQsedFhZKPfJiKeTSt1CnqQr -nsQb3lr/LEwZmiehb+eDif2ndgAxP+T8ySHbdXvoX5zF5bcx63lhQKnExc9zmrWZ -gn60BnZ8aLr8G1CkMhm4fugdVkcXoQAmOXRJEdu3kbY8xI2350Cxw3H3E9gzpx6p -amKpVOK5Ag0EaXYSqAEQALMb57+x1zm2hs4DdCmajxRGZ1F4DJQFmKgV/z0KSeeO -8DYJp+vZ/zU6wQX6GU4kbYOK9+sUq8VrZTrUe1CFQuIfUWMQj03cXWizTTktcsfV -nLyj2ucNpTZxV2Yx/4A7T1x48ICt6q2vVoAI2nshqfrxL1J629olW8XG7v5kKQtx -IwHVVzgGgnfLVo/IkysudzYYAehP6E1aGiMRt6ZWOsq71FOeIjTD4FOmTzfzNyXP -zn31C3R6Cka7/xn/frN4KUVBu5ynFkfpifJvuSPX1DRk3nz+fEtilPCoHx9UZERm -sFKwjzPpCEoqMYi0PbjeJnILS32CvZE47uw6S2YDMsHzxbd3TcIgJP7VElI7Oa7r -n71KMEkyCTbD6kcy0qCQvcCVa4/868PBbBbiu1/I7AARC12jSprNI7NlRFkprfkm -+JtzsH7drjCLqr7GKWPzU1lgVYEC9vDJInPLH/GpbTF+wQM+K84n4KRSknHK2JAX -HQ6Aop1lUa/ZeWdD5oormYb8UrLs9fmSQ8GR9b8Jpca+0P3D5+3NSBJt1sGvkify -bC5EAAD8OJo8BLm6dfE/1u3L054h35Cw+RVv2zzhigN07YQ51Ljse6cadd2usYXz -yEuxk7983tSUup8elaVSGtSvKcTXjylpZoK+R8oemmdnEbuJM3zRFDRLPKqZdALV -ABEBAAGJAmwEGAEIAGAFgml2EqgJED7EZz63mCOKNRQAAAAAABwAEHNhbHRAbm90 -YXRpb25zLm9wZW5wZ3Bqcy5vcmfVQTha3ksLF9ZTQkJ+Iv5tApsMFiEEhm/ZPUVt -yoAPJEhBPsRnPreYI4oAAGkyD/9yA6a59iOhPedtOIEmjJWVvjtv06yYlnB66tbM -WGXWTofsb98CF9bymE+YvMNXYvqkw4q0P7OY6D64PXhQTSiYRtDscIZ8w3Hw5t73 -qttZ0QAx5HFjKoQUnyHvGO1rUgykKx+9sytTQwBIFq4FmyVQltsY8tX8D1nLS0iH -8IFwqBNM26bVcAkV9aeayjoRKodyy9Xz035Bmh8pFIMjM2JvCoub1TrftF2EzYng -ljQF76AQHGmPa36rq2oSocE+xP5GFyZv+PEPGCFTLo/5ZaHui8iqPMfVWIAdWAj1 -a6SW6zDk8DQQRGll4e7kWpGZ2+z4Zk9o449Ka7Kwc6lgpx86Ir6XT0XJKX55Q7Od -dKajTMDJE36t/00oAS4/AokL7StJTwmpMQAPv5/829uPkfcV6Oll79XvAcwV7vSq -0is+m5InUzkwunuBUsYBCtFKFY47oB5D0RLGSdUlo8GLfT1tf/0n4uRSq4aQgN/D -2gvvddXyFUds0Ar4y3Hthi1QHYOL5/4pmfhH45+Hxje03XSI9twGMqpFoennAYvV -wuyeu9XNXDI4gKiAMbzyxyhifOooBOyxOKEtXWPzfP8v9iTFw7cofmvSD1FTt45l -6JckYfV3TFaN2lFD5SxrasIuYPWPoOf4zzcljQjqOw0sVqXeaQgkq/+soD08YzKg -6cZ0NQ== -=3Ea2 ------END PGP PUBLIC KEY BLOCK-----""" - -GPG_LEGACY_KEY_ID = "CB3A0DF161ECC416" -GPG_LEGACY_ASCII_ARMOR = """-----BEGIN PGP PUBLIC KEY BLOCK----- - -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 -=q8Tw ------END PGP PUBLIC KEY BLOCK-----""" - -# --------------------------------------------------------------------------- -# S3 client (MinIO-compatible, Secure=true in Go → use_ssl=True + s3v4) -# --------------------------------------------------------------------------- -s3 = boto3.client( - "s3", - endpoint_url=f"https://{S3_ENDPOINT}", - aws_access_key_id=S3_ACCESS_KEY, - aws_secret_access_key=S3_SECRET_KEY, - config=Config(signature_version="s3v4"), - use_ssl=True, -) - -# --------------------------------------------------------------------------- -# Flask app -# --------------------------------------------------------------------------- -app = Flask(__name__) - - -# --------------------------------------------------------------------------- -# Route: Discovery -# GET /.well-known/terraform.json -# --------------------------------------------------------------------------- -@app.route("/.well-known/terraform.json") -def discovery(): - return jsonify({"providers.v1": "/v1/providers/"}) - - -# --------------------------------------------------------------------------- -# Route: Provider router -# GET /v1/providers/{ns}/{name}/versions -# GET /v1/providers/{ns}/{name}/{ver}/download/{os}/{arch} -# --------------------------------------------------------------------------- -@app.route("/v1/providers/") -def provider_router(provider_path): - parts = provider_path.split("/") - - # /v1/providers/{ns}/{name}/versions - if len(parts) == 3 and parts[2] == "versions": - return _list_versions(parts[0], parts[1]) - - # /v1/providers/{ns}/{name}/{ver}/download/{os}/{arch} - if len(parts) == 6 and parts[3] == "download": - return _download_version(parts[0], parts[1], parts[2], parts[4], parts[5]) - - return "Not Found", 404 - - -def _list_versions(namespace, name): - """List all versions and platforms by scanning S3 objects.""" - prefix = f"{S3_PREFIX}/{namespace}/{name}/" - seen = {} - - paginator = s3.get_paginator("list_objects_v2") - for page in paginator.paginate(Bucket=S3_BUCKET, Prefix=prefix): - for obj in page.get("Contents", []): - key = obj["Key"] - key_parts = key.split("/") - if len(key_parts) < 5: - continue - ver = key_parts[3] - filename = key_parts[4] - - if ver not in seen: - seen[ver] = { - "version": ver, - "protocols": ["5.0"], - "platforms": [], - } - - if "_darwin_amd64.zip" in filename: - seen[ver]["platforms"].append({"os": "darwin", "arch": "amd64"}) - if "_linux_amd64.zip" in filename: - seen[ver]["platforms"].append({"os": "linux", "arch": "amd64"}) - if "_windows_amd64.zip" in filename: - seen[ver]["platforms"].append({"os": "windows", "arch": "amd64"}) - - versions = list(seen.values()) - return jsonify({ - "id": f"{namespace}/{name}", - "versions": versions, - "warnings": [], - }) - - -def _download_version(namespace, name, version, os_type, arch): - """Build download metadata JSON with S3 proxy URLs.""" - base = f"{S3_PREFIX}/{namespace}/{name}/{version}" - filename = f"terraform-provider-{name}_{version}_{os_type}_{arch}.zip" - full_key = f"{base}/{filename}" - shasums_key = f"{base}/terraform-provider-{name}_{version}_SHA256SUMS" - sig_key = f"{base}/terraform-provider-{name}_{version}_SHA256SUMS.sig" - - # Extract shasum for this file from SHA256SUMS - shasum = "" - try: - resp = s3.get_object(Bucket=S3_BUCKET, Key=shasums_key) - for line in resp["Body"].read().decode("utf-8", errors="replace").splitlines(): - if filename in line: - shasum = line.split()[0] - break - except Exception: - pass - - base_url = f"https://{HOSTNAME}" - download_url = f"{base_url}/v1/proxy?bucket={S3_BUCKET}&key={urllib.parse.quote(full_key)}" - shasums_url = f"{base_url}/v1/proxy?bucket={S3_BUCKET}&key={urllib.parse.quote(shasums_key)}" - sig_url = f"{base_url}/v1/proxy?bucket={S3_BUCKET}&key={urllib.parse.quote(sig_key)}" - - return jsonify({ - "protocols": ["5.0"], - "os": os_type, - "arch": arch, - "filename": filename, - "download_url": download_url, - "shasums_url": shasums_url, - "shasums_signature_url": sig_url, - "shasum": shasum, - "signing_keys": { - "gpg_public_keys": [ - { - "key_id": GPG_PRIMARY_KEY_ID, - "ascii_armor": GPG_PRIMARY_ASCII_ARMOR, - }, - { - "key_id": GPG_LEGACY_KEY_ID, - "ascii_armor": GPG_LEGACY_ASCII_ARMOR, - }, - ] - }, - }) - - -# --------------------------------------------------------------------------- -# Route: Proxy (S3 streaming) -# GET /v1/proxy?bucket=...&key=... -# --------------------------------------------------------------------------- -@app.route("/v1/proxy") -def proxy(): - bucket = request.args.get("bucket", "") - key = request.args.get("key", "") - - if not bucket or not key: - return "Missing bucket or key params", 400 - - try: - head = s3.head_object(Bucket=bucket, Key=key) - obj = s3.get_object(Bucket=bucket, Key=key) - - return Response( - stream_with_context(obj["Body"].iter_chunks(chunk_size=65536)), - content_type="application/octet-stream", - headers={ - "Content-Length": str(head["ContentLength"]), - "Last-Modified": head["LastModified"].strftime("%a, %d %b %Y %H:%M:%S GMT"), - }, - ) - except Exception: - return "File not found", 404 - - -# --------------------------------------------------------------------------- -# Route: Docs (static from S3) -# GET /docs/{ns}/{name}/{ver}/... -# --------------------------------------------------------------------------- -@app.route("/docs/") -def docs(doc_path): - # Parse: docs/{namespace}/{name}/{version}/...rest - parts = doc_path.strip("/").split("/") - if len(parts) < 3: - return "Bad docs path", 400 - - namespace = parts[0] - name = parts[1] - version = parts[2] - rest = "/".join(parts[3:]) if len(parts) > 3 else "" - - # Build candidate S3 keys (exact 1:1 with Go tryCandidateKeys) - candidates = [] - - if rest == "": - candidates.append(f"docs/{namespace}/{name}/{version}/index.html") - else: - # 1) exact path - candidates.append(f"docs/{namespace}/{name}/{version}/{rest}") - - # 2) if looks like directory or no extension → try index.html under it - rest_ext = os.path.splitext(rest)[1] # ".css" or "" - if rest.endswith("/") or rest_ext == "": - candidates.append( - f"docs/{namespace}/{name}/{version}/{rest.rstrip('/')}/index.html" - ) - - # 3) fallback: root index.html - candidates.append(f"docs/{namespace}/{name}/{version}/index.html") - - for s3_key in candidates: - try: - head = s3.head_object(Bucket=S3_BUCKET, Key=s3_key) - obj = s3.get_object(Bucket=S3_BUCKET, Key=s3_key) - - # Content-Type (1:1 with Go docsHandler) - ext = os.path.splitext(s3_key)[1] # ".html", ".css", etc. - ctype = mimetypes.types_map.get(ext, "") - if not ctype: - if ext == ".html" or s3_key.endswith("index.html"): - ctype = "text/html; charset=utf-8" - else: - ctype = "application/octet-stream" - - return Response( - stream_with_context(obj["Body"].iter_chunks(chunk_size=65536)), - content_type=ctype, - headers={ - "Content-Length": str(head["ContentLength"]), - "Last-Modified": head["LastModified"].strftime( - "%a, %d %b %Y %H:%M:%S GMT" - ), - }, - ) - except Exception: - continue - - return "Documentation not found", 404 - - -# --------------------------------------------------------------------------- -# Route: Healthz -# GET /healthz -# --------------------------------------------------------------------------- -@app.route("/healthz") -def healthz(): - return "ok", 200 - - -# --------------------------------------------------------------------------- -# Route: Readyz -# GET /readyz -# --------------------------------------------------------------------------- -@app.route("/readyz") -def readyz(): - return "ok", 200 - - -# --------------------------------------------------------------------------- -# Route: Root (LAST — must not shadow /docs) -# GET / -# --------------------------------------------------------------------------- -@app.route("/") -def index(): - return f""" - -Terra Registry v{VERSION} - -

Terra Registry & Documentation Server v{VERSION}

-

Status: ONLINE

-
-

Powered by Nubes Cloud S3 Storage

- -""" - - -# --------------------------------------------------------------------------- -# Main (mandatory for Nubes managed service) -# --------------------------------------------------------------------------- -if __name__ == "__main__": - app.run(host="0.0.0.0", port=5000) From 3aca89ea602d488ea1d47aaf20de1776d3c515f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CNaeel=E2=80=9D?= Date: Sun, 9 Aug 2026 07:22:36 +0400 Subject: [PATCH 10/10] =?UTF-8?q?doc:=20README.md,=20CHAT=5FSUMMARY=20+=20?= =?UTF-8?q?REGISTRY=5FOVERVIEW=20=E2=86=92=20HISTORY?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CHAT_SUMMARY_2026-08-06.md | 0 .../REGISTRY_OVERVIEW.md | 0 README.md | 86 +++++++++++++++++++ 3 files changed, 86 insertions(+) rename CHAT_SUMMARY_2026-08-06.md => HISTORY/CHAT_SUMMARY_2026-08-06.md (100%) rename REGISTRY_OVERVIEW.md => HISTORY/REGISTRY_OVERVIEW.md (100%) create mode 100644 README.md diff --git a/CHAT_SUMMARY_2026-08-06.md b/HISTORY/CHAT_SUMMARY_2026-08-06.md similarity index 100% rename from CHAT_SUMMARY_2026-08-06.md rename to HISTORY/CHAT_SUMMARY_2026-08-06.md diff --git a/REGISTRY_OVERVIEW.md b/HISTORY/REGISTRY_OVERVIEW.md similarity index 100% rename from REGISTRY_OVERVIEW.md rename to HISTORY/REGISTRY_OVERVIEW.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..865ec9f --- /dev/null +++ b/README.md @@ -0,0 +1,86 @@ +# Terraform Provider Registry + +Реестр Terraform-провайдеров по протоколу HashiCorp. +Деплой: Docker-контейнер в Nubes Cloud (`containerk8s`). + +**URL:** `https://registry.containerk8s.dev.nubes.ru` + +## Структура + +``` +├── server/ # Go-код HTTP-сервера +│ ├── main.go # Роуты, S3, структуры +│ ├── gpg_key.go # GPG-ключи (2 шт.) +│ ├── docs.go # Статика документации из S3 +│ ├── build-provider.sh # Сборка и заливка провайдера +│ ├── go.mod / go.sum +│ └── gpg/ +├── tests/ +│ └── smoke.sh # 9 интеграционных тестов +├── secrets/ # Креды (в .gitignore) +├── HISTORY/ # Документация, планы, ревью +├── Dockerfile # Multi-stage сборка (ветка go-container) +└── .gitignore +``` + +## Эндпоинты + +| Метод | URL | Ответ | +|---|---|---| +| GET | `/` | HTML с версией | +| GET | `/.well-known/terraform.json` | `{"providers.v1":"/v1/providers/"}` | +| GET | `/v1/providers/{ns}/{name}/versions` | JSON: список версий и платформ | +| GET | `/v1/providers/{ns}/{name}/{ver}/download/{os}/{arch}` | JSON: download_url, shasum, GPG | +| GET | `/v1/proxy?key=...` | Стриминг файла из S3 | +| GET | `/docs/{ns}/{name}/{ver}/...` | Статика документации из S3 | +| GET | `/healthz` | `ok` 200 | +| GET | `/readyz` | `ok` 200 (проверяет S3) | + +## Сборка и деплой + +```bash +# Сборка образа (на ВМ) +ssh naeel@5.172.178.213 +cd ~/tf_registry && git checkout go-container +docker build -t gitea.services.ngcloud.ru/nail/tf_registry:latest . +docker push gitea.services.ngcloud.ru/nail/tf_registry:latest + +# Редеплой — через UI modify или пересоздать инстанс +``` + +## Заливка провайдера + +Подробно: `HISTORY/HOWTO-UPLOAD.md` + +Кратко: +```bash +export REGISTRY_HOSTNAME=registry.containerk8s.dev.nubes.ru +export S3_ENDPOINT=http://ceph.tst.nubes.ru +export S3_ACCESS_KEY=... # super-креды +export S3_SECRET_KEY=... +bash server/build-provider.sh 5.1.17 +``` + +## Тесты + +```bash +bash tests/smoke.sh +``` + +## Конфигурация + +| Где | Что | +|---|---| +| `jsonEnv` (UI) | Только `S3_ACCESS_KEY`, `S3_SECRET_KEY` (reader) | +| `Dockerfile ENV` | `S3_ENDPOINT`, `S3_BUCKET`, `S3_PREFIX`, `REGISTRY_HOSTNAME`, `PORT`, `S3_USE_SSL` | + +Подробно: `HISTORY/env-config.md` + +## Ветки + +| Ветка | Назначение | +|---|---| +| `master` | Основная, чистый код | +| `go-container` | Сборка: Go + Dockerfile | +| `with-flask-legacy` | Архив Flask-версии | +| `golang` | Архив исходного Go (до правок) |