Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,9 @@ linters:
- funlen
- mnd
path: cmd/mcp-smoke/
- linters:
- godox
path: internal/license/client\.go
paths:
- bin
- config
Expand Down
290 changes: 290 additions & 0 deletions .spec/features/local-test-env/approved/design.md

Large diffs are not rendered by default.

152 changes: 152 additions & 0 deletions .spec/features/local-test-env/approved/explore.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
# Exploration: local-test-env

## Введение (Intent)

После миграции на `disk-plugin-execution` механизм выполнения плагинов изменился с Docker-контейнеров на запуск бинарников с диска. Нужно адаптировать локальное окружение разработчика: конфиги, docker-compose, Taskfile — чтобы можно было легко поставить пару плагинов для тестов и запустить сервис.

## Исследование (Investigation)

### Существующие плагины в `registry/`

4 Dockerfile-а, все с идентичной структурой (multi-stage: `golang:alpine` → `scratch`):

| Плагин | Dockerfile | Бинарник |
|--------|-----------|----------|
| `protocolbuffers/go:v1.36.10` | `registry/protocolbuffers/go/v1.36.10/Dockerfile` | `/protoc-gen-go` |
| `grpc/go:v1.5.1` | `registry/grpc/go/v1.5.1/Dockerfile` | `/protoc-gen-go-grpc` |
| `grpc-ecosystem/gateway:v2.27.3` | `registry/grpc-ecosystem/gateway/v2.27.3/Dockerfile` | `/protoc-gen-grpc-gateway` |
| `grpc-ecosystem/openapiv2:v2.27.3` | `registry/grpc-ecosystem/openapiv2/v2.27.3/Dockerfile` | `/protoc-gen-openapiv2` |

### Конфиги — остатки старого подхода

- `config.yml` (строка 13): `registry.domain: "localhost:5005"` — старое поле, не используемое новым кодом. Нужно заменить на `plugins_dir` + `max_output_size`.
- `config.local.yml` (строка 13): тоже `registry.domain: "localhost:5005"`.
- `cmd/main.go` (строка 76–79): структура `registryConfig` уже использует `PluginsDir` и `MaxOutputSize` — конфиги просто отстали.

### docker-compose.yml

- Сервис `registry` (`easyp-registry`, порт 5005) — Docker Registry v3, больше не нужен для plugin execution.
- Сервис `service` всё ещё монтирует `/var/run/docker.sock` (строка 211) — больше не нужен.
- Нет volume для `./plugins:/plugins`.

### Taskfile.yml

- `local-push-registry` — запускает `./push.sh` для пуша всех образов в локальный registry. Устарело.
- `local-push-required` — собирает и пушит образы `protoc-gen-go` и `protoc-gen-go-grpc`. Нужно переделать.

### .gitignore

- `plugins/` уже в `.gitignore` (строка 9). ✅

### Подход: `docker build --output`

Переделать Dockerfile-ы так, чтобы они **только собирали бинарник** и ничего больше. Финальный стейдж — просто `COPY` в `/plugin`. Затем вызов с `--output` копирует результат прямо на диск:

**Пример переделанного Dockerfile (`registry/protocolbuffers/go/v1.36.10/Dockerfile`):**
```dockerfile
FROM golang:1.25-alpine3.22

ENV CGO_ENABLED=0 GOOS=linux GOARCH=amd64
RUN apk add upx=5.0.2-r0 --no-cache

RUN --mount=type=cache,target=/go/pkg/mod \
go install -ldflags "-s -w" -trimpath google.golang.org/protobuf/cmd/protoc-gen-go@v1.36.10 \
&& mv /go/bin/${GOOS}_${GOARCH}/protoc-gen-go /go/bin/protoc-gen-go || true \
&& upx --best --lzma /go/bin/protoc-gen-go

FROM scratch
COPY --from=0 /go/bin/protoc-gen-go /plugin
```

**Вызов:**
```bash
docker build --output=./plugins/protocolbuffers/go/v1.36.10/ registry/protocolbuffers/go/v1.36.10/
```

Результат: `./plugins/protocolbuffers/go/v1.36.10/plugin` — готовый бинарник. Одна команда, без промежуточных контейнеров.

### Регистрация плагинов через API

После сборки бинарников и запуска сервиса, плагины регистрируются через gRPC API `CreatePlugin`:

```bash
grpcurl -plaintext -d '{
"group": "protocolbuffers",
"name": "go",
"version": "v1.36.10",
"config": {"command": ["/plugins/protocolbuffers/go/v1.36.10/plugin"]},
"tags": ["go", "official"]
}' localhost:8080 api.generator.v1.ServiceAPI/CreatePlugin
```

