openapi: 3.0.3 info: title: Workflows API version: "1.0.0" description: | REST API сервиса **workflows-api** для работы с workflow (пайплайнами задач): создание, получение, перезапуск задач, отмена запусков, приоритизация и получение позиции в очереди. ### Технологии - Язык: Go 1.24, HTTP-фреймворк **Fiber v2**. - JSON-сериализация: `bytedance/sonic`. - Хранилище: PostgreSQL (пул `pgx/v5`). - Трейсинг: OpenTelemetry (`otelfiber`), включается переменной `TRACER_USE`. ### Группы маршрутов Все обработчики регистрируются дважды — в двух группах с одинаковым набором путей (см. `internal/server/server.go` и `internal/controller/http/v1/workflows/routes.go`): - **`/api/v1/...`** — публичная группа. На префикс `/api` навешен middleware аутентификации (`auth.AuthMiddleware`): требуется валидный JWT. Для запросов выставляется `is_internal=false` и проверяется принадлежность пользователя к компании. - **`/internal/v1/...`** — внутренняя группа (сервис-сервис) без аутентификации; трактуется как доверенная (`is_internal=true`), проверки принадлежности к компании пропускаются. Отдельно, вне групп и без авторизации, доступен health-check **`GET /ping`**. В этом документе пути описаны относительно базового префикса группы `/api/v1` (см. `servers`). Те же пути доступны и под `/internal/v1`. ### Аутентификация Группа `/api` защищена middleware, который принимает один из двух токенов: - **`Authorization: Bearer `** — токен Sarex, подпись проверяется публичным ключом из переменной `PUBLIC_KEY` (RS/PKIX). Из claims извлекаются `user_id`, `company_ids`, `is_superuser`. - **`Identity: Bearer `** — токен Zitadel (проверяется без верификации подписи, `ParseUnverified`); данные пользователя берутся из claim `urn:zitadel:iam:user:metadata` (поля `id`, `company_ids`, `is_superuser`). Если присутствует заголовок `Identity`, используется он; иначе — `Authorization`. Часть операций (`prioritize`, `move_to_super_high_resources`) доступна только суперпользователю (`is_superuser=true`). ### Пагинация Метод `GET /workflows` поддерживает `limit` и `offset` (query-параметры, строки). Ответ содержит `items`, а также `limit`, `offset`, `total`. ### Обработка ошибок Ошибки возвращаются в JSON вида: ```json { "message": "human readable message", "error_code": "WF-0001" } ``` Коды (`internal/app_errors`): `WF-0000` (system, 500), `WF-0001` (not found, 404), `WF-0002` (no auth, 401), `WF-0003` (no access), `WF-0004` (invalid, 400), `WF-0005` (workflow config error / forbidden, 400/403). HTTP-статус выбирается в `ErrorHandler` фреймворка по типу ошибки: 400 (ошибки парсинга/валидации/ конфигурации workflow), 401 (нет/некорректный токен), 403 (forbidden), 404 (не найдено), 500 (прочее). ### Замечания (расхождения кода и существующей схемы) Документ приведён в соответствие с реальными маршрутами `routes.go`. Отличия от старого `openapi_schema.yaml`: - **Добавлены** отсутствовавшие маршруты: `POST /workflows/batch`, `GET /workflows/{workflow_id}/position`, `POST /workflows/{workflow_id}/prioritize`, `POST /tasks/{task_id}/move_to_super_high_resources`. - **Удалён** маршрут `GET /tasks-runs/{task_run_id}/logs` — в коде такого обработчика нет. (Внимание: `workflows-frontend` этот эндпоинт вызывает — см. `workflows-frontend.ENDPOINTS.md`.) - `POST /tasks-runs/{task_run_id}/cancel` фактически возвращает пустой объект `{}` (в коде `c.JSON(&struct{}{})`), а не объект task_run. - У задачи (`Task`) в модели есть поля `execution`, `services`, `layer`, `id`, `workflow_id`, а у workflow — `document_id`, которые в старой схеме отсутствовали. servers: - url: 'http://localhost:8000/api/v1' description: Локальный сервер (дефолт HTTP_HOST / docker-compose) - url: 'http://workflows-service/api/v1' description: Внутрикластерный сервис (preprod/production; stage — workflows-api-service:8000) - url: 'https://stage-api.sarex.io/workflows/api/v1' description: Публичный stage (через ingress, префикс /workflows) - url: 'https://api.sarex.io/workflows/api/v1' description: Публичный production (через ingress, префикс /workflows) tags: - name: workflows description: Операции с workflow - name: tasks description: Операции с задачами workflow - name: task-runs description: Операции с запусками задач - name: health description: Проверка доступности сервиса paths: /ping: get: tags: [health] summary: Health-check description: > Проверка готовности сервиса. Зарегистрирован вне групп `/api` и `/internal`, без аутентификации (реальный путь — `/ping`, без префикса `/api/v1`). operationId: Ping responses: '200': description: Сервис готов content: application/json: schema: type: object properties: status: type: string example: ready /companies/{company_id}/workflows: post: tags: [workflows] summary: Создать workflow description: > Создаёт workflow для компании. Для группы `/api` пользователь должен принадлежать компании `company_id`. Задачи не должны содержать поле `services` — используется `service_requests`. Если у задачи не задан `backoff_limit`, он проставляется равным 5. Поле `valid_until` должно быть в будущем. operationId: CreateWorkflow parameters: - $ref: '#/components/parameters/AuthorizationHeader' - name: company_id in: path required: true description: Идентификатор компании schema: type: integer example: 1 requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateWorkflowRequest' responses: '200': description: Созданный workflow content: application/json: schema: $ref: '#/components/schemas/Workflow' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '500': $ref: '#/components/responses/InternalError' /workflows: get: tags: [workflows] summary: Список workflow по компаниям description: > Возвращает список workflow для указанных компаний. Параметр `company_ids` — обязательная строка с идентификаторами через запятую. Для группы `/api` не-суперпользователю возвращаются только его компании. operationId: ListByCompanyID parameters: - $ref: '#/components/parameters/AuthorizationHeader' - name: company_ids in: query required: true description: Идентификаторы компаний через запятую schema: type: string example: "1,2,3" - name: limit in: query required: false schema: type: integer example: 100 - name: offset in: query required: false schema: type: integer example: 0 responses: '200': description: Список workflow content: application/json: schema: $ref: '#/components/schemas/WorkflowsResponse' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '500': $ref: '#/components/responses/InternalError' /workflows/batch: post: tags: [workflows] summary: Получить workflow пачкой description: Возвращает workflow по списку идентификаторов. operationId: GetWorkflows parameters: - $ref: '#/components/parameters/AuthorizationHeader' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/GetBatchWorkflowsRequest' responses: '200': description: Найденные workflow content: application/json: schema: $ref: '#/components/schemas/WorkflowsBatchResponse' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '500': $ref: '#/components/responses/InternalError' /workflows/{id}: get: tags: [workflows] summary: Получить workflow по ID operationId: GetWorkflow parameters: - $ref: '#/components/parameters/AuthorizationHeader' - name: id in: path required: true description: UUID workflow schema: type: string format: uuid responses: '200': description: Workflow content: application/json: schema: $ref: '#/components/schemas/Workflow' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '500': $ref: '#/components/responses/InternalError' /workflows/{workflows_ids}/state: get: tags: [workflows] summary: Состояния нескольких workflow description: > Возвращает отображение `workflow_id -> state` для списка workflow. `workflows_ids` — UUID через запятую. operationId: GetStates parameters: - $ref: '#/components/parameters/AuthorizationHeader' - name: workflows_ids in: path required: true description: UUID workflow через запятую schema: type: string example: "3fa85f64-5717-4562-b3fc-2c963f66afa6,4fa85f64-5717-4562-b3fc-2c963f66afa6" responses: '200': description: Отображение id -> состояние content: application/json: schema: type: object additionalProperties: $ref: '#/components/schemas/CompletenessState' example: { "3fa85f64-5717-4562-b3fc-2c963f66afa6": "done" } '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '500': $ref: '#/components/responses/InternalError' /workflows/{workflow_id}/position: get: tags: [workflows] summary: Позиция workflow в очереди description: Возвращает позицию workflow в очереди на выполнение (ограничение 100). operationId: GetWorkflowQueuePosition parameters: - $ref: '#/components/parameters/AuthorizationHeader' - name: workflow_id in: path required: true schema: type: string format: uuid responses: '200': description: Позиция в очереди content: application/json: schema: type: object properties: position: type: string '400': $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' '500': $ref: '#/components/responses/InternalError' /workflows/{workflow_id}/prioritize: post: tags: [workflows] summary: Приоритизировать workflow description: Повышает приоритет workflow. Доступно только суперпользователю. operationId: PrioritizeWorkflow parameters: - $ref: '#/components/parameters/AuthorizationHeader' - name: workflow_id in: path required: true schema: type: string format: uuid responses: '204': description: Успешно, тело отсутствует '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '500': $ref: '#/components/responses/InternalError' /tasks/{task_id}/restart: post: tags: [tasks] summary: Перезапустить задачу description: > Перезапускает задачу, опционально переопределяя `inputs`, `outputs`, `parameters`, `valid_until`. Возвращает обновлённый workflow. operationId: RestartTask parameters: - $ref: '#/components/parameters/AuthorizationHeader' - name: task_id in: path required: true schema: type: string format: uuid requestBody: required: false content: application/json: schema: $ref: '#/components/schemas/RestartTaskRequest' responses: '200': description: Обновлённый workflow content: application/json: schema: $ref: '#/components/schemas/Workflow' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '500': $ref: '#/components/responses/InternalError' /tasks/{task_id}/move_to_super_high_resources: post: tags: [tasks] summary: Перевести задачу на super-high-resources description: Перемещает задачу на пул ресурсов super-high-resources. Только суперпользователь. operationId: MoveTaskToSuperHighResources parameters: - $ref: '#/components/parameters/AuthorizationHeader' - name: task_id in: path required: true schema: type: string format: uuid responses: '204': description: Успешно, тело отсутствует '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '500': $ref: '#/components/responses/InternalError' /tasks-runs/{task_run_id}/cancel: post: tags: [task-runs] summary: Отменить запуск задачи description: Отменяет запуск задачи (task run). Возвращает пустой объект. operationId: CancelTaskRun parameters: - $ref: '#/components/parameters/AuthorizationHeader' - name: task_run_id in: path required: true schema: type: string format: uuid responses: '200': description: Успешно (пустой объект) content: application/json: schema: type: object '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '500': $ref: '#/components/responses/InternalError' components: parameters: AuthorizationHeader: name: Authorization in: header required: true description: > `Bearer ` — токен Sarex. Альтернативно можно передать заголовок `Identity: Bearer ` (токен Zitadel). Не требуется для группы `/internal`. schema: type: string example: "Bearer eyJhbGciOi..." responses: BadRequest: description: Некорректный запрос (парсинг/валидация/конфигурация workflow) content: application/json: schema: $ref: '#/components/schemas/AppError' Unauthorized: description: Не авторизован (нет/некорректный токен) content: application/json: schema: $ref: '#/components/schemas/AppError' Forbidden: description: Доступ запрещён content: application/json: schema: $ref: '#/components/schemas/AppError' NotFound: description: Ресурс не найден content: application/json: schema: $ref: '#/components/schemas/AppError' InternalError: description: Внутренняя ошибка content: application/json: schema: $ref: '#/components/schemas/AppError' schemas: AppError: type: object properties: message: type: string example: "not found workflow" error_code: type: string description: "Код ошибки: WF-0000..WF-0005" example: "WF-0001" CompletenessState: type: string description: Состояние workflow enum: [done, running, error] ExecutionState: type: string description: Состояние запуска задачи (task run) enum: [pending, done, canceling, canceled, idle, running, error, lost] Description: type: object description: Описание входа/выхода задачи (источник/приёмник данных) required: [type, path] properties: type: type: string maxLength: 32 description: "Тип хранилища (валидация: google, local, s3, s3v2, srx-tmp, url, pdm)" example: s3 path: type: string maxLength: 512 ServicePublicDescription: type: object properties: type: type: string kind: type: string Resources: type: object properties: cpu_limits: type: string memory_limits: type: string cpu_requests: type: string memory_requests: type: string Execution: type: object properties: executor: type: string description: "Исполнитель задачи (допустимые: k8s, amqp)" enum: [k8s, amqp] resources: $ref: '#/components/schemas/Resources' Task: type: object properties: id: type: string format: uuid workflow_id: type: string format: uuid slug: type: string docker_image: type: string backoff_limit: type: integer format: int32 description: "По умолчанию 5, если не задан" inputs: type: object additionalProperties: $ref: '#/components/schemas/Description' outputs: type: object additionalProperties: $ref: '#/components/schemas/Description' parameters: type: object additionalProperties: true service_requests: type: array items: type: string services: type: array items: $ref: '#/components/schemas/ServicePublicDescription' execution: $ref: '#/components/schemas/Execution' needs: type: array items: type: string layer: type: integer nullable: true created_at: type: string format: date-time updated_at: type: string format: date-time TaskRun: type: object properties: id: type: string format: uuid task_id: type: string format: uuid workflow_id: type: string format: uuid state: $ref: '#/components/schemas/ExecutionState' reason: type: string nullable: true progress: type: integer format: int32 nullable: true total_time: type: integer format: int32 nullable: true logs_paths: type: array items: type: string logs_storage: type: string nullable: true logs_last_gathered: type: string format: date-time nullable: true created_at: type: string format: date-time updated_at: type: string format: date-time Workflow: type: object properties: id: type: string format: uuid company_id: type: integer state: $ref: '#/components/schemas/CompletenessState' name: type: string valid_until: type: string format: date-time document_id: type: integer nullable: true created_at: type: string format: date-time updated_at: type: string format: date-time tasks: type: array items: $ref: '#/components/schemas/Task' task_runs: type: array items: $ref: '#/components/schemas/TaskRun' CreateWorkflowRequest: type: object required: [name, valid_until] properties: name: type: string valid_until: type: string format: date-time document_id: type: integer nullable: true tasks: type: array items: $ref: '#/components/schemas/Task' RestartTaskRequest: type: object properties: inputs: type: object additionalProperties: $ref: '#/components/schemas/Description' outputs: type: object additionalProperties: $ref: '#/components/schemas/Description' parameters: type: object additionalProperties: true valid_until: type: string format: date-time GetBatchWorkflowsRequest: type: object required: [workflows_ids] properties: workflows_ids: type: array items: type: string format: uuid WorkflowsBatchResponse: type: object properties: workflows: type: array items: $ref: '#/components/schemas/Workflow' WorkflowsResponse: type: object properties: items: type: array items: $ref: '#/components/schemas/Workflow' limit: type: integer offset: type: integer total: type: integer