Author SHA1 Message Date
“Naeel” 6ec580c616 v1.0.5 — fix: S3_PREFIX/REGISTRY_HOSTNAME → registry.containerk8s.dev.nubes.ru 2026-08-08 23:15:34 +04:00
“Naeel” 86c4a1f25c v1.0.4 — Ceph reader creds (hardcode), S3_USE_SSL ENV 2026-08-08 22:47:39 +04:00
“Naeel” 13f8ce8be6 config: +REGISTRY_HOSTNAME в Dockerfile ENV, doc: env-config.md 2026-08-08 22:30:21 +04:00
“Naeel” e87eab4df9 v1.0.3 — fix: bucket → bucketName в логах proxyHandler 2026-08-08 22:03:53 +04:00
“Naeel” da03bd023b v1.0.3 — ревью Соннета: SSRF fix, go.sum, readyz, sort, graceful shutdown, gofmt 2026-08-08 22:02:41 +04:00
“Naeel” 4c10a3b836 doc: результаты код-ревью Соннета (10 пунктов, приоритеты) 2026-08-08 21:59:23 +04:00
“Naeel” f10f2d3998 doc: запрос на код-ревью для Соннета 2026-08-08 21:55:23 +04:00
“Naeel” b9c47765ac v1.0.2 — fix S3_PREFIX, убраны все kube5s из кода 2026-08-07 08:38:43 +04:00
“Naeel” a9cfea16e9 v1.0.1 — bump version 2026-08-07 08:07:27 +04:00
“Naeel” 91472e1259 go-container: ENV в Dockerfile, без хардкода в Go 2026-08-07 07:57:53 +04:00
“Naeel” 35a07ca374 go-container: хардкод S3_PREFIX c TODO-комментарием 2026-08-06 21:40:28 +04:00
“Naeel” 8110aad493 go-container: S3_PREFIX default=registry.kube5s.ru (без ENV) 2026-08-06 21:36:46 +04:00
“Naeel” e19bb6f290 go-container: S3_PREFIX, VERSION на главной 2026-08-06 21:33:41 +04:00
“Naeel” a0e72b1c7f go-container: порт 5000, GPG 2 ключа, darwin, Dockerfile 2026-08-06 21:07:21 +04:00
10 changed files with 612 additions and 101 deletions
+30
View File
@@ -0,0 +1,30 @@
# 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"]
+40
View File
@@ -0,0 +1,40 @@
# 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. **Общая архитектура**: что можно улучшить? Есть ли проблемы с безопасностью?
## Как отвечать
Если что-то неясно — спрашивай. Контекст: сервис внутренний, не публичный, трафик низкий.
+134
View File
@@ -0,0 +1,134 @@
# 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 | 🟢 |
+55
View File
@@ -0,0 +1,55 @@
# Переменные инстанса и конфигурация
## Принцип
- **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` |
| Прод | (будет отдельно) | (будет отдельно) |
+26
View File
@@ -0,0 +1,26 @@
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
)
+60
View File
@@ -0,0 +1,60 @@
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=
+125 -4
View File
@@ -1,8 +1,129 @@
package main
import registrykeys "terraform-registry-keys"
// GPG public keys from live deployment (two keys: primary + legacy).
// Primary key is used by the current operator-built binary.
var gpgPublicKey = GPGPublicKey{
KeyID: registrykeys.KeyID,
ASCIIArmor: registrykeys.ASCIIArmor,
var gpgPrimaryKey = GPGPublicKey{
KeyID: "3EC4673EB798238A",
ASCIIArmor: `-----BEGIN PGP PUBLIC KEY BLOCK-----
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
6cZ0NQ==
=3Ea2
-----END PGP PUBLIC KEY BLOCK-----`,
}
var gpgLegacyKey = GPGPublicKey{
KeyID: "CB3A0DF161ECC416",
ASCIIArmor: `-----BEGIN PGP PUBLIC KEY BLOCK-----
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
=q8Tw
-----END PGP PUBLIC KEY BLOCK-----`,
}
+64 -19
View File
@@ -10,7 +10,11 @@ import (
"net/http"
"net/url"
"os"
"os/signal"
"sort"
"strings"
"syscall"
"time"
s3 "github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
@@ -20,8 +24,11 @@ var (
s3Client *s3.Client
bucketName = "terraform-registry" // Default
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
type Discovery struct {
ProvidersV1 string `json:"providers.v1"`
@@ -68,7 +75,11 @@ type GPGPublicKey struct {
func main() {
// 1. Init S3 Connection
if hostname == "" {
hostname = "localhost:8080"
hostname = "localhost:5000"
}
// S3_PREFIX default comes from Dockerfile ENV; override in jsonEnv if needed.
if s3Prefix == "" {
s3Prefix = hostname
}
endpoint := os.Getenv("S3_ENDPOINT")
@@ -80,9 +91,10 @@ func main() {
}
var err error
useSSL := os.Getenv("S3_USE_SSL") != "false"
s3Client, err = s3.New(endpoint, &s3.Options{
Creds: credentials.NewStaticV4(accessKeyID, secretAccessKey, ""),
Secure: true, // Force secure for cloud S3
Secure: useSSL,
})
if err != nil {
log.Fatalln(err)
@@ -97,8 +109,31 @@ func main() {
http.HandleFunc("/healthz", healthzHandler)
http.HandleFunc("/readyz", readyzHandler)
log.Printf("Starting Registry Service on :8080 (Bucket: %s, Endpoint: %s)\n", bucketName, endpoint)
log.Fatal(http.ListenAndServe(":8080", nil))
port := os.Getenv("PORT")
if port == "" {
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) {
@@ -107,12 +142,12 @@ func rootHandler(w http.ResponseWriter, r *http.Request) {
return
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
fmt.Fprint(w, `
fmt.Fprintf(w, `
<!DOCTYPE html>
<html>
<head><title>Terra Registry</title></head>
<head><title>Terra Registry v`+VERSION+`</title></head>
<body>
<h1>Terra Registry & Documentation Server</h1>
<h1>Terra Registry & Documentation Server v`+VERSION+`</h1>
<p>Status: <span style="color: green">ONLINE</span></p>
<hr>
<p>Powered by Nubes Cloud S3 Storage</p>
@@ -122,17 +157,16 @@ func rootHandler(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")
if bucket == "" || key == "" {
http.Error(w, "Missing bucket or key params", http.StatusBadRequest)
if key == "" {
http.Error(w, "Missing key param", http.StatusBadRequest)
return
}
obj, err := s3Client.GetObject(context.Background(), bucket, key, s3.GetObjectOptions{})
obj, err := s3Client.GetObject(context.Background(), bucketName, key, s3.GetObjectOptions{})
if err != nil {
log.Printf("Error getting object %s/%s: %v", bucket, key, err)
log.Printf("Error getting object %s/%s: %v", bucketName, key, err)
http.Error(w, "File not found", http.StatusNotFound)
return
}
@@ -140,7 +174,7 @@ func proxyHandler(w http.ResponseWriter, r *http.Request) {
stat, err := obj.Stat()
if err != nil {
log.Printf("Error stating object %s/%s: %v", bucket, key, err)
log.Printf("Error stating object %s/%s: %v", bucketName, key, err)
http.Error(w, "File not found or not accessible", http.StatusNotFound)
return
}
@@ -177,7 +211,7 @@ func router(w http.ResponseWriter, r *http.Request) {
}
func listVersions(w http.ResponseWriter, r *http.Request, namespace, pType string) {
prefix := fmt.Sprintf("%s/%s/%s/", hostname, namespace, pType)
prefix := fmt.Sprintf("%s/%s/%s/", s3Prefix, namespace, pType)
ctx := context.Background()
versions := []Version{}
seenVersions := map[string]*Version{}
@@ -206,6 +240,9 @@ 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") {
seenVersions[verStr].Platforms = append(seenVersions[verStr].Platforms, Platform{OS: "linux", Arch: "amd64"})
}
@@ -218,6 +255,10 @@ func listVersions(w http.ResponseWriter, r *http.Request, namespace, pType strin
versions = append(versions, *v)
}
sort.Slice(versions, func(i, j int) bool {
return versions[i].Version < versions[j].Version
})
resp := VersionList{
ID: fmt.Sprintf("%s/%s", namespace, pType),
Versions: versions,
@@ -228,7 +269,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) {
basePath := fmt.Sprintf("%s/%s/%s/%s", hostname, namespace, pType, version)
basePath := fmt.Sprintf("%s/%s/%s/%s", s3Prefix, 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)
@@ -256,8 +297,6 @@ 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))
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,
@@ -268,7 +307,7 @@ func downloadVersion(w http.ResponseWriter, r *http.Request, namespace, pType, v
ShasumsSignatureURL: sigLink,
Shasum: shasumValue,
SigningKeys: SigningKeys{
GPGPublicKeys: []GPGPublicKey{gpgKey},
GPGPublicKeys: []GPGPublicKey{gpgPrimaryKey, gpgLegacyKey},
},
}
@@ -282,7 +321,13 @@ func healthzHandler(w http.ResponseWriter, r *http.Request) {
}
func readyzHandler(w http.ResponseWriter, r *http.Request) {
// Проверить доступность S3
_, err := s3Client.BucketExists(context.Background(), bucketName)
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.Write([]byte("ok"))
}
+1 -1
View File
@@ -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", "go-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", "")