Скрипт `register-plugins.sh` (или часть `build-plugins.sh`) будет вызывать `CreatePlugin` для каждого собранного плагина. Это лучше, чем прямой SQL, потому что:
- Проходит через всю цепочку валидации (`ValidateConfig`).
- Не нужен доступ к PostgreSQL напрямую.
- Идемпотентность: если плагин уже зарегистрирован (`ALREADY_EXISTS`), скрипт просто продолжает.

## Инструменты сборки (Build Tooling)

- **Orchestrator:** Taskfile v3
- **Test:** `go test ./...`
- **Build:** `go build -o main ./cmd/main.go`
- **Lint:** `golangci-lint run ./...`
- **Generate:** `easyp --cfg easyp.yaml generate`
- **Source:** `Taskfile.yml`

## Рассмотренные варианты (Options Considered)

### Вариант А: Переделать Dockerfile-ы + `docker build --output` (рекомендован)

- **Описание:** Упростить Dockerfile-ы: убрать `ENTRYPOINT`, `USER`, `/etc/passwd`. Финальный стейдж `FROM scratch` содержит только `COPY --from=0 ... /plugin`. Сборка через `docker build --output=./plugins/{path}/ registry/{path}/` — Docker выплёвывает бинарник прямо на диск. Скрипт `build-plugins.sh` обходит все Dockerfile-ы и вызывает эту команду.
- **Плюсы:**
- Dockerfile-ы становятся чище — одна ответственность (билд бинарника).
- Не нужны промежуточные `docker create` / `docker cp` / `docker rm`.
- Кросс-компиляция из коробки, UPX-сжатие.
- Единый источник правды для версий плагинов.
- **Минусы:**
- Требует BuildKit (`DOCKER_BUILDKIT=1`), но он включён по умолчанию в Docker >= 23.0.
- **Сложность:** Низкая.

### Вариант Б: `docker build` + `docker create` + `docker cp`

- **Описание:** Оставить Dockerfile-ы как есть (multi-stage с scratch), собирать образ, создавать контейнер, копировать бинарник, удалять контейнер.
- **Плюсы:** Не меняет Dockerfile-ы.
- **Минусы:** 4 команды вместо 1. Нужно чистить контейнеры. Dockerfile-ы содержат ненужные `ENTRYPOINT`/`USER`.
- **Сложность:** Низкая, но больше boilerplate.

## Ограничения и риски (Constraints & Risks)

- **Кросс-платформенность:** `docker compose up` запускает Linux-контейнер. `task run-local` запускает на macOS. Dockerfile-ы сейчас хардкодят `GOOS=linux GOARCH=amd64`. Для `task run-local` нужен будет отдельный режим — но это Deferred (v2).
- **Изменение конфигов:** `config.yml` и `config.local.yml` нужно обновить — но поле `domain` уже не используется кодом, так что ничего не ломается.
- **BuildKit:** `--output` требует BuildKit, который включён по умолчанию в Docker >= 23.0. Для старых версий нужен `DOCKER_BUILDKIT=1`.

## Рекомендованное направление (Recommended Direction)

**Вариант А** — переделать Dockerfile-ы + `docker build --output`. Создать скрипт `build-plugins.sh`, который:
1. Обходит все Dockerfile-ы в `registry/`.
2. Для каждого вызывает `docker build --output=./plugins/{group}/{name}/{version}/` .
3. Результат: готовые бинарники в `./plugins/`.

Плюс обновить конфиги, docker-compose, Taskfile и добавить механизм регистрации плагинов в БД.

## Границы области (Scope Boundaries)

- **Must-have (v1):**
- Переделать 4 Dockerfile-а в `registry/` (убрать `ENTRYPOINT`/`USER`/`passwd`, финальный стейдж выдаёт только `/plugin`).
- Скрипт `build-plugins.sh` для сборки всех плагинов через `docker build --output`.
- Обновление `config.yml` и `config.local.yml`: `plugins_dir` + `max_output_size` вместо `domain`.
- Обновление `docker-compose.yml`: убрать `docker.sock` mount, убрать сервис `registry`, убрать volume `registry-data`, добавить volume `./plugins:/plugins`.
- Обновление `Taskfile.yml`: переделать `local-push-required` → `build-plugins`, убрать `local-push-registry`.
- Удаление `push.sh`.
- Скрипт или таска для регистрации плагинов через gRPC API (`CreatePlugin`).
- **Deferred (v2):**
- Режим `--local` для сборки macOS-бинарников (`task run-local`).
- **Needs spike:**
- Нет.

## Допущения и открытые вопросы (Assumptions & Open Questions)

