Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
13e57aa096 | ||
|
|
25b50ce59b | ||
|
|
2a9a7311ef | ||
|
|
d841ffcbac | ||
|
|
01e0245139 | ||
|
|
efa7c5e085 | ||
|
|
c235db00de | ||
|
|
24796f4b4f |
-30
@@ -1,30 +0,0 @@
|
|||||||
# Multi-stage build for Terraform Registry
|
|
||||||
#
|
|
||||||
# Stage 1: Build Go binary
|
|
||||||
# Stage 2: Minimal Alpine runtime
|
|
||||||
|
|
||||||
FROM golang:1.24-alpine AS builder
|
|
||||||
ENV GOTOOLCHAIN=auto
|
|
||||||
WORKDIR /app
|
|
||||||
COPY server/go.mod server/go.sum ./
|
|
||||||
RUN go mod download
|
|
||||||
COPY server/ .
|
|
||||||
RUN CGO_ENABLED=0 go build -o registry .
|
|
||||||
|
|
||||||
FROM alpine:3.21
|
|
||||||
RUN apk --no-cache add ca-certificates
|
|
||||||
COPY --from=builder /app/registry /registry
|
|
||||||
|
|
||||||
# ── TEMP: reader creds hardcoded for Ceph test ────────────────────────────
|
|
||||||
ENV S3_ENDPOINT=ceph.tst.nubes.ru \
|
|
||||||
S3_BUCKET=terraform-registry \
|
|
||||||
S3_PREFIX=registry.containerk8s.dev.nubes.ru \
|
|
||||||
REGISTRY_HOSTNAME=registry.containerk8s.dev.nubes.ru \
|
|
||||||
S3_USE_SSL=false \
|
|
||||||
PORT=5000 \
|
|
||||||
S3_ACCESS_KEY=AAYTNYSZIM08DDWV3TK1 \
|
|
||||||
S3_SECRET_KEY=xQVd9CeGXACPIXTBTH4KV6DIHEAyySOFSTrS17Lc
|
|
||||||
# ── TODO: remove hardcoded keys, use jsonEnv for new instance ─────────────
|
|
||||||
|
|
||||||
EXPOSE 5000
|
|
||||||
CMD ["/registry"]
|
|
||||||
@@ -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` (можно пересоздать)
|
||||||
@@ -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/...`
|
||||||
@@ -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
|
||||||
|
```
|
||||||
@@ -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).
|
||||||
@@ -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` (сейчас захардкожено).
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
# Code Review Request — Go Terraform Registry
|
|
||||||
|
|
||||||
## Контекст
|
|
||||||
|
|
||||||
Terraform Provider Registry по протоколу HashiCorp. Деплоится как Docker-контейнер (containerk8s managed service в Nubes Cloud). Работает на проде: `https://go-registry.containerk8s.dev.nubes.ru`.
|
|
||||||
|
|
||||||
Хранилище — S3 (MinIO). Провайдеры, SHA256SUMS, GPG-подписи, документация — всё в S3.
|
|
||||||
|
|
||||||
## Файлы для ревью
|
|
||||||
|
|
||||||
- `server/main.go` — HTTP-сервер, S3, все роуты
|
|
||||||
- `server/gpg_key.go` — GPG-ключи (2 штуки, вшиты в код)
|
|
||||||
- `server/docs.go` — статика документации из S3
|
|
||||||
- `Dockerfile` — multi-stage сборка
|
|
||||||
|
|
||||||
## Вопросы
|
|
||||||
|
|
||||||
1. **`downloadVersion`: `defer` внутри `if err == nil`** — defer в Go выполняется при выходе из функции, а не из блока. Правильно ли это здесь? Не приведёт ли к накоплению defer'ов или утечке?
|
|
||||||
|
|
||||||
2. **`readyzHandler`**: комментарий «Проверить доступность S3», но возвращает просто "ok". Стоит ли добавить реальную проверку (head bucket)?
|
|
||||||
|
|
||||||
3. **`listVersions`**: версии не сортируются, порядок зависит от S3 ListObjects. Стоит ли сортировать?
|
|
||||||
|
|
||||||
4. **`proxHandler`**: никаких ограничений на размер файла, таймаутов, rate-limiting. Нормально ли для внутреннего сервиса?
|
|
||||||
|
|
||||||
5. **`rootHandler`**: HTML захардкожен. Вынести в template или embed?
|
|
||||||
|
|
||||||
6. **Graceful shutdown**: нет обработки SIGTERM. Для managed-сервиса это критично?
|
|
||||||
|
|
||||||
7. **`tyCandidateKeys` в docs.go**: индентация сломана (табы/пробелы вперемешку). Это баг форматирования или Go это нормально компилирует?
|
|
||||||
|
|
||||||
8. **GPG-ключи в коде**: стоит ли вынести в файлы/ENV или оставить как есть?
|
|
||||||
|
|
||||||
9. **Dockerfile**: `go mod init` + `go mod tidy` при каждой сборке. Стоит ли добавить `go.sum` в репо для воспроизводимости?
|
|
||||||
|
|
||||||
10. **Общая архитектура**: что можно улучшить? Есть ли проблемы с безопасностью?
|
|
||||||
|
|
||||||
## Как отвечать
|
|
||||||
|
|
||||||
Если что-то неясно — спрашивай. Контекст: сервис внутренний, не публичный, трафик низкий.
|
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
# Code Review — Соннет (2026-08-08)
|
|
||||||
|
|
||||||
## Файлы ревью
|
|
||||||
- `server/main.go` — HTTP, S3, роуты
|
|
||||||
- `server/gpg_key.go` — GPG-ключи
|
|
||||||
- `server/docs.go` — статика из S3
|
|
||||||
- `Dockerfile` — сборка
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. `defer` внутри `if err == nil` — ✅ OK
|
|
||||||
|
|
||||||
```go
|
|
||||||
if err == nil {
|
|
||||||
defer shasumsObj.Close() // выполнится при выходе из ФУНКЦИИ, не из if
|
|
||||||
}
|
|
||||||
```
|
|
||||||
Не утечка. `defer` всегда выполняется при выходе из функции. `shasumsObj` открывается один раз на вызов и закрывается при возврате — правильный паттерн.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. `readyzHandler` без проверки S3 — ⚠️ надо чинить
|
|
||||||
|
|
||||||
```go
|
|
||||||
// Проверить доступность S3 ← комментарий, не реализовано
|
|
||||||
w.Write([]byte("ok"))
|
|
||||||
```
|
|
||||||
K8s readiness probe всегда 200 — даже если S3 недоступен. Pod остаётся в rotation с неработающим S3.
|
|
||||||
|
|
||||||
**Фикс:** `s3Client.BucketExists(ctx, bucketName)`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. Версии не сортируются — ⚠️ надо чинить
|
|
||||||
|
|
||||||
`seenVersions` — map, итерация недетерминирована. Terraform может некорректно выбрать последнюю версию.
|
|
||||||
|
|
||||||
**Фикс:** `sort.Slice` по семверу.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. `proxyHandler` без лимитов — 🔴 КРИТИЧНО
|
|
||||||
|
|
||||||
### 4a. Размер/таймаут — некритично для внутреннего сервиса.
|
|
||||||
|
|
||||||
### 4b. SSRF / Authorization bypass — КРИТИЧНО
|
|
||||||
|
|
||||||
```go
|
|
||||||
bucket := r.URL.Query().Get("bucket") // ← принимает ЛЮБОЙ bucket!
|
|
||||||
key := r.URL.Query().Get("key")
|
|
||||||
```
|
|
||||||
Кто знает URL сервера — может читать объекты из любого бакета в том же S3.
|
|
||||||
|
|
||||||
**Фикс:** захардкодить `bucket = bucketName`, игнорировать параметр `bucket` из запроса. `key` валидировать что начинается с разрешённого префикса.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. HTML хардкод — ✅ OK
|
|
||||||
|
|
||||||
```go
|
|
||||||
fmt.Fprintf(w, `...` + VERSION + `...`)
|
|
||||||
```
|
|
||||||
Для крошечной страницы приемлемо. `VERSION` сейчас `"1.0.2"` — безопасно.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. Graceful Shutdown — ⚠️ желательно
|
|
||||||
|
|
||||||
```go
|
|
||||||
log.Fatal(http.ListenAndServe(":"+port, nil))
|
|
||||||
```
|
|
||||||
SIGTERM убивает процесс мгновенно. In-flight download обрывается → Terraform-клиент теряет кэш.
|
|
||||||
|
|
||||||
**Фикс:**
|
|
||||||
```go
|
|
||||||
srv := &http.Server{Addr: ":" + port}
|
|
||||||
go srv.ListenAndServe()
|
|
||||||
// signal.NotifyContext + srv.Shutdown(ctx)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. Индентация в docs.go — ✅ косметика
|
|
||||||
|
|
||||||
Смешаны табы и пробелы. Go компилирует нормально, но `gofmt -w docs.go` переформатирует полностью. Если CI проверяет `gofmt -l` — упадёт.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 8. GPG-ключи в коде — ✅ OK
|
|
||||||
|
|
||||||
Это **публичные** ключи — их задача быть известными. Смена ключа = rebuild + redeploy — для редкого события приемлемо.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 9. `go mod init` + `go mod tidy` в Dockerfile — 🔴 КРИТИЧНО
|
|
||||||
|
|
||||||
```dockerfile
|
|
||||||
RUN go mod init tf-registry && go mod tidy && ...
|
|
||||||
```
|
|
||||||
Каждая сборка качает зависимости из интернета. Если proxy недоступен или версия изменилась — сборка сломается.
|
|
||||||
|
|
||||||
**Фикс:** добавить `go.mod` + `go.sum` в репо (`server/go.mod`, `server/go.sum`).
|
|
||||||
```dockerfile
|
|
||||||
COPY server/go.mod server/go.sum ./
|
|
||||||
RUN go mod download
|
|
||||||
COPY server/ .
|
|
||||||
RUN CGO_ENABLED=0 go build -o registry .
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 10. Общая архитектура и безопасность
|
|
||||||
|
|
||||||
| Проблема | Серьёзность | Статус |
|
|
||||||
|---|---|---|
|
|
||||||
| `proxyHandler` принимает любой `bucket` | 🔴 Высокая | SSRF — фикс п.4b |
|
|
||||||
| Нет аутентификации | 🟡 Средняя | Внутренний сервис |
|
|
||||||
| `key` не валидируется | 🟡 Средняя | `../` MinIO отклонит, но лучше проверять |
|
|
||||||
| JSON-ошибки не логируются | 🟢 Низкая | `json.NewEncoder` без проверки ошибок |
|
|
||||||
| Нет `go.sum` | 🟡 Средняя | Нерепродуцируемые сборки |
|
|
||||||
| `readyz` всегда 200 | 🟡 Средняя | см. п.2 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Приоритеты к исправлению
|
|
||||||
|
|
||||||
| # | Что | Серьёзность |
|
|
||||||
|---|---|---|
|
|
||||||
| 1 | `proxyHandler` — убрать параметр `bucket` из запроса | 🔴 SSRF |
|
|
||||||
| 2 | `go.sum` в репо | 🔴 Воспроизводимость |
|
|
||||||
| 3 | `readyz` — реальная проверка S3 | 🟡 |
|
|
||||||
| 4 | Сортировка версий | 🟡 |
|
|
||||||
| 5 | Graceful shutdown | 🟡 |
|
|
||||||
| 6 | `gofmt` docs.go | 🟢 |
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
# Переменные инстанса и конфигурация
|
|
||||||
|
|
||||||
## Принцип
|
|
||||||
|
|
||||||
- **jsonEnv** — только секреты. Задаются при создании инстанса, НЕ меняются (modify не поддерживает).
|
|
||||||
- **Dockerfile ENV** — всё несекретное. Меняются через правку Dockerfile → rebuild → push → redeploy.
|
|
||||||
- **Go-код** — `os.Getenv()` с fallback'ами, без хардкода.
|
|
||||||
|
|
||||||
## jsonEnv (создание инстанса)
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"S3_ACCESS_KEY": "...",
|
|
||||||
"S3_SECRET_KEY": "..."
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
| Переменная | Назначение |
|
|
||||||
|---|---|
|
|
||||||
| `S3_ACCESS_KEY` | Доступ к S3 |
|
|
||||||
| `S3_SECRET_KEY` | Доступ к S3 |
|
|
||||||
|
|
||||||
## Dockerfile ENV (rebuild)
|
|
||||||
|
|
||||||
```dockerfile
|
|
||||||
ENV S3_ENDPOINT=s3.msk-1.ngcloud.ru \
|
|
||||||
S3_BUCKET=terraform-registry \
|
|
||||||
S3_PREFIX=go-registry.containerk8s.dev.nubes.ru \
|
|
||||||
REGISTRY_HOSTNAME=go-registry.containerk8s.dev.nubes.ru \
|
|
||||||
PORT=5000
|
|
||||||
```
|
|
||||||
|
|
||||||
| Переменная | Назначение | Пример |
|
|
||||||
|---|---|---|
|
|
||||||
| `S3_ENDPOINT` | Адрес S3 | `s3.msk-1.ngcloud.ru` |
|
|
||||||
| `S3_BUCKET` | Имя бакета | `terraform-registry` |
|
|
||||||
| `S3_PREFIX` | Префикс S3-ключей | `go-registry.containerk8s.dev.nubes.ru` |
|
|
||||||
| `REGISTRY_HOSTNAME` | Домен реестра (для download_url) | `go-registry.containerk8s.dev.nubes.ru` |
|
|
||||||
| `PORT` | Порт HTTP | `5000` |
|
|
||||||
|
|
||||||
## При создании нового инстанса (другой домен/стенд)
|
|
||||||
|
|
||||||
1. В Dockerfile поменять `S3_PREFIX`, `REGISTRY_HOSTNAME` под новый домен
|
|
||||||
2. Поменять `S3_ENDPOINT`, `S3_BUCKET` если S3 другой
|
|
||||||
3. `docker build && docker push`
|
|
||||||
4. Создать инстанс: `registryPath` → образ, `jsonEnv` → ключи S3
|
|
||||||
|
|
||||||
## Смена окружения
|
|
||||||
|
|
||||||
Не менять инстанс — создать новый с другими параметрами. Старый удалить.
|
|
||||||
|
|
||||||
| Окружение | Домен | S3_PREFIX |
|
|
||||||
|---|---|---|
|
|
||||||
| Тест | `go-registry.containerk8s.dev.nubes.ru` | `go-registry.containerk8s.dev.nubes.ru` |
|
|
||||||
| Прод | (будет отдельно) | (будет отдельно) |
|
|
||||||
@@ -8,10 +8,10 @@ metadata:
|
|||||||
spec:
|
spec:
|
||||||
tls:
|
tls:
|
||||||
- hosts:
|
- hosts:
|
||||||
- terr.kube5s.ru
|
- registry.containerk8s.dev.nubes.ru
|
||||||
secretName: terr-kube5s-ru-tls
|
secretName: registry-containerk8s-tls
|
||||||
rules:
|
rules:
|
||||||
- host: terr.kube5s.ru
|
- host: registry.containerk8s.dev.nubes.ru
|
||||||
http:
|
http:
|
||||||
paths:
|
paths:
|
||||||
- path: /
|
- path: /
|
||||||
|
|||||||
+84
-84
@@ -16,101 +16,101 @@ import (
|
|||||||
// parseDocsRequestPath parses paths like:
|
// parseDocsRequestPath parses paths like:
|
||||||
// /docs/<namespace>/<name>/<version>/... (rest may be empty)
|
// /docs/<namespace>/<name>/<version>/... (rest may be empty)
|
||||||
func parseDocsRequestPath(p string) (namespace, name, version, rest string, err error) {
|
func parseDocsRequestPath(p string) (namespace, name, version, rest string, err error) {
|
||||||
p = strings.TrimPrefix(p, "/")
|
p = strings.TrimPrefix(p, "/")
|
||||||
p = strings.TrimPrefix(p, "docs/")
|
p = strings.TrimPrefix(p, "docs/")
|
||||||
parts := strings.SplitN(p, "/", 4)
|
parts := strings.SplitN(p, "/", 4)
|
||||||
if len(parts) < 3 {
|
if len(parts) < 3 {
|
||||||
err = fmt.Errorf("invalid docs path: %s", p)
|
err = fmt.Errorf("invalid docs path: %s", p)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
namespace = parts[0]
|
namespace = parts[0]
|
||||||
name = parts[1]
|
name = parts[1]
|
||||||
version = parts[2]
|
version = parts[2]
|
||||||
if len(parts) == 3 {
|
if len(parts) == 3 {
|
||||||
rest = ""
|
rest = ""
|
||||||
} else {
|
} else {
|
||||||
rest = parts[3]
|
rest = parts[3]
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// docsObjectKey builds the S3 key for a docs object given parsed parts.
|
// docsObjectKey builds the S3 key for a docs object given parsed parts.
|
||||||
func docsObjectKey(namespace, name, version, pathPart string) string {
|
func docsObjectKey(namespace, name, version, pathPart string) string {
|
||||||
clean := strings.TrimPrefix(pathPart, "/")
|
clean := strings.TrimPrefix(pathPart, "/")
|
||||||
if clean == "" {
|
if clean == "" {
|
||||||
return fmt.Sprintf("docs/%s/%s/%s/index.html", namespace, name, version)
|
return fmt.Sprintf("docs/%s/%s/%s/index.html", namespace, name, version)
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("docs/%s/%s/%s/%s", namespace, name, version, clean)
|
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.
|
// tryCandidateKeys returns a list of keys to attempt for a given request path.
|
||||||
// Order matters: exact path first, then <path>/index.html, then top-level index.
|
// Order matters: exact path first, then <path>/index.html, then top-level index.
|
||||||
func tryCandidateKeys(namespace, name, version, rest string) []string {
|
func tryCandidateKeys(namespace, name, version, rest string) []string {
|
||||||
keys := []string{}
|
keys := []string{}
|
||||||
if rest == "" {
|
if rest == "" {
|
||||||
keys = append(keys, docsObjectKey(namespace, name, version, "index.html"))
|
keys = append(keys, docsObjectKey(namespace, name, version, "index.html"))
|
||||||
return keys
|
return keys
|
||||||
}
|
}
|
||||||
// exact
|
// exact
|
||||||
keys = append(keys, docsObjectKey(namespace, name, version, rest))
|
keys = append(keys, docsObjectKey(namespace, name, version, rest))
|
||||||
// if it looks like a directory or has no extension, try index under it
|
// if it looks like a directory or has no extension, try index under it
|
||||||
if strings.HasSuffix(rest, "/") || filepath.Ext(rest) == "" {
|
if strings.HasSuffix(rest, "/") || filepath.Ext(rest) == "" {
|
||||||
keys = append(keys, docsObjectKey(namespace, name, version, strings.TrimSuffix(rest, "/")+"/index.html"))
|
keys = append(keys, docsObjectKey(namespace, name, version, strings.TrimSuffix(rest, "/")+"/index.html"))
|
||||||
}
|
}
|
||||||
// finally, try root index
|
// finally, try root index
|
||||||
keys = append(keys, docsObjectKey(namespace, name, version, "index.html"))
|
keys = append(keys, docsObjectKey(namespace, name, version, "index.html"))
|
||||||
return keys
|
return keys
|
||||||
}
|
}
|
||||||
|
|
||||||
// docsHandler serves static documentation files from S3 (public-facing via Ingress).
|
// docsHandler serves static documentation files from S3 (public-facing via Ingress).
|
||||||
func docsHandler(w http.ResponseWriter, r *http.Request) {
|
func docsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ns, name, ver, rest, err := parseDocsRequestPath(r.URL.Path)
|
ns, name, ver, rest, err := parseDocsRequestPath(r.URL.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Bad docs path", http.StatusBadRequest)
|
http.Error(w, "Bad docs path", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
candidates := tryCandidateKeys(ns, name, ver, rest)
|
candidates := tryCandidateKeys(ns, name, ver, rest)
|
||||||
log.Printf("Docs candidates (host=%s): %v", hostname, candidates)
|
log.Printf("Docs candidates (host=%s): %v", hostname, candidates)
|
||||||
|
|
||||||
var lastErr error
|
var lastErr error
|
||||||
for _, key := range candidates {
|
for _, key := range candidates {
|
||||||
obj, err := s3Client.GetObject(ctx, bucketName, key, s3.GetObjectOptions{})
|
obj, err := s3Client.GetObject(ctx, bucketName, key, s3.GetObjectOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lastErr = err
|
lastErr = err
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
stat, err := obj.Stat()
|
stat, err := obj.Stat()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lastErr = err
|
lastErr = err
|
||||||
_ = obj.Close()
|
_ = obj.Close()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine content-type
|
// Determine content-type
|
||||||
ext := filepath.Ext(key)
|
ext := filepath.Ext(key)
|
||||||
ctype := mime.TypeByExtension(ext)
|
ctype := mime.TypeByExtension(ext)
|
||||||
if ctype == "" {
|
if ctype == "" {
|
||||||
// fallback for HTML
|
// fallback for HTML
|
||||||
if ext == ".html" || strings.HasSuffix(key, "index.html") {
|
if ext == ".html" || strings.HasSuffix(key, "index.html") {
|
||||||
ctype = "text/html; charset=utf-8"
|
ctype = "text/html; charset=utf-8"
|
||||||
} else {
|
} else {
|
||||||
ctype = "application/octet-stream"
|
ctype = "application/octet-stream"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", ctype)
|
w.Header().Set("Content-Type", ctype)
|
||||||
w.Header().Set("Content-Length", fmt.Sprintf("%d", stat.Size))
|
w.Header().Set("Content-Length", fmt.Sprintf("%d", stat.Size))
|
||||||
w.Header().Set("Last-Modified", stat.LastModified.Format(http.TimeFormat))
|
w.Header().Set("Last-Modified", stat.LastModified.Format(http.TimeFormat))
|
||||||
|
|
||||||
if _, err := io.Copy(w, obj); err != nil {
|
if _, err := io.Copy(w, obj); err != nil {
|
||||||
log.Printf("Error streaming object %s: %v", key, err)
|
log.Printf("Error streaming object %s: %v", key, err)
|
||||||
}
|
}
|
||||||
_ = obj.Close()
|
_ = obj.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Docs not found in candidates: %v, lastErr: %v", candidates, lastErr)
|
log.Printf("Docs not found in candidates: %v, lastErr: %v", candidates, lastErr)
|
||||||
http.Error(w, "Documentation not found", http.StatusNotFound)
|
http.Error(w, "Documentation not found", http.StatusNotFound)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
module tf-registry
|
|
||||||
|
|
||||||
go 1.25.0
|
|
||||||
|
|
||||||
require github.com/minio/minio-go/v7 v7.2.1
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
|
||||||
github.com/klauspost/compress v1.18.6 // indirect
|
|
||||||
github.com/klauspost/cpuid/v2 v2.2.11 // indirect
|
|
||||||
github.com/klauspost/crc32 v1.3.0 // 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/rs/xid v1.6.0 // indirect
|
|
||||||
github.com/tinylib/msgp v1.6.1 // indirect
|
|
||||||
github.com/zeebo/xxh3 v1.1.0 // indirect
|
|
||||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
|
||||||
golang.org/x/crypto v0.51.0 // indirect
|
|
||||||
golang.org/x/net v0.53.0 // indirect
|
|
||||||
golang.org/x/sys v0.44.0 // indirect
|
|
||||||
golang.org/x/text v0.37.0 // indirect
|
|
||||||
gopkg.in/ini.v1 v1.67.2 // indirect
|
|
||||||
)
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
|
||||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
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/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.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao=
|
|
||||||
github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
|
||||||
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/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.2.1 h1:PfBfwvKB/MmqyN8Vb1G9voWisaM9OrLv+WwOvMwS9Dw=
|
|
||||||
github.com/minio/minio-go/v7 v7.2.1/go.mod h1:EU9hENAStx/xXduNdrGO5e4X5vk19NtgB+RIPjZO8o0=
|
|
||||||
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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
|
||||||
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/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
|
||||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
|
||||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
|
||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
|
||||||
github.com/tinylib/msgp v1.6.1 h1:ESRv8eL3u+DNHUoSAAQRE50Hm162zqAnBoGv9PzScPY=
|
|
||||||
github.com/tinylib/msgp v1.6.1/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA=
|
|
||||||
github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
|
|
||||||
github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
|
|
||||||
github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
|
|
||||||
github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
|
|
||||||
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.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI=
|
|
||||||
golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8=
|
|
||||||
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
|
|
||||||
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
|
|
||||||
golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
|
|
||||||
golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
|
||||||
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
|
||||||
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/ini.v1 v1.67.2 h1:JtOSMb9OuaCZKr7h5D/h6iii14sK0hLbplTc6frx4Ss=
|
|
||||||
gopkg.in/ini.v1 v1.67.2/go.mod h1:x/cyOwCgZqOkJoDIJ3c1KNHMo10+nLGAhh+kn3Zizss=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
+4
-125
@@ -1,129 +1,8 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
// GPG public keys from live deployment (two keys: primary + legacy).
|
import registrykeys "terraform-registry-keys"
|
||||||
// Primary key is used by the current operator-built binary.
|
|
||||||
|
|
||||||
var gpgPrimaryKey = GPGPublicKey{
|
var gpgPublicKey = GPGPublicKey{
|
||||||
KeyID: "3EC4673EB798238A",
|
KeyID: registrykeys.KeyID,
|
||||||
ASCIIArmor: `-----BEGIN PGP PUBLIC KEY BLOCK-----
|
ASCIIArmor: registrykeys.ASCIIArmor,
|
||||||
|
|
||||||
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
|
|
||||||
6cZ0NQ==
|
|
||||||
=3Ea2
|
|
||||||
-----END PGP PUBLIC KEY BLOCK-----`,
|
|
||||||
}
|
|
||||||
|
|
||||||
var gpgLegacyKey = GPGPublicKey{
|
|
||||||
KeyID: "CB3A0DF161ECC416",
|
|
||||||
ASCIIArmor: `-----BEGIN PGP PUBLIC KEY BLOCK-----
|
|
||||||
|
|
||||||
mQINBGmIuRcBEADM19WQhGlAdIuS8+KINFnD7F8dJMO6MQUsM6UHOKQ2wdVtacFn
|
|
||||||
d8/MUlUk3OdTmmzVlg6Zoc6/bsvHA4XUg5MZoqeSZGrffuv6fOEc9afDGId3fjB8
|
|
||||||
TBku0JFIG+94oveXKq3vA4ZYHz2ZkKrRng/Ad918178wsd8lS7uwoziS/9LvQTuV
|
|
||||||
TvaPodYOgy65NQnwwWm88NCcDxq0Yvot6T38hhanthmEEhQ4R0lf4pM19DNDvtUu
|
|
||||||
YI7v9P/+ODsLCZwVyj5vbxtrDHJN2smREvgxMB8pCz/UHVJl2DrPRrGe9K31Vpuy
|
|
||||||
OB2VfZ75kVbaruUzmGEfG6dVPEyxDur4dQI191CPWYMjZ95pTcAlBdJsPZmdkgtZ
|
|
||||||
U0t+mZzfsMOemvD1lWxH/rOIPSPGmiSC6h11I++bsC+sMwS6xPpCAwopdGAIgEQd
|
|
||||||
Pd2mFm8QswvQreorYjrHMvliPo7Z0/nDRKAL6ZeE5jQ9GjSxHFxfJim+0jfXEx5G
|
|
||||||
gZ7bufQn9phnrT4wxOywkVdVP0gI+fhC0NAJwHDpdGsiukUbA2LGah0mHer576IJ
|
|
||||||
TYlhLWl70SaFxLTOLSn2Tc4KMhyAnLPbswy91eEcHUZdghCkbqEmlaAhAPMaY3rM
|
|
||||||
qm2Q7P07aO7p5rZGYGYo7GTpgIMQ72Rai6FBVHeC7lnE35vO110y5eOfUQARAQAB
|
|
||||||
tB90YXpldEBuYXJvZC5ydSA8dGF6ZXRAbmFyb2QucnU+iQJSBBMBCgA8FiEELZMv
|
|
||||||
dnDJHz/m0s40yzoN8WHsxBYFAmmIuRcDGy8EBQsJCAcCAiICBhUKCQgLAgQWAgMB
|
|
||||||
Ah4HAheAAAoJEMs6DfFh7MQWvHIP+wZjgWwpnjP0u1LBBrTOJ194MLbtfjfG9bM7
|
|
||||||
/LSNYYy8cuQapS4Vf5H8mYUf/D3dBJFUqpaVhQ3PyppeYxJLe5J9feifEmoJ4LQ7
|
|
||||||
rCphn6tmx5mi8pjEyDVzIp+G1cTL1JPI7LBnQdKbvfuAkJnJaELxhICuCKe5Pm9v
|
|
||||||
mvm4N9w1K5nq+t5VBd1J8FRm6fk0W+bUYdcsvdcV0hWeXJwoH+11nC+5UWs8K99R
|
|
||||||
eX5SEVkXiN/UHKnDHh29X3dNv0MV1T0u2rBesXZNph8qGj6c623LGwIbB1BGt09+
|
|
||||||
m0Ya+gMQ1i0RPt043xuinQczSCLp0qsoRSHS3fB6qL3a4W/PANh/tDBpzT8VFqPt
|
|
||||||
SFwDXA+x/iK15ZGNpyv1mTz/Usyyb5K16sDDwbek3P9Vd7R8RlJuyjBEseMJLMzu
|
|
||||||
85fqzVF5QLs0PsHF5r58xb83ACcO3aQYb7NRTcmJajIkCWHOV2osvjNXLtpfJjeQ
|
|
||||||
KNofVKyCFpWOawj38AN6hX6OIm1IH2ilbtFda14Y3Nc3p7h3BtRZAQ92els9fwi2
|
|
||||||
WIXip6WieKcpOp54NUA0A0TizELPtZoVIvuMbzJ8ZI9y0VSqhXlvH52lcZft6Jmd
|
|
||||||
2t2AxxmphA6tMBf3QSGP8GBH5DSEyPmobzXdbt9NQoukozOxpafotNlxTLobedAn
|
|
||||||
Avs50MFhuQINBGmIuRcBEACz4fOjeZ7MUAATenxMhTPng30ytz+d+IdNY0woB//8
|
|
||||||
Ksy7VysggZ0vi++hAQSrA0/W0Wqo3tGfms/7wM1f/LJvCNC/Cz+OwdqQghMgxhBO
|
|
||||||
hKqlyAVc5ei7R8Q9/QdTyQj0EkFp3+MEnuVScta4ZMFhfyCeKQTtcWRbTZNt32dc
|
|
||||||
Qy1LVG12EGzuW9n5b49XBKjPsKKcUO2MLvOucusY7uufAl2msjuETQBaCaBz+1a0
|
|
||||||
u1UsCAzNc5hvPrlPRoK2JG5Rj8FAcWycblAvWuG4c1iOBMWx3O2E8SAWeoxrHh5S
|
|
||||||
nzfzAZY6GFcDJu4TMOCFt7EIPWl6dwk3HlGnATm9zYRM+m/eDcgEbsx0vK74tuAP
|
|
||||||
iC73J8FOR2bmkjPsSIMv85/JQ29HrXHXTDm+77AFBEvHbjaAflpdBE7c5iKVmz96
|
|
||||||
FlUceniDMyRnfiWZ5w4RJ0QpI7Eveo4wUbKTT+9eA+hobEIFxs1K6Dk67yrLiOKx
|
|
||||||
hpgNQrYqzKKqhaU1IuYuk9a8TgD/ZQouj80JMhhbkPshP8zZuaik5IEYgL3JXiws
|
|
||||||
aAn1lZGbXActJU8Xu0aI4Hr/UY4c9AN+qAkLjqJxej/UoPnMH5byPU4aVRkYYFFK
|
|
||||||
SkJJJXznYOgUzA/8x7Hoaf5P4vifJbWj4Pe+kI1+PDo7wlcWFCIEIeGKxaKpH4an
|
|
||||||
SwARAQABiQRsBBgBCgAgFiEELZMvdnDJHz/m0s40yzoN8WHsxBYFAmmIuRcCGy4C
|
|
||||||
QAkQyzoN8WHsxBbBdCAEGQEKAB0WIQS345MoCgP1wvYg7MH52VVM4R+29wUCaYi5
|
|
||||||
FwAKCRD52VVM4R+2901iD/0R3Eai/B+9iPcYRDq8c3LPHakcr0E4EJolsMa4IU/N
|
|
||||||
VNMQsq5jt7Wf15POGzFs8+TKUj76u0WdOGkzbDiXpt376AqohdocEtgA+xrc8j4O
|
|
||||||
45dNJpOfzaxWVjKkKDMKTsTlbbR/+Wkk1S0R0M3N59lZ7j2u+hNS+Hc72DmUsU+0
|
|
||||||
1oNyNelARnetSIgtz8eCJXD484vpKwOAorZJt84hT4Bj+h1S/R6tMNWq8DgbarPG
|
|
||||||
iV4xIyauEuM4sxM/Mmx80KVvhEyQTUf29EVo/J6ntetL0aZB/IowhC4jVdOnxbvJ
|
|
||||||
MSnCEHD2Bg58zBsUy+WdfUu16TncRRGmjAF3H+nThhZcojn/1F26kehD9nC/cGWI
|
|
||||||
WoGLNzRS1A4z7b4kYLM5d1QrqfM0omPwQmP85nFAJMDtHPevyHTdUWDmC6sHSBBS
|
|
||||||
/Ya0F5gGDZ4MPGKrNWAq55Af48YOAfV85Jact7DqnbN4kM2MosDromGl9Ts7pR4+
|
|
||||||
djbXGDvO+gadMK94Bc2EnL/UUVtcJ7yMzRjMtQlCXCkHvKXOf3Q5sQ9UEjDcaq55
|
|
||||||
zzwNZXVo24F1QvzGG+soDcPTGJzn1pBb70qd/SOZbtGkyZ4nw46xInR/EkxX4gLv
|
|
||||||
fEjwqa064x2LV1JSwowikIDwz0KBC4AIOhc1p0B64qUMozwx7Wfl41j9e2ElKN2q
|
|
||||||
NFKrD/0ZzCijpVIgerA0ub9J/D8x9zR4oyn8z8rsaL56duQB6MKvxcvoSk7hky17
|
|
||||||
WtRvCswaza7r4zqDGp346sRJU3f6Aww3WMlP4KNAEy79QZYRfzaT3EIvdZ8yssJF
|
|
||||||
ND4YuXEz6JnbivXBqRESFdMe1N3YHmmeh5nagDQE9afyPepr2mgGm8Uzy1/Wn2RN
|
|
||||||
Idk/2CpS9Ed/68Cma9goJFp0xZi6PN3ohpUqURAg6GD7RgInhTj8iqErvuWAqIws
|
|
||||||
z2sfdyhc1yF+/NXcOChcPnWU55AUA5lAwy8JZqy9kclGDvKZERb+nSZx2XZLU1TQ
|
|
||||||
JLrdkMYCkz1oCf2pyTb6+JtNUcb54QkD8ycjXPGraH1fCZHmZ6AqrlEdTn3feoTG
|
|
||||||
QNrPz8rcUCmVMQHQOVAEstrw/C6Rg3VLz4fy1YnbLHQBnpLjg08s5gnJt/h4uDCb
|
|
||||||
qQq93sphV3AZrr5+GGWBr4QtKQ4vCo8OVeF0IX5njpNxVWbFKfjAqUdUK4UkPNJi
|
|
||||||
H8akc95rm+3hcG4A1LU/76n+FJnVdLcfsK/GpdNpGUCD9MxZdwEoRD/mDr/Z7loz
|
|
||||||
Ig4Ny9ck/NUvk8bGFRylrzds7CfySZMvxayWi7Tqmo4aU7V17FB8/wlAb0EPTr4v
|
|
||||||
cZTgF5h8IiOJN4ZCklRNkOvcvKRx8OFctPtyiYNhJ+Eer36StQ==
|
|
||||||
=q8Tw
|
|
||||||
-----END PGP PUBLIC KEY BLOCK-----`,
|
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-64
@@ -10,11 +10,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
s3 "github.com/minio/minio-go/v7"
|
s3 "github.com/minio/minio-go/v7"
|
||||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||||
@@ -24,11 +20,8 @@ var (
|
|||||||
s3Client *s3.Client
|
s3Client *s3.Client
|
||||||
bucketName = "terraform-registry" // Default
|
bucketName = "terraform-registry" // Default
|
||||||
hostname = os.Getenv("REGISTRY_HOSTNAME")
|
hostname = os.Getenv("REGISTRY_HOSTNAME")
|
||||||
s3Prefix = os.Getenv("S3_PREFIX") // S3 key prefix (may differ from hostname)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const VERSION = "1.0.5"
|
|
||||||
|
|
||||||
// Terraform Registry Protocol Structs
|
// Terraform Registry Protocol Structs
|
||||||
type Discovery struct {
|
type Discovery struct {
|
||||||
ProvidersV1 string `json:"providers.v1"`
|
ProvidersV1 string `json:"providers.v1"`
|
||||||
@@ -75,11 +68,7 @@ type GPGPublicKey struct {
|
|||||||
func main() {
|
func main() {
|
||||||
// 1. Init S3 Connection
|
// 1. Init S3 Connection
|
||||||
if hostname == "" {
|
if hostname == "" {
|
||||||
hostname = "localhost:5000"
|
hostname = "localhost:8080"
|
||||||
}
|
|
||||||
// S3_PREFIX default comes from Dockerfile ENV; override in jsonEnv if needed.
|
|
||||||
if s3Prefix == "" {
|
|
||||||
s3Prefix = hostname
|
|
||||||
}
|
}
|
||||||
|
|
||||||
endpoint := os.Getenv("S3_ENDPOINT")
|
endpoint := os.Getenv("S3_ENDPOINT")
|
||||||
@@ -91,10 +80,9 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
useSSL := os.Getenv("S3_USE_SSL") != "false"
|
|
||||||
s3Client, err = s3.New(endpoint, &s3.Options{
|
s3Client, err = s3.New(endpoint, &s3.Options{
|
||||||
Creds: credentials.NewStaticV4(accessKeyID, secretAccessKey, ""),
|
Creds: credentials.NewStaticV4(accessKeyID, secretAccessKey, ""),
|
||||||
Secure: useSSL,
|
Secure: true, // Force secure for cloud S3
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
log.Fatalln(err)
|
||||||
@@ -109,31 +97,8 @@ func main() {
|
|||||||
http.HandleFunc("/healthz", healthzHandler)
|
http.HandleFunc("/healthz", healthzHandler)
|
||||||
http.HandleFunc("/readyz", readyzHandler)
|
http.HandleFunc("/readyz", readyzHandler)
|
||||||
|
|
||||||
port := os.Getenv("PORT")
|
log.Printf("Starting Registry Service on :8080 (Bucket: %s, Endpoint: %s)\n", bucketName, endpoint)
|
||||||
if port == "" {
|
log.Fatal(http.ListenAndServe(":8080", nil))
|
||||||
port = "5000"
|
|
||||||
}
|
|
||||||
|
|
||||||
srv := &http.Server{Addr: ":" + port}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
log.Printf("Starting Registry Service on :%s (Bucket: %s, Endpoint: %s)\n", port, bucketName, endpoint)
|
|
||||||
if err := srv.ListenAndServe(); err != http.ErrServerClosed {
|
|
||||||
log.Fatalf("HTTP server error: %v", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
quit := make(chan os.Signal, 1)
|
|
||||||
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
|
||||||
<-quit
|
|
||||||
log.Println("Shutting down server...")
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
if err := srv.Shutdown(ctx); err != nil {
|
|
||||||
log.Fatalf("Server forced to shutdown: %v", err)
|
|
||||||
}
|
|
||||||
log.Println("Server stopped")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func rootHandler(w http.ResponseWriter, r *http.Request) {
|
func rootHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
@@ -142,12 +107,12 @@ func rootHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
fmt.Fprintf(w, `
|
fmt.Fprint(w, `
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head><title>Terra Registry v`+VERSION+`</title></head>
|
<head><title>Terra Registry</title></head>
|
||||||
<body>
|
<body>
|
||||||
<h1>Terra Registry & Documentation Server v`+VERSION+`</h1>
|
<h1>Terra Registry & Documentation Server</h1>
|
||||||
<p>Status: <span style="color: green">ONLINE</span></p>
|
<p>Status: <span style="color: green">ONLINE</span></p>
|
||||||
<hr>
|
<hr>
|
||||||
<p>Powered by Nubes Cloud S3 Storage</p>
|
<p>Powered by Nubes Cloud S3 Storage</p>
|
||||||
@@ -157,16 +122,17 @@ func rootHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func proxyHandler(w http.ResponseWriter, r *http.Request) {
|
func proxyHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
bucket := r.URL.Query().Get("bucket")
|
||||||
key := r.URL.Query().Get("key")
|
key := r.URL.Query().Get("key")
|
||||||
|
|
||||||
if key == "" {
|
if bucket == "" || key == "" {
|
||||||
http.Error(w, "Missing key param", http.StatusBadRequest)
|
http.Error(w, "Missing bucket or key params", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
obj, err := s3Client.GetObject(context.Background(), bucketName, key, s3.GetObjectOptions{})
|
obj, err := s3Client.GetObject(context.Background(), bucket, key, s3.GetObjectOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error getting object %s/%s: %v", bucketName, key, err)
|
log.Printf("Error getting object %s/%s: %v", bucket, key, err)
|
||||||
http.Error(w, "File not found", http.StatusNotFound)
|
http.Error(w, "File not found", http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -174,7 +140,7 @@ func proxyHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
stat, err := obj.Stat()
|
stat, err := obj.Stat()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error stating object %s/%s: %v", bucketName, key, err)
|
log.Printf("Error stating object %s/%s: %v", bucket, key, err)
|
||||||
http.Error(w, "File not found or not accessible", http.StatusNotFound)
|
http.Error(w, "File not found or not accessible", http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -211,7 +177,7 @@ func router(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func listVersions(w http.ResponseWriter, r *http.Request, namespace, pType string) {
|
func listVersions(w http.ResponseWriter, r *http.Request, namespace, pType string) {
|
||||||
prefix := fmt.Sprintf("%s/%s/%s/", s3Prefix, namespace, pType)
|
prefix := fmt.Sprintf("%s/%s/%s/", hostname, namespace, pType)
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
versions := []Version{}
|
versions := []Version{}
|
||||||
seenVersions := map[string]*Version{}
|
seenVersions := map[string]*Version{}
|
||||||
@@ -240,9 +206,6 @@ func listVersions(w http.ResponseWriter, r *http.Request, namespace, pType strin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.Contains(fileName, "_darwin_amd64.zip") {
|
|
||||||
seenVersions[verStr].Platforms = append(seenVersions[verStr].Platforms, Platform{OS: "darwin", Arch: "amd64"})
|
|
||||||
}
|
|
||||||
if strings.Contains(fileName, "_linux_amd64.zip") {
|
if strings.Contains(fileName, "_linux_amd64.zip") {
|
||||||
seenVersions[verStr].Platforms = append(seenVersions[verStr].Platforms, Platform{OS: "linux", Arch: "amd64"})
|
seenVersions[verStr].Platforms = append(seenVersions[verStr].Platforms, Platform{OS: "linux", Arch: "amd64"})
|
||||||
}
|
}
|
||||||
@@ -255,10 +218,6 @@ func listVersions(w http.ResponseWriter, r *http.Request, namespace, pType strin
|
|||||||
versions = append(versions, *v)
|
versions = append(versions, *v)
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Slice(versions, func(i, j int) bool {
|
|
||||||
return versions[i].Version < versions[j].Version
|
|
||||||
})
|
|
||||||
|
|
||||||
resp := VersionList{
|
resp := VersionList{
|
||||||
ID: fmt.Sprintf("%s/%s", namespace, pType),
|
ID: fmt.Sprintf("%s/%s", namespace, pType),
|
||||||
Versions: versions,
|
Versions: versions,
|
||||||
@@ -269,7 +228,7 @@ func listVersions(w http.ResponseWriter, r *http.Request, namespace, pType strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
func downloadVersion(w http.ResponseWriter, r *http.Request, namespace, pType, version, osType, arch string) {
|
func downloadVersion(w http.ResponseWriter, r *http.Request, namespace, pType, version, osType, arch string) {
|
||||||
basePath := fmt.Sprintf("%s/%s/%s/%s", s3Prefix, namespace, pType, version)
|
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)
|
filename := fmt.Sprintf("terraform-provider-%s_%s_%s_%s.zip", pType, version, osType, arch)
|
||||||
fullKey := fmt.Sprintf("%s/%s", basePath, filename)
|
fullKey := fmt.Sprintf("%s/%s", basePath, filename)
|
||||||
shasumsKey := fmt.Sprintf("%s/terraform-provider-%s_%s_SHA256SUMS", basePath, pType, version)
|
shasumsKey := fmt.Sprintf("%s/terraform-provider-%s_%s_SHA256SUMS", basePath, pType, version)
|
||||||
@@ -297,6 +256,8 @@ func downloadVersion(w http.ResponseWriter, r *http.Request, namespace, pType, v
|
|||||||
shasumsLink := fmt.Sprintf("%s/v1/proxy?bucket=%s&key=%s", baseURL, bucketName, url.QueryEscape(shasumsKey))
|
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))
|
sigLink := fmt.Sprintf("%s/v1/proxy?bucket=%s&key=%s", baseURL, bucketName, url.QueryEscape(sigKey))
|
||||||
|
|
||||||
|
gpgKey := gpgPublicKey
|
||||||
|
|
||||||
resp := DownloadResponse{
|
resp := DownloadResponse{
|
||||||
Protocols: []string{"5.0"},
|
Protocols: []string{"5.0"},
|
||||||
OS: osType,
|
OS: osType,
|
||||||
@@ -307,7 +268,7 @@ func downloadVersion(w http.ResponseWriter, r *http.Request, namespace, pType, v
|
|||||||
ShasumsSignatureURL: sigLink,
|
ShasumsSignatureURL: sigLink,
|
||||||
Shasum: shasumValue,
|
Shasum: shasumValue,
|
||||||
SigningKeys: SigningKeys{
|
SigningKeys: SigningKeys{
|
||||||
GPGPublicKeys: []GPGPublicKey{gpgPrimaryKey, gpgLegacyKey},
|
GPGPublicKeys: []GPGPublicKey{gpgKey},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -321,13 +282,7 @@ func healthzHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func readyzHandler(w http.ResponseWriter, r *http.Request) {
|
func readyzHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
_, err := s3Client.BucketExists(context.Background(), bucketName)
|
// Проверить доступность S3
|
||||||
if err != nil {
|
|
||||||
log.Printf("readyz: S3 bucket check failed: %v", err)
|
|
||||||
w.WriteHeader(http.StatusServiceUnavailable)
|
|
||||||
w.Write([]byte("s3 unreachable"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
w.Write([]byte("ok"))
|
w.Write([]byte("ok"))
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
+1
-1
@@ -13,7 +13,7 @@ from botocore.config import Config
|
|||||||
# Config (from ENV, matching Go defaults)
|
# Config (from ENV, matching Go defaults)
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
HOSTNAME = os.getenv("REGISTRY_HOSTNAME", "localhost:8080")
|
HOSTNAME = os.getenv("REGISTRY_HOSTNAME", "localhost:8080")
|
||||||
S3_PREFIX = os.getenv("S3_PREFIX", "go-registry.containerk8s.dev.nubes.ru")
|
S3_PREFIX = os.getenv("S3_PREFIX", "registry.containerk8s.dev.nubes.ru")
|
||||||
S3_ENDPOINT = os.getenv("S3_ENDPOINT", "")
|
S3_ENDPOINT = os.getenv("S3_ENDPOINT", "")
|
||||||
S3_ACCESS_KEY = os.getenv("S3_ACCESS_KEY", "")
|
S3_ACCESS_KEY = os.getenv("S3_ACCESS_KEY", "")
|
||||||
S3_SECRET_KEY = os.getenv("S3_SECRET_KEY", "")
|
S3_SECRET_KEY = os.getenv("S3_SECRET_KEY", "")
|
||||||
|
|||||||
Executable
+87
@@ -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://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
|
||||||
Reference in New Issue
Block a user