iac/apps/processing/workflows-api.openapi.yaml

756 lines
26 KiB
YAML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 <JWT>`** — токен Sarex, подпись проверяется публичным ключом
из переменной `PUBLIC_KEY` (RS/PKIX). Из claims извлекаются `user_id`, `company_ids`,
`is_superuser`.
- **`Identity: Bearer <JWT>`** — токен 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 <JWT>` — токен Sarex. Альтернативно можно передать заголовок
`Identity: Bearer <JWT>` (токен 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