- [ASSUMPTION: Docker >= 23.0 установлен у всех разработчиков (BuildKit включён по умолчанию).]
- [ASSUMPTION: Все Dockerfile-ы в `registry/` имеют предсказуемую структуру: один build-стейдж, один бинарник.]
- [ASSUMPTION: Для `task up` достаточно Linux/amd64 бинарников.]
- Открытых вопросов нет — пользователь подтвердил полную очистку docker-compose от registry и docker.sock.
38 changes: 38 additions & 0 deletions .spec/features/local-test-env/approved/implementation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# local-test-env — Implementation Summary

**Date:** 2026-05-24

## Выполненные задачи

- [x] **T-1** GREEN — Baseline (go build + go test проходят)
- [x] **T-2** CODE — Переделаны 4 Dockerfile-а (убраны ENTRYPOINT/USER/passwd, финальный стейдж `/plugin`)
- [x] **T-3** CODE — Создан `build-plugins.sh`
- [x] **T-4** CODE — Создан `register-plugins.sh`
- [x] **T-5** CODE — Обновлены config.yml, config.local.yml, docker-compose.yml, Taskfile.yml, удалён push.sh
- [x] **T-6** VERIFY — Все проверки пройдены
- [x] **T-7** GATE — Финальная контрольная точка пройдена

## Изменённые файлы

| File | Change |
|------|--------|
| `registry/protocolbuffers/go/v1.36.10/Dockerfile` | Упрощён: `FROM scratch` + `COPY /plugin` |
| `registry/grpc/go/v1.5.1/Dockerfile` | Упрощён: `FROM scratch` + `COPY /plugin` |
| `registry/grpc-ecosystem/gateway/v2.27.3/Dockerfile` | Упрощён: `FROM scratch` + `COPY /plugin` |
| `registry/grpc-ecosystem/openapiv2/v2.27.3/Dockerfile` | Упрощён: `FROM scratch` + `COPY /plugin` |
| `build-plugins.sh` | **NEW** — Скрипт сборки бинарников через `docker build --output` |
| `register-plugins.sh` | **NEW** — Скрипт регистрации через gRPC CreatePlugin |
| `config.yml` | `registry.domain` → `registry.plugins_dir` + `max_output_size` |
| `config.local.yml` | `registry.domain` → `registry.plugins_dir` + `max_output_size` |
| `docker-compose.yml` | Убран `registry`, `registry-data`, `docker.sock` → `./plugins:/plugins:ro` |
| `Taskfile.yml` | Убраны `local-push-*`, добавлены `build-plugins` + `register-plugins` |
| `push.sh` | **DELETED** |

## Результат верификации

- ✓ `go build` — компилируется
- ✓ `go test ./...` — все тесты проходят
- ✓ Нет ENTRYPOINT в Dockerfile-ах
- ✓ `/plugin` присутствует в 4 Dockerfile-ах
- ✓ `build-plugins.sh` и `register-plugins.sh` исполняемые
- ✓ `push.sh` удалён
98 changes: 98 additions & 0 deletions .spec/features/local-test-env/approved/requirements.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# local-test-env — Requirements

**Status:** Draft
**Date:** 2026-05-24

## Обзор

Адаптация локального окружения разработчика к новому способу выполнения плагинов (бинарники с диска вместо Docker-контейнеров). Включает: переделку Dockerfile-ов из `registry/` для сборки бинарников через `docker build --output`, скрипты для сборки и регистрации плагинов, очистку docker-compose от устаревших сервисов (registry, docker.sock), обновление конфигов и Taskfile.

## Глоссарий

| Термин | Определение | Code Artifact |
|--------|------------|---------------|
| Plugin binary | Скомпилированный Go-бинарник плагина, принимающий `CodeGeneratorRequest` на stdin и отдающий `CodeGeneratorResponse` на stdout | `./plugins/{group}/{name}/{version}/plugin` |
| `PluginConfig` | JSON-конфигурация плагина с массивом `command`, опциональными `env` и `timeout` | `internal/adapters/registry/registry.go` → `PluginConfig` |
| `build-plugins.sh` | Скрипт сборки plugin binary из Dockerfile-ов через `docker build --output` | `build-plugins.sh` |
| `register-plugins.sh` | Скрипт регистрации собранных плагинов через gRPC API `CreatePlugin` | `register-plugins.sh` |

---

## Требования

### 1. Dockerfile-ы

**REQ-1.1** WHEN `docker build --output=./plugins/{group}/{name}/{version}/ registry/{group}/{name}/{version}/` выполняется для любого Dockerfile из `registry/`, the system SHALL создать файл `./plugins/{group}/{name}/{version}/plugin` — исполняемый Linux/amd64 бинарник.

