# Конфигурация проекта workflows-api

`workflows-api` — HTTP-сервис (Go 1.24, фреймворк [Fiber v2](https://github.com/gofiber/fiber)) для работы с workflow: создание, чтение, перезапуск задач, отмена запусков, приоритизация. Хранилище — PostgreSQL. Трейсинг — OpenTelemetry (через внешнюю библиотеку `gitlab.sarex.io/infra/golang-fiber-otel-tools`).

## Способы конфигурирования

Конфигурация читается **только из переменных окружения**. Используется библиотека [`github.com/ilyakaznacheev/cleanenv`](https://github.com/ilyakaznacheev/cleanenv) (`cleanenv.ReadEnv`). Файлы конфигурации (`.yaml`, `.json`) не читаются — вызывается именно `ReadEnv`, а не `ReadConfig`.

- **Префикс** у переменных отсутствует — используются «плоские» имена (`POSTGRES_ADDRESS`, `HTTP_HOST` и т. п.).
- **Вложенность** структуры `Config` описывается через встроенные (embedded) структуры (`App`, `Log`, `HTTP`, `pgxconnection.Postgres`, `TRACER`, `Execution`), но на имена переменных это не влияет — теги `env` заданы плоско.
- Значения по умолчанию задаются тегом `env-default`.
- Булевы значения cleanenv принимает как `true/false`, а также `1/0` (в Helm используется числовая форма).

Точка сборки конфигурации — `config/config.go`, функция `config.New()`. Отдельно, для запуска миграций, вторая структура `pkg/postgres/gopg.Postgres` читается своим вызовом `cleanenv.ReadEnv` в `gopg.GetPgConnectionWithoutConfig()` — **у неё те же имена переменных, но частично другие значения по умолчанию** (см. «Замечания»).

### Способы запуска

| Способ запуска | Откуда берутся переменные |
| --- | --- |
| Бинарь `httpserver` (production, `Dockerfile` `ENTRYPOINT ["/httpserver", "migrate"]`) | Переменные окружения контейнера (в k8s — из Helm-чарта: блоки `envs` и `secretEnvs`) |
| Бинарь `migrations` (отдельный ранер миграций) | Переменные окружения контейнера |
| Локальный запуск через `air` (`.air.toml`, hot-reload, сборка `./cmd/httpserver/main.go`) | Переменные окружения оболочки / `.env` (подхватываются вручную), значения по умолчанию из кода |
| `docker-compose up` (`docker-compose.yaml`) | `environment:` в compose + значения по умолчанию из кода |

### Точки входа и вспомогательные скрипты

| Файл / скрипт | Назначение |
| --- | --- |
| `cmd/httpserver/main.go` | Основная точка входа. Если передан хотя бы один аргумент (например `migrate`) — сначала выполняет миграции (`go-pg-migrations`), затем поднимает Fiber-сервер |
| `cmd/migrations/main.go` | Отдельный бинарь только для миграций (без запуска сервера) |
| `Dockerfile` | Multi-stage сборка: собирает `httpserver` и `migrations`, `ENTRYPOINT ["/httpserver", "migrate"]` |
| `entrypoint.sh` | Скрипт-обёртка (`/go/bin/migrations migrate` → `/go/bin/httpserver`). **Не используется** Dockerfile и ссылается на несуществующие пути бинарей — устаревший артефакт (см. «Замечания») |
| `.air.toml` | Конфиг hot-reload `air` для локальной разработки |
| `docker-compose.yaml` | Локальный стенд: PostgreSQL 14-alpine + сборка API. Блок `migrations` закомментирован |
| `Makefile` | Юнит-тесты, генерация моков, поднятие/сборка контейнера БД (`.docker/postgres`) |
| `.docker/postgres/Dockerfile` | Образ локальной БД для `make container-run-deps` |

### Порядок старта контейнера (production)

1. Контейнер стартует с `ENTRYPOINT ["/httpserver", "migrate"]`.
2. `httpserver` видит аргумент `migrate` (`len(os.Args) > 1`) → открывает подключение к БД через `go-pg` (`gopg.GetPgConnectionWithoutConfig`, читает env заново) и прогоняет миграции из `cmd/migrations/migrationfiles`.
3. После миграций поднимается Fiber-приложение (`server.New` → `server.Run`), подключается пул `pgx` (`pgxconnection.GetPgConnection`), при `TRACER_USE=true` инициализируется трейсер/otel-логгер.
4. Сервер слушает адрес из `HTTP_HOST`. Health-check — `GET /ping`.

## Переменные приложения

Ниже — переменные, которые **реально читает код** (`config/config.go` + `pkg/postgres/pgxconnection/postgres.go`).

### App (`config/config.go`)

| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `APP_NAME` | string | `workflows-api` | Имя приложения (`App.Name`) |
| `APP_VERSION` | string | `v1` | Версия приложения (`App.Version`) |

### Log

| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `LOG_LEVEL` | string | `info` | Уровень логирования (`logging.NewLogger`) |

### HTTP

| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `HTTP_HOST` | string | `0.0.0.0:8000` | Адрес и порт прослушивания Fiber-сервера |
| `PUBLIC_KEY` | string | — (пусто) | PEM-публичный ключ (PKIX) для проверки JWT Sarex. **Обязателен**: при пустом значении `auth.New` вызывает `panic` на старте |
| `HTTP_BODY_LIMIT` | int | `268435456` (256 MiB) | Максимальный размер тела запроса (`fiber.Config.BodyLimit`) |
| `HTTP_READ_BUFFER_SIZE` | int | `98304` (96 KiB) | Размер буфера чтения (`fiber.Config.ReadBufferSize`) |

### Database (`pkg/postgres/pgxconnection`)

| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `POSTGRES_ADDRESS` | string | `localhost` | Хост PostgreSQL |
| `POSTGRES_DB` | string | `processing_db` | Имя базы данных |
| `POSTGRES_USER` | string | `sarex` | Пользователь БД |
| `POSTGRES_PASSWORD` | string | `sarex` | Пароль БД |
| `POSTGRES_PORT` | string | `5432` | Порт PostgreSQL |
| `POSTGRES_POOL_SIZE` | int | `3` | Размер пула (используется только для логирования; фактический размер пула pgx задаётся строкой подключения) |
| `ENABLE_SQL_QUERY` | bool | `true` | Флаг логирования SQL. **Читается в конфиг, но нигде не используется** (см. «Замечания») |
| `YC-PG-CERTIFICATE` | string | — (пусто) | CA-сертификат (PEM) для TLS-подключения к БД. Непустое значение включает TLS |
| `POSTGRES_SSL_USE` | bool | `false` | Включение TLS-подключения к БД |

### Tracer (`config.TRACER`, OpenTelemetry)

| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `TRACER_USE` | bool | `false` | Включить трейсинг/otel-логгер и otelfiber-middleware |
| `TRACER_HOST` | string | `localhost:4317` | Адрес OTLP-коллектора (gRPC) |
| `TRACER_USE_INSECURE` | bool | `true` | Небезопасное (без TLS) подключение к коллектору |
| `SERVICE_NAME` | string | `wf-test-db` | Имя сервиса в трейсах |
| `TRACER_LOGGER_NAME` | string | `tracer_logger` | Имя otel-логгера |

### Execution (лимиты ресурсов задач, `config.Execution`)

| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `MAX_CPU_REQUESTS` | string | `25` | Максимально допустимый CPU-request в конфиге execution задачи (валидация через `k8s.io/apimachinery/resource`) |
| `MAX_MEMORY_REQUESTS` | string | `300Gi` | Максимально допустимый memory-request в конфиге execution задачи |

## Переменные инфраструктуры, сборки и вспомогательных утилит

Переменные `Makefile` (значения по умолчанию, переопределяются через `make VAR=...`):

| Переменная | Значение по умолчанию | Назначение |
| --- | --- | --- |
| `OCI` | `docker` | Контейнерный движок (`docker`/`podman`) |
| `WF_API_CONTAINER__NETWORK_NAME` | `wf-api-network` | Имя bridge-сети для локальных контейнеров |
| `WF_API_DATABASE_IMAGE__TAG` | `wf-api-database` | Тег образа локальной БД |
| `WF_API_DATABASE_CONTAINER__NAME` | `wf-api-database-c` | Имя контейнера БД |
| `WF_API_DATABASE__VOLUME_NAME` | `wf-api-data` | Имя тома данных БД |
| `POSTGRES_USER` / `POSTGRES_PASSWORD` / `POSTGRES_DB` / `POSTGRES_PORT` | берутся из окружения | Прокидываются в контейнер БД при `container-run-database` |

Переменные сборки образа (`Dockerfile`):

| Переменная | Значение | Назначение |
| --- | --- | --- |
| `CGO_ENABLED` | `0` | Статическая сборка Go |
| `GOOS` | `linux` | Целевая ОС |
| `GOARCH` | `amd64` | Целевая архитектура |

Переменные `.docker/postgres/Dockerfile` и `docker-compose.yaml` (локальный стенд):

| Переменная | Значение | Назначение |
| --- | --- | --- |
| `POSTGRES_DB` | `processing_db` | БД локального PostgreSQL |
| `POSTGRES_USER` | `processing` | Пользователь локального PostgreSQL |
| `POSTGRES_PASSWORD` | `processing` | Пароль локального PostgreSQL |
| `POSTGRES_ADDRESS` | `database` (в compose для сервиса `api`) | Хост БД внутри сети compose |
| `POSTGRES_SSL_USE` | `false` | Отключение TLS локально |

## Переменные из Helm-чарта

Деплой выполняется зависимостью-чартом `universal-chart` (`.helm/Chart.yaml`, версия `0.1.7`), значения — в `.helm/values.yaml`. Значения даются по стендам через ключи `_default / stage / preprod / production`.

### Обычные переменные (`services.workflows-api.envs`)

| Переменная | Значение (`_default`) | Значения по стендам / примечание |
| --- | --- | --- |
| `POD_NAME` | `$(K8S_POD_NAME)` | Имя пода. **Кодом не читается** |
| `POSTGRES_POOL_SIZE` | `3` | Размер пула (логирование) |
| `HTTP_HOST` | `0.0.0.0:8080` | Адрес прослушивания в k8s (порт 8080) |
| `S3_SERVICE_ACCOUNT` | `/etc/sarex/yc-s3/yc-s3-service-account.json` | **Кодом не читается** |
| `DJANGO_HOST` | `https://stage.sarex.io` | stage: `stage.sarex.io`, preprod: `preprod.sarex.io`, production: `lk.sarex.io`. **Кодом не читается** |
| `OBSERVABILITY_TRACE_COLLECTOR_ENDPOINT` | `opentelemetry-collector.observability.svc.cluster.local:4318` | Одинаково на всех стендах. **Кодом не читается** (легаси-пакет `observavility` не подключён) |
| `ENABLE_SQL_QUERY` | `0` | Читается в конфиг, но не используется |
| `POSTGRES_SSL_USE` | `1` | preprod: `true`, остальные `1` |
| `ENABLE_OBSERVABILITY` | `1` | stage `1`, preprod `0`, production `1`. **Кодом не читается** |
| `TRACER_USE` | `1` | Включает трейсинг |
| `TRACER_HOST` | `signoz-otel-collector.signoz.svc.cluster.local:4317` | preprod/production: `signoz-otel-collector-external.signoz.svc.cluster.local:4317` |
| `SERVICE_NAME` | `workflows-api.processing-stage` | stage: `workflows-api.platform`, preprod: `workflows-api.processing-preprod`, production: `workflows-api.processing-prod` |
| `TRACER_USE_INSECURE` | `1` | Небезопасное подключение к коллектору |
| `TRACER_LOGGER_NAME` | `tracer_logger` | Имя otel-логгера |
| `MAX_CPU_REQUESTS` | `25` | Лимит CPU-request задач |
| `MAX_MEMORY_REQUESTS` | `300Gi` | Лимит memory-request задач |

### Секретные переменные (`services.workflows-api.secretEnvs`)

| Переменная | Секрет (secret_name) | Ключ (secret_key) |
| --- | --- | --- |
| `POSTGRES_ADDRESS` | `_default`/`stage`: `processing-postgresql-secret`; `preprod`/`production`: `ya-pg-secret` | `host` |
| `POSTGRES_PORT` | `_default`/`stage`: `processing-postgresql-secret`; `preprod`/`production`: `ya-pg-secret` | `port` |
| `POSTGRES_DB` | `_default`/`stage`: `processing-postgresql-secret`; `preprod`/`production`: `ya-pg-secret` | `database` |
| `POSTGRES_USER` | `_default`/`stage`: `processing-postgresql-secret`; `preprod`/`production`: `ya-pg-secret` | `_default`/`stage`: `user`; `preprod`/`production`: `username` |
| `POSTGRES_PASSWORD` | `_default`/`stage`: `processing-postgresql-secret`; `preprod`/`production`: `ya-pg-secret` | `password` |
| `PUBLIC_KEY` | `_default`/`stage`: `jwt-secret`; `preprod`/`production`: `public-key` | `_default`/`stage`: `public_key`; `preprod`/`production`: `key` |
| `YC-PG-CERTIFICATE` | `_default`/`stage`: `processing-postgresql-secret`; `preprod`/`production`: `yc-pg-certificate` | `_default`/`stage`: `ca.crt`; `preprod`/`production`: `certificate` |

### Прочие параметры чарта

- **Порт деплоймента**: `8080`; сервис `ClusterIP`, `targetPort: 8080`, `port: 80` (stage: `8000`).
- **Имя сервиса**: `workflows-service` (stage: `workflows-api-service`).
- **Реплики**: `_default`/`stage` — 1, preprod/production — 2.
- **Ресурсы пода**: requests `_default` 100Mi / 100m, preprod/production 200Mi / 200m.
- **Пробы**: liveness и readiness — `httpGet /ping` на порту 8080.
- **serviceAccount**: `workflows-api-sa`.
- **imagePullSecrets**: `dockerhub`. **Образ**: `cr.yandex/crp3ccidau046kdj8g9q/workflows-api`.

## Переменные в CI

`.gitlab-ci.yml` подключает общие пайплайны `generic/common-ci` (`common-security-scan.yaml`, `universal-pipeline.yaml@apps-business`). Глобальные переменные:

| Переменная | Значение | Назначение |
| --- | --- | --- |
| `SERVICE_NAME` | `workflows-api` | Имя сервиса в пайплайне |
| `DOCKERFILE_PATH` | `Dockerfile` | Путь к Dockerfile |
| `BUILD_ARGS` | `--build-arg CI_COMMIT_SHORT_SHA=${CI_COMMIT_SHORT_SHA}` | Аргументы сборки образа |
| `CI_TRIGGER_SOURCE` | `app` | Источник триггера |

Маппинг ветка/тег → стенд и namespace (`workflow.rules`):

| Условие | STAND | NAMESPACE | CHART_VERSION | K8S_HUSTLER_BRANCH | env (universal-chart.global.env) |
| --- | --- | --- | --- | --- | --- |
| `CI_COMMIT_BRANCH == "stage"` | `stage` | `platform` | `0.0.1-stage` | `universal-chart-stage` | `stage` |
| `CI_COMMIT_BRANCH == "master"` | `preprod` | `processing-preprod` | `0.0.1-preprod` | `universal-chart-preprod` | `preprod` |
| `CI_COMMIT_TAG` (любой тег) | `production` | `processing-prod` | `0.0.1-prod` | `universal-chart-production` | `production` |
| `CI_PIPELINE_SOURCE == "merge_request_event"` | — | — | — | — | сборка образа выключена (`ENABLE_BUILD_IMAGE=false`) |

Во всех деплой-правилах через `HELM_SET_ARGS` пробрасываются `IMAGE_NAME`, `commitSha=${CI_COMMIT_SHA}`, `gitlabUri`, `gitlabJobUrl`, `owner`. `RELEASE_NAME`/`CHART_NAME` — `workflows-api`.

Джоба `unittest` (`stage: test`, образ `golang:1.24`, `make unit-tests`) запускается на любых ветках/тегах и MR, `allow_failure: true`.

## Замечания и потенциальные проблемы

1. **Две разные структуры конфигурации БД с разными дефолтами.** Сервер использует `pkg/postgres/pgxconnection.Postgres` (дефолты `POSTGRES_USER=sarex`, `POSTGRES_PASSWORD=sarex`), а миграции — `pkg/postgres/gopg.Postgres` (дефолты `processing`/`processing`). При запуске без явно заданных переменных сервер и миграции подключались бы под разными кредами. В production это не проявляется, т. к. все переменные приходят из секретов.
2. **`ENABLE_SQL_QUERY` не используется.** Поле читается в обе структуры (`EnableSQLQuery`), но нигде в коде не применяется — флаг «мёртвый».
3. **`OBSERVABILITY_TRACE_COLLECTOR_ENDPOINT`, `ENABLE_OBSERVABILITY` не используются.** Пакет `pkg/observavility` (с `SetupOTelSDK`) нигде не импортируется — это легаси. Реальный трейсинг настраивается переменными `TRACER_*` через внешнюю библиотеку `golang-fiber-otel-tools`.
4. **`POD_NAME`, `S3_SERVICE_ACCOUNT`, `DJANGO_HOST` из Helm кодом не читаются** — либо задел на будущее, либо устаревшие переменные.
5. **`entrypoint.sh` устарел и не используется.** Он ссылается на `/go/bin/migrations` и `/go/bin/httpserver`, тогда как в образе бинари лежат в `/httpserver` и `/migrations`, а `ENTRYPOINT` задан в `Dockerfile` напрямую (`/httpserver migrate`).
6. **Опечатка в mount-пути internal-middleware.** В `server.go` middleware, выставляющий `is_internal=true`, монтируется как `app.Use("./internal", ...)` (с ведущей точкой) вместо `"/internal"`. Из-за этого для маршрутов группы `/internal` флаг `is_internal` может не выставляться; в контроллерах `nil`-значение трактуется как «внутренний/доверенный запрос» (проверки принадлежности к компании пропускаются). Логически поведение сохраняется, но путь выглядит как баг.
7. **`PUBLIC_KEY` обязателен.** При пустом значении `auth.New` делает `panic("failed to parse PEM block ...")` — сервис не стартует. Дефолта нет.
8. **Расхождение по порту.** Дефолт кода `HTTP_HOST=0.0.0.0:8000`, docker-compose — `8000`, а в k8s (Helm) — `8080`. Локально сервис слушает 8000, в кластере — 8080.
9. **`BUILD_ARGS` передаёт `CI_COMMIT_SHORT_SHA`, но `Dockerfile` не объявляет соответствующий `ARG`** — build-arg игнорируется.
10. **`POSTGRES_SSL_USE` в Helm задаётся то как `1`, то как `true`** (preprod). cleanenv корректно парсит обе формы, но единообразия нет.

## Минимальный набор для локального запуска

Для запуска сервера локально (например, БД поднята через `make container-run-deps` или `docker-compose`) достаточно:

```env
# БД
POSTGRES_ADDRESS=localhost
POSTGRES_PORT=5432
POSTGRES_DB=processing_db
POSTGRES_USER=processing
POSTGRES_PASSWORD=processing
POSTGRES_SSL_USE=false

# HTTP
HTTP_HOST=0.0.0.0:8000

# Обязательно: PEM публичный ключ (PKIX) для проверки JWT
PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----"

# Трейсинг можно выключить
TRACER_USE=false
```

Минимальный сценарий:
1. Поднять PostgreSQL: `make container-run-deps` (или `docker-compose up database`).
2. Экспортировать переменные выше (особенно валидный `PUBLIC_KEY`, иначе `panic`).
3. Прогнать миграции и запустить сервер: `go run ./cmd/httpserver migrate` (аргумент `migrate` включает миграции), либо `air` для hot-reload.
4. Проверить: `GET http://localhost:8000/ping` → `{"status":"ready"}`.

> Через `docker-compose up` сервис поднимается на `:8000`, БД — `processing/processing/processing_db`, но `PUBLIC_KEY` в compose не задан — для полноценной работы API его нужно добавить.