**REQ-1.2** WHEN Dockerfile собирается, the system SHALL использовать UPX-сжатие для минимизации размера бинарника.

**REQ-1.3** WHEN финальный стейдж Dockerfile описывается, the system SHALL содержать только `FROM scratch` и `COPY` бинарника в `/plugin` — без `ENTRYPOINT`, `USER`, или `/etc/passwd`.

### 2. Скрипт сборки (`build-plugins.sh`)

**REQ-2.1** WHEN `build-plugins.sh` запускается, the system SHALL найти все файлы `Dockerfile` в директории `registry/` рекурсивно и собрать каждый через `docker build --output`.

**REQ-2.2** WHEN `docker build` для любого плагина завершается с ошибкой, the system SHALL немедленно остановить выполнение скрипта с ненулевым exit-кодом (`set -e`).

**REQ-2.3** WHEN `build-plugins.sh` завершается успешно, the system SHALL создать для каждого Dockerfile файл `./plugins/{group}/{name}/{version}/plugin` с правами на исполнение.

### 3. Скрипт регистрации (`register-plugins.sh`)

**REQ-3.1** WHEN `register-plugins.sh` запускается, the system SHALL для каждого собранного плагина в директории `plugins/` вызвать gRPC метод `CreatePlugin` с полями `group`, `name`, `version` и `config.command = ["/plugins/{group}/{name}/{version}/plugin"]`.

**REQ-3.2** WHEN gRPC-вызов `CreatePlugin` возвращает ошибку `ALREADY_EXISTS`, the system SHALL пропустить этот плагин и продолжить с остальными (не завершаться с ошибкой).

**REQ-3.3** WHEN gRPC-вызов `CreatePlugin` возвращает любую другую ошибку (кроме `ALREADY_EXISTS`), the system SHALL немедленно остановить выполнение с ненулевым exit-кодом.

### 4. Конфигурация

**REQ-4.1** WHEN сервис запускается с `config.yml`, the system SHALL использовать поле `registry.plugins_dir` (по умолчанию `/plugins`) вместо устаревшего `registry.domain`.

**REQ-4.2** WHEN сервис запускается с `config.yml`, the system SHALL использовать поле `registry.max_output_size` (по умолчанию `67108864`, 64 МБ).

**REQ-4.3** WHEN сервис запускается с `config.local.yml`, the system SHALL использовать `registry.plugins_dir` указывающий на локальную директорию `./plugins`.

### 5. Docker Compose

**REQ-5.1** WHEN `docker-compose.yml` описывает сервис `service`, the system SHALL монтировать volume `./plugins:/plugins` вместо `/var/run/docker.sock`.

**REQ-5.2** WHEN `docker-compose.yml` описывает инфраструктуру, the system SHALL не содержать сервис `registry` и volume `registry-data`.

### 6. Taskfile

**REQ-6.1** WHEN `task build-plugins` выполняется, the system SHALL вызвать `build-plugins.sh` для сборки всех плагинов.

**REQ-6.2** WHEN `task register-plugins` выполняется, the system SHALL вызвать `register-plugins.sh` для регистрации всех плагинов через gRPC API.

**REQ-6.3** WHEN `task run` выполняется, the system SHALL использовать `build-plugins` вместо устаревшей `local-push-registry` в зависимостях.

**REQ-6.4** WHEN Taskfile описывает таски, the system SHALL не содержать `local-push-registry` и `local-push-required`.

### 7. Удаление устаревших файлов

**REQ-7.1** WHEN проект собран, the system SHALL не содержать файл `push.sh` в корне репозитория.

---

## Порядок зависимостей (Topological Order)

```
REQ-1.1..1.3 → REQ-2.1..2.3 → REQ-3.1..3.3
Причина: Dockerfile-ы (1.x) нужны для скрипта сборки (2.x), который создаёт бинарники для скрипта регистрации (3.x).

REQ-4.1..4.3 (независимы — можно параллельно с 1–3)
REQ-5.1..5.2 (независимы — можно параллельно с 1–3)
REQ-6.1..6.4 → зависят от REQ-2.x и REQ-3.x (таски ссылаются на скрипты)
REQ-7.1 (независим)
```

---

## Команды верификации

| Действие | Команда | Источник |
|----------|---------|----------|
| Test | `go test ./...` | Taskfile.yml |
| Build | `go build -o main ./cmd/main.go` | Taskfile.yml |
| Lint | `golangci-lint run ./...` | Taskfile.yml |
| Generate | `easyp --cfg easyp.yaml generate` | Taskfile.yml |
Loading
Loading