docs: update architecture and API documentation to clarify job processing, in-memory registry usage, and future broker integration plans. Enhance OpenAPI specifications for enrollment responses and refine quickstart instructions for Docker setups.
CI / changes (push) Successful in 4s
CI / openapi (push) Successful in 24s
CI / go (push) Successful in 22s
CI / bird2 (push) Successful in 13s
CI / docker-images (deploy/docker/bird2/Dockerfile, evobgp-bird2) (push) Successful in 38s
CI / docker-images (deploy/docker/evobgp-agent/Dockerfile, evobgp-agent) (push) Successful in 1m8s
CI / docker-images (deploy/docker/evobgp-web/Dockerfile, evobgp-web) (push) Successful in 49s
CI / docker-images (evobgp-all, 1, deploy/docker/gobinary/Dockerfile, evobgp-all) (push) Successful in 1m30s
CI / docker-images (evobgp-api, 1, deploy/docker/gobinary/Dockerfile, evobgp-api) (push) Successful in 1m31s
CI / docker-images (evobgp-ingest, 0, deploy/docker/gobinary/Dockerfile, evobgp-ingest) (push) Has been cancelled
CI / docker-images (evobgp-deploy, 0, deploy/docker/gobinary/Dockerfile, evobgp-deploy) (push) Has been cancelled
CI / docker-images (evobgp-node, 0, deploy/docker/gobinary/Dockerfile, evobgp-node) (push) Has been cancelled
CI / docker-images (evobgp-render, 0, deploy/docker/gobinary/Dockerfile, evobgp-render) (push) Has been cancelled
CI / docker-images (evobgp-scheduler, 0, deploy/docker/gobinary/Dockerfile, evobgp-scheduler) (push) Has been cancelled
CI / changes (push) Successful in 4s
CI / openapi (push) Successful in 24s
CI / go (push) Successful in 22s
CI / bird2 (push) Successful in 13s
CI / docker-images (deploy/docker/bird2/Dockerfile, evobgp-bird2) (push) Successful in 38s
CI / docker-images (deploy/docker/evobgp-agent/Dockerfile, evobgp-agent) (push) Successful in 1m8s
CI / docker-images (deploy/docker/evobgp-web/Dockerfile, evobgp-web) (push) Successful in 49s
CI / docker-images (evobgp-all, 1, deploy/docker/gobinary/Dockerfile, evobgp-all) (push) Successful in 1m30s
CI / docker-images (evobgp-api, 1, deploy/docker/gobinary/Dockerfile, evobgp-api) (push) Successful in 1m31s
CI / docker-images (evobgp-ingest, 0, deploy/docker/gobinary/Dockerfile, evobgp-ingest) (push) Has been cancelled
CI / docker-images (evobgp-deploy, 0, deploy/docker/gobinary/Dockerfile, evobgp-deploy) (push) Has been cancelled
CI / docker-images (evobgp-node, 0, deploy/docker/gobinary/Dockerfile, evobgp-node) (push) Has been cancelled
CI / docker-images (evobgp-render, 0, deploy/docker/gobinary/Dockerfile, evobgp-render) (push) Has been cancelled
CI / docker-images (evobgp-scheduler, 0, deploy/docker/gobinary/Dockerfile, evobgp-scheduler) (push) Has been cancelled
This commit is contained in:
1 parent
c6687b2c91
commit
acc0608432
18 files changed
+110
-96
No files matched your search
@@ -118,7 +118,7 @@ EvoBGP/
|
|||||||
│ ├── repository/ # SQL по сущностям (tenant, module, peer, revision, jobs, …)
|
│ ├── repository/ # SQL по сущностям (tenant, module, peer, revision, jobs, …)
|
||||||
│ ├── domain/ # типы и правила без I/O (префиксы, community, ревизии)
|
│ ├── domain/ # типы и правила без I/O (префиксы, community, ревизии)
|
||||||
│ ├── httpapi/ # роуты OpenAPI, middleware, валидация, маппинг в сервисы
|
│ ├── httpapi/ # роуты OpenAPI, middleware, валидация, маппинг в сервисы
|
||||||
│ ├── jobs/ # очередь: брокер (reference) vs PG + SKIP LOCKED (microVPS)
|
│ ├── jobs/ # задачи: сейчас in-memory Registry в процессе API; PG job_audit / брокер — целевое расширение (см. примечание ниже §2)
|
||||||
│ ├── scheduler/ # триггеры по расписанию модулей
|
│ ├── scheduler/ # триггеры по расписанию модулей
|
||||||
│ ├── ingest/ # CDN, DoH, нормализация, запись в БД
|
│ ├── ingest/ # CDN, DoH, нормализация, запись в БД
|
||||||
│ ├── render/ # материализация префиксов, ревизия, текст артефактов BIRD
|
│ ├── render/ # материализация префиксов, ревизия, текст артефактов BIRD
|
||||||
@@ -237,6 +237,8 @@ flowchart LR
|
|||||||
|
|
||||||
> **Правило:** логика домена **одинакова**; **один движок БД — PostgreSQL** (одинаковые миграции и SQL). Отличаются число процессов Go, наличие брокера и **настройки** PG.
|
> **Правило:** логика домена **одинакова**; **один движок БД — PostgreSQL** (одинаковые миграции и SQL). Отличаются число процессов Go, наличие брокера и **настройки** PG.
|
||||||
|
|
||||||
|
> **Примечание (текущий код vs целевая очередь):** исполнение async jobs идёт через **in-memory `jobs.Registry` в процессе `evobgp-api` / `evobgp-all`**; таблица `job_audit` в миграциях заложена под будущую персистенцию и идемпотентность между процессами. В **reference** Compose отдельный `evobgp-scheduler` **не** читает эту очередь, а вызывает `POST .../modules/{id}/refresh` по HTTP. NATS в compose — для будущей интеграции; см. [docs/architecture.md](../../docs/architecture.md).
|
||||||
|
|
||||||
> **Опция `microVPS_sqlite`:** один контейнер `evobgp-all` без PG — только если критичен абсолютный минимум контейнеров; иначе **не рекомендуется** как основной путь.
|
> **Опция `microVPS_sqlite`:** один контейнер `evobgp-all` без PG — только если критичен абсолютный минимум контейнеров; иначе **не рекомендуется** как основной путь.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import (
|
|||||||
"evobgp/internal/scheduler"
|
"evobgp/internal/scheduler"
|
||||||
)
|
)
|
||||||
|
|
||||||
// microVPS entrypoint: one process — HTTP API plus in-process scheduler, ingest, render, deploy workers (shared store + job registry).
|
// microVPS entrypoint: один процесс — HTTP API и фоновые воркеры scheduler, ingest, render, deploy (общий store и jobs.Registry).
|
||||||
func main() {
|
func main() {
|
||||||
cfg := config.Load()
|
cfg := config.Load()
|
||||||
opts := httpapi.Options{
|
opts := httpapi.Options{
|
||||||
|
|||||||
@@ -45,7 +45,10 @@ func main() {
|
|||||||
Store: st,
|
Store: st,
|
||||||
HTTP: &http.Client{Timeout: 45 * time.Second},
|
HTTP: &http.Client{Timeout: 45 * time.Second},
|
||||||
}
|
}
|
||||||
if apiBase != "" && apiTok != "" {
|
if (apiBase != "") != (apiTok != "") {
|
||||||
|
log.Fatal("evobgp-scheduler: set both EVOBGP_CONTROL_PLANE_URL and EVOBGP_SCHEDULER_BEARER for HTTP mode, or leave both empty for in-process job registry")
|
||||||
|
}
|
||||||
|
if apiBase != "" {
|
||||||
deps.APIBase = apiBase
|
deps.APIBase = apiBase
|
||||||
deps.APIToken = apiTok
|
deps.APIToken = apiTok
|
||||||
log.Printf("evobgp-scheduler: control plane HTTP mode (%s)", apiBase)
|
log.Printf("evobgp-scheduler: control plane HTTP mode (%s)", apiBase)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
# docker compose --profile microvps up -d --build
|
# docker compose --profile microvps up -d --build
|
||||||
#
|
#
|
||||||
# reference: 5× Go (api + scheduler + ingest + render + deploy) + postgres + NATS JetStream + bird2 + agent + web.
|
# reference: 5× Go (api + scheduler + ingest + render + deploy) + postgres + NATS JetStream + bird2 + agent + web.
|
||||||
|
# Async jobs выполняются in-process в evobgp-api (jobs.Registry); NATS — задел под брокер (см. docs/architecture.md).
|
||||||
# microvps: evobgp-all + postgres + bird2 + agent (без брокера).
|
# microvps: evobgp-all + postgres + bird2 + agent (без брокера).
|
||||||
#
|
#
|
||||||
# Опция microVPS_sqlite из плана (без контейнера Postgres): требует реализации store на SQLite в приложении;
|
# Опция microVPS_sqlite из плана (без контейнера Postgres): требует реализации store на SQLite в приложении;
|
||||||
|
|||||||
@@ -38,7 +38,7 @@
|
|||||||
| `birddeploy` | Логика применения конфигурации к BIRD (используется в цепочке деплоя). |
|
| `birddeploy` | Логика применения конфигурации к BIRD (используется в цепочке деплоя). |
|
||||||
| `config` | Переменные окружения `EVOBGP_*`. |
|
| `config` | Переменные окружения `EVOBGP_*`. |
|
||||||
| `observability` | Метрики Prometheus, HTTP middleware. |
|
| `observability` | Метрики Prometheus, HTTP middleware. |
|
||||||
| `broker` | Заготовка под NATS/Redis (логирование подключения в воркерах). |
|
| `broker` | Опциональный `EVOBGP_BROKER_URL` для будущей шины; сейчас задачи только in-process (`jobs.Registry`), пакет лишь логирует факт настройки URL. |
|
||||||
| `pipeline` | Ingest+render в одном шаге для `module_refresh`: выборка префиксов (CDN/AS/IP/пустые DOMAINS), `CreateRenderRevision`, превью BIRD через `birdfmt`. |
|
| `pipeline` | Ingest+render в одном шаге для `module_refresh`: выборка префиксов (CDN/AS/IP/пустые DOMAINS), `CreateRenderRevision`, превью BIRD через `birdfmt`. |
|
||||||
|
|
||||||
## Диаграмма: эталонный Compose (reference)
|
## Диаграмма: эталонный Compose (reference)
|
||||||
|
|||||||
+4
-4
@@ -3210,15 +3210,15 @@ data-styled.g138[id="sc-iJQrDi"]{content:"gtHWGb,"}/*!sc*/
|
|||||||
" class="sc-iKGpAq sc-cCYyou dXXcln dHaogz"><p>Обмен ключами и привязка к <code>speaker_id</code>; детали протокола уточняются отдельно.</p>
|
" class="sc-iKGpAq sc-cCYyou dXXcln dHaogz"><p>Обмен ключами и привязка к <code>speaker_id</code>; детали протокола уточняются отдельно.</p>
|
||||||
</div></div><div class="sc-ftWlEC pzAgN"><div class="sc-cYRmzp kuPPCO"><h5 class="sc-irTswZ sc-jOQpHb fA-dGxt qtYpT">Authorizations:</h5><svg class="sc-fbJfz eiOVYa" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0" aria-hidden="true"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></div><div class="sc-jTsRVM gZngKQ"><span class="sc-dskThK ddMcul"><span class="sc-hNeXkj cLOXvd"><i>bearerAuth</i></span></span></div></div><h5 class="sc-irTswZ fA-dGxt">Request Body schema: <span class="sc-gXSCqT bzXJk">application/json</span><div class="sc-gUrTyB sc-eKYjSU sc-dVCGSo bPAaet boAHLn iuCmqI">required</div></h5><div html="" class="sc-iKGpAq sc-cCYyou dXXcln dHaogz"></div><table class="sc-hIqOWV dybric"><tbody><tr class=""><td kind="field" title="public_key" class="sc-hAtEya sc-fGFwA-d kddqHa eIujjv"><span class="sc-ieZDjf bEKmDE"></span><span class="property-name">public_key</span></td><td class="sc-blLsxE bmauLr"><div><div><span class="sc-gUrTyB sc-kZGvTq bPAaet UMvzF"></span><span class="sc-gUrTyB sc-iMfspz bPAaet fEQrHJ">string</span></div> <div><div html="" class="sc-iKGpAq sc-cCYyou dXXcln cFvDiF"></div></div></div></td></tr><tr class=""><td kind="field" title="speaker_id" class="sc-hAtEya sc-fGFwA-d kddqHa eIujjv"><span class="sc-ieZDjf bEKmDE"></span><span class="property-name">speaker_id</span></td><td class="sc-blLsxE bmauLr"><div><div><span class="sc-gUrTyB sc-kZGvTq bPAaet UMvzF"></span><span class="sc-gUrTyB sc-iMfspz bPAaet fEQrHJ">string</span><span class="sc-gUrTyB sc-jfSnVt bPAaet kEFcTG"> (<!-- -->ResourceId<!-- -->) </span><span class="sc-gUrTyB sc-ejdXBF bPAaet gTwxVV">^[0-9A-Za-z_-]{20,36}$</span></div> <div><div html="<p>UUID v7 или ULID.</p>
|
</div></div><div class="sc-ftWlEC pzAgN"><div class="sc-cYRmzp kuPPCO"><h5 class="sc-irTswZ sc-jOQpHb fA-dGxt qtYpT">Authorizations:</h5><svg class="sc-fbJfz eiOVYa" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0" aria-hidden="true"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></div><div class="sc-jTsRVM gZngKQ"><span class="sc-dskThK ddMcul"><span class="sc-hNeXkj cLOXvd"><i>bearerAuth</i></span></span></div></div><h5 class="sc-irTswZ fA-dGxt">Request Body schema: <span class="sc-gXSCqT bzXJk">application/json</span><div class="sc-gUrTyB sc-eKYjSU sc-dVCGSo bPAaet boAHLn iuCmqI">required</div></h5><div html="" class="sc-iKGpAq sc-cCYyou dXXcln dHaogz"></div><table class="sc-hIqOWV dybric"><tbody><tr class=""><td kind="field" title="public_key" class="sc-hAtEya sc-fGFwA-d kddqHa eIujjv"><span class="sc-ieZDjf bEKmDE"></span><span class="property-name">public_key</span></td><td class="sc-blLsxE bmauLr"><div><div><span class="sc-gUrTyB sc-kZGvTq bPAaet UMvzF"></span><span class="sc-gUrTyB sc-iMfspz bPAaet fEQrHJ">string</span></div> <div><div html="" class="sc-iKGpAq sc-cCYyou dXXcln cFvDiF"></div></div></div></td></tr><tr class=""><td kind="field" title="speaker_id" class="sc-hAtEya sc-fGFwA-d kddqHa eIujjv"><span class="sc-ieZDjf bEKmDE"></span><span class="property-name">speaker_id</span></td><td class="sc-blLsxE bmauLr"><div><div><span class="sc-gUrTyB sc-kZGvTq bPAaet UMvzF"></span><span class="sc-gUrTyB sc-iMfspz bPAaet fEQrHJ">string</span><span class="sc-gUrTyB sc-jfSnVt bPAaet kEFcTG"> (<!-- -->ResourceId<!-- -->) </span><span class="sc-gUrTyB sc-ejdXBF bPAaet gTwxVV">^[0-9A-Za-z_-]{20,36}$</span></div> <div><div html="<p>UUID v7 или ULID.</p>
|
||||||
" class="sc-iKGpAq sc-cCYyou dXXcln cFvDiF"><p>UUID v7 или ULID.</p>
|
" class="sc-iKGpAq sc-cCYyou dXXcln cFvDiF"><p>UUID v7 или ULID.</p>
|
||||||
</div></div></div></td></tr><tr class="last "><td kind="additionalProperties" title="property name*" class="sc-hAtEya sc-fGFwA-d kddqHa eIujjv"><span class="sc-ieZDjf bEKmDE"></span><span class="property-name">property name*</span><div class="sc-gUrTyB sc-eKYjSU sc-oQLfz bPAaet boAHLn llEodL">additional property</div></td><td class="sc-blLsxE bmauLr"><div><div><span class="sc-gUrTyB sc-kZGvTq bPAaet UMvzF"></span><span class="sc-gUrTyB sc-iMfspz bPAaet fEQrHJ">any</span></div> <div><div html="" class="sc-iKGpAq sc-cCYyou dXXcln cFvDiF"></div></div></div></td></tr></tbody></table><div><h3 class="sc-fJjTez hsJdXF">Responses</h3><div><button class="sc-caslwi brztng"><svg class="sc-fbJfz iZiZiV" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0" aria-hidden="true"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong class="sc-fYaxgW jJkGwY">200<!-- --> </strong><div html="<p>Успешная регистрация (плейсхолдер).</p>
|
</div></div></div></td></tr><tr class="last "><td kind="additionalProperties" title="property name*" class="sc-hAtEya sc-fGFwA-d kddqHa eIujjv"><span class="sc-ieZDjf bEKmDE"></span><span class="property-name">property name*</span><div class="sc-gUrTyB sc-eKYjSU sc-oQLfz bPAaet boAHLn llEodL">additional property</div></td><td class="sc-blLsxE bmauLr"><div><div><span class="sc-gUrTyB sc-kZGvTq bPAaet UMvzF"></span><span class="sc-gUrTyB sc-iMfspz bPAaet fEQrHJ">any</span></div> <div><div html="" class="sc-iKGpAq sc-cCYyou dXXcln cFvDiF"></div></div></div></td></tr></tbody></table><div><h3 class="sc-fJjTez hsJdXF">Responses</h3><div><button class="sc-caslwi brztng"><svg class="sc-fbJfz iZiZiV" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0" aria-hidden="true"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong class="sc-fYaxgW jJkGwY">200<!-- --> </strong><div html="<p>Enrollment записан (node_public_key и время в meta спикера при наличии ключа).</p>
|
||||||
" class="sc-iKGpAq sc-cCYyou sc-cjERFZ dXXcln fTBBlJ dkmSdy"><p>Успешная регистрация (плейсхолдер).</p>
|
" class="sc-iKGpAq sc-cCYyou sc-cjERFZ dXXcln fTBBlJ dkmSdy"><p>Enrollment записан (node_public_key и время в meta спикера при наличии ключа).</p>
|
||||||
</div></button></div><div><button class="sc-caslwi fvWYOy"><svg class="sc-fbJfz kiMFkB" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0" aria-hidden="true"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong class="sc-fYaxgW jJkGwY">422<!-- --> </strong><div html="<p>Семантическая ошибка запроса или валидации полей.</p>
|
</div></button></div><div><button class="sc-caslwi fvWYOy"><svg class="sc-fbJfz kiMFkB" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0" aria-hidden="true"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong class="sc-fYaxgW jJkGwY">422<!-- --> </strong><div html="<p>Семантическая ошибка запроса или валидации полей.</p>
|
||||||
" class="sc-iKGpAq sc-cCYyou sc-cjERFZ dXXcln fTBBlJ dkmSdy"><p>Семантическая ошибка запроса или валидации полей.</p>
|
" class="sc-iKGpAq sc-cCYyou sc-cjERFZ dXXcln fTBBlJ dkmSdy"><p>Семантическая ошибка запроса или валидации полей.</p>
|
||||||
</div></button></div><div><button class="sc-caslwi fvWYOy"><svg class="sc-fbJfz kiMFkB" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0" aria-hidden="true"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong class="sc-fYaxgW jJkGwY">default<!-- --> </strong><div html="<p>Ошибка (см. тело Problem).</p>
|
</div></button></div><div><button class="sc-caslwi fvWYOy"><svg class="sc-fbJfz kiMFkB" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0" aria-hidden="true"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong class="sc-fYaxgW jJkGwY">default<!-- --> </strong><div html="<p>Ошибка (см. тело Problem).</p>
|
||||||
" class="sc-iKGpAq sc-cCYyou sc-cjERFZ dXXcln fTBBlJ dkmSdy"><p>Ошибка (см. тело Problem).</p>
|
" class="sc-iKGpAq sc-cCYyou sc-cjERFZ dXXcln fTBBlJ dkmSdy"><p>Ошибка (см. тело Problem).</p>
|
||||||
</div></button></div></div></div><div class="sc-jTrPJt sc-gLDzao dVngAA fYLqku"><div class="sc-fYzRkH dHdMVa"><button class="sc-jYvNnh iWrBta"><span type="post" class="sc-eGFuAY ejslkd http-verb post">post</span><span class="sc-GJyyy dkiPkt">/v1/nodes/enroll</span><svg class="sc-fbJfz ivEQut" style="margin-right:-25px" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0" aria-hidden="true"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></button><div aria-hidden="true" class="sc-fnxdBX dplsyJ"><div class="sc-llcuoK cNCbuV"><div html="<p>Базовый URL инсталляции (замените на свой)</p>
|
</div></button></div></div></div><div class="sc-jTrPJt sc-gLDzao dVngAA fYLqku"><div class="sc-fYzRkH dHdMVa"><button class="sc-jYvNnh iWrBta"><span type="post" class="sc-eGFuAY ejslkd http-verb post">post</span><span class="sc-GJyyy dkiPkt">/v1/nodes/enroll</span><svg class="sc-fbJfz ivEQut" style="margin-right:-25px" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0" aria-hidden="true"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></button><div aria-hidden="true" class="sc-fnxdBX dplsyJ"><div class="sc-llcuoK cNCbuV"><div html="<p>Базовый URL инсталляции (замените на свой)</p>
|
||||||
" class="sc-iKGpAq sc-cCYyou dXXcln cFvDiF"><p>Базовый URL инсталляции (замените на свой)</p>
|
" class="sc-iKGpAq sc-cCYyou dXXcln cFvDiF"><p>Базовый URL инсталляции (замените на свой)</p>
|
||||||
</div><div tabindex="0" role="button"><div class="sc-jnsZEx eobUac"><span>https://api.example.com</span>/v1/nodes/enroll</div></div></div></div></div><div><h3 class="sc-kFuwaQ dEbuTz"> <!-- -->Request samples<!-- --> </h3><div class="sc-cyRfQY lbIFgo" data-rttabs="true"><ul class="react-tabs__tab-list" role="tablist"><li class="react-tabs__tab react-tabs__tab--selected" role="tab" id="tab_R_157la_0" aria-selected="true" aria-disabled="false" aria-controls="panel_R_157la_0" tabindex="0" data-rttab="true">Payload</li></ul><div class="react-tabs__tab-panel react-tabs__tab-panel--selected" role="tabpanel" id="panel_R_157la_0" aria-labelledby="tab_R_157la_0"><div><div class="sc-cPlDXk gpxHhK"><span class="sc-bCDidX ccmcKc">Content type</span><div class="sc-dQelHO eMpCUl">application/json</div></div><div class="sc-hVkBjf ksuOBo"><div class="sc-cRZddz iLjyyA"><div class="sc-gjTGSz btblAa"><button><div class="sc-jegxcw fJsoyS">Copy</div></button></div><div tabindex="0" class="sc-iKGpAq dXXcln sc-jMAIzW jKIGwd"><div class="redoc-json"><code><button class="collapser" aria-label="collapse"></button><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable "><span class="property token string">"public_key"</span>: <span class="token string">"string"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"speaker_id"</span>: <span class="token string">"01JQXYZABCDEFGHIJKLMNOPQRS"</span></div></li></ul><span class="token punctuation">}</span></code></div></div></div></div></div></div></div></div><div><h3 class="sc-kFuwaQ dEbuTz"> <!-- -->Response samples<!-- --> </h3><div class="sc-cyRfQY lbIFgo" data-rttabs="true"><ul class="react-tabs__tab-list" role="tablist"><li class="tab-success react-tabs__tab--selected" role="tab" id="tab_R_177la_0" aria-selected="true" aria-disabled="false" aria-controls="panel_R_177la_0" tabindex="0" data-rttab="true">200</li><li class="tab-error" role="tab" id="tab_R_177la_1" aria-selected="false" aria-disabled="false" aria-controls="panel_R_177la_1" data-rttab="true">422</li><li class="tab-error" role="tab" id="tab_R_177la_2" aria-selected="false" aria-disabled="false" aria-controls="panel_R_177la_2" data-rttab="true">default</li></ul><div class="react-tabs__tab-panel react-tabs__tab-panel--selected" role="tabpanel" id="panel_R_177la_0" aria-labelledby="tab_R_177la_0"><div><div class="sc-cPlDXk gpxHhK"><span class="sc-bCDidX ccmcKc">Content type</span><div class="sc-dQelHO eMpCUl">application/json</div></div><div class="sc-hVkBjf ksuOBo"><div class="sc-cRZddz iLjyyA"><div class="sc-gjTGSz btblAa"><button><div class="sc-jegxcw fJsoyS">Copy</div></button></div><div tabindex="0" class="sc-iKGpAq dXXcln sc-jMAIzW jKIGwd"><div class="redoc-json"><code><span class="token punctuation">{ }</span></code></div></div></div></div></div></div><div class="react-tabs__tab-panel" role="tabpanel" id="panel_R_177la_1" aria-labelledby="tab_R_177la_1"></div><div class="react-tabs__tab-panel" role="tabpanel" id="panel_R_177la_2" aria-labelledby="tab_R_177la_2"></div></div></div></div></div></div><div id="tag/Settings" data-section-id="tag/Settings" class="sc-eDDNvO eTiIZG"><div class="sc-iAEyYj cmAaWK"><div class="sc-hLseeT hoYmkG"><h2 class="sc-qRumy bcNKdh"><a class="sc-csCMJq jSIqAu" href="#tag/Settings" aria-label="tag/Settings"></a>Settings</h2></div></div><div class="sc-hLseeT eiTXyS"><div class="sc-iKGpAq sc-cCYyou dXXcln dHaogz redoc-markdown " html="<p>Глобальные настройки и feature flags; изменение - только operator.</p>
|
</div><div tabindex="0" role="button"><div class="sc-jnsZEx eobUac"><span>https://api.example.com</span>/v1/nodes/enroll</div></div></div></div></div><div><h3 class="sc-kFuwaQ dEbuTz"> <!-- -->Request samples<!-- --> </h3><div class="sc-cyRfQY lbIFgo" data-rttabs="true"><ul class="react-tabs__tab-list" role="tablist"><li class="react-tabs__tab react-tabs__tab--selected" role="tab" id="tab_R_157la_0" aria-selected="true" aria-disabled="false" aria-controls="panel_R_157la_0" tabindex="0" data-rttab="true">Payload</li></ul><div class="react-tabs__tab-panel react-tabs__tab-panel--selected" role="tabpanel" id="panel_R_157la_0" aria-labelledby="tab_R_157la_0"><div><div class="sc-cPlDXk gpxHhK"><span class="sc-bCDidX ccmcKc">Content type</span><div class="sc-dQelHO eMpCUl">application/json</div></div><div class="sc-hVkBjf ksuOBo"><div class="sc-cRZddz iLjyyA"><div class="sc-gjTGSz btblAa"><button><div class="sc-jegxcw fJsoyS">Copy</div></button></div><div tabindex="0" class="sc-iKGpAq dXXcln sc-jMAIzW jKIGwd"><div class="redoc-json"><code><button class="collapser" aria-label="collapse"></button><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable "><span class="property token string">"public_key"</span>: <span class="token string">"string"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"speaker_id"</span>: <span class="token string">"01JQXYZABCDEFGHIJKLMNOPQRS"</span></div></li></ul><span class="token punctuation">}</span></code></div></div></div></div></div></div></div></div><div><h3 class="sc-kFuwaQ dEbuTz"> <!-- -->Response samples<!-- --> </h3><div class="sc-cyRfQY lbIFgo" data-rttabs="true"><ul class="react-tabs__tab-list" role="tablist"><li class="tab-success react-tabs__tab--selected" role="tab" id="tab_R_177la_0" aria-selected="true" aria-disabled="false" aria-controls="panel_R_177la_0" tabindex="0" data-rttab="true">200</li><li class="tab-error" role="tab" id="tab_R_177la_1" aria-selected="false" aria-disabled="false" aria-controls="panel_R_177la_1" data-rttab="true">422</li><li class="tab-error" role="tab" id="tab_R_177la_2" aria-selected="false" aria-disabled="false" aria-controls="panel_R_177la_2" data-rttab="true">default</li></ul><div class="react-tabs__tab-panel react-tabs__tab-panel--selected" role="tabpanel" id="panel_R_177la_0" aria-labelledby="tab_R_177la_0"><div><div class="sc-cPlDXk gpxHhK"><span class="sc-bCDidX ccmcKc">Content type</span><div class="sc-dQelHO eMpCUl">application/json</div></div><div class="sc-hVkBjf ksuOBo"><div class="sc-cRZddz iLjyyA"><div class="sc-gjTGSz btblAa"><button><div class="sc-jegxcw fJsoyS">Copy</div></button></div><div tabindex="0" class="sc-iKGpAq dXXcln sc-jMAIzW jKIGwd"><div class="redoc-json"><code><button class="collapser" aria-label="collapse"></button><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable "><span class="property token string">"status"</span>: <span class="token string">"enrolled"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"speaker_id"</span>: <span class="token string">"01JQXYZABCDEFGHIJKLMNOPQRS"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"tenant_id"</span>: <span class="token string">"01JQXYZABCDEFGHIJKLMNOPQRS"</span></div></li></ul><span class="token punctuation">}</span></code></div></div></div></div></div></div><div class="react-tabs__tab-panel" role="tabpanel" id="panel_R_177la_1" aria-labelledby="tab_R_177la_1"></div><div class="react-tabs__tab-panel" role="tabpanel" id="panel_R_177la_2" aria-labelledby="tab_R_177la_2"></div></div></div></div></div></div><div id="tag/Settings" data-section-id="tag/Settings" class="sc-eDDNvO eTiIZG"><div class="sc-iAEyYj cmAaWK"><div class="sc-hLseeT hoYmkG"><h2 class="sc-qRumy bcNKdh"><a class="sc-csCMJq jSIqAu" href="#tag/Settings" aria-label="tag/Settings"></a>Settings</h2></div></div><div class="sc-hLseeT eiTXyS"><div class="sc-iKGpAq sc-cCYyou dXXcln dHaogz redoc-markdown " html="<p>Глобальные настройки и feature flags; изменение - только operator.</p>
|
||||||
"><p>Глобальные настройки и feature flags; изменение - только operator.</p>
|
"><p>Глобальные настройки и feature flags; изменение - только operator.</p>
|
||||||
</div></div></div><div id="tag/Settings/operation/getSettings" data-section-id="tag/Settings/operation/getSettings" class="sc-eDDNvO iUTsUN"><div data-section-id="operation/getSettings" id="operation/getSettings" class="sc-iAEyYj cmAaWK"><div class="sc-hLseeT hoYmkG"><h2 class="sc-qRumy bcNKdh"><a class="sc-csCMJq jSIqAu" href="#tag/Settings/operation/getSettings" aria-label="tag/Settings/operation/getSettings"></a>Получить настройки<!-- --> </h2><div class="sc-iERabE dUdgLx"><div html="<p>KV (лимиты CDN, feature flags и т.д.).</p>
|
</div></div></div><div id="tag/Settings/operation/getSettings" data-section-id="tag/Settings/operation/getSettings" class="sc-eDDNvO iUTsUN"><div data-section-id="operation/getSettings" id="operation/getSettings" class="sc-iAEyYj cmAaWK"><div class="sc-hLseeT hoYmkG"><h2 class="sc-qRumy bcNKdh"><a class="sc-csCMJq jSIqAu" href="#tag/Settings/operation/getSettings" aria-label="tag/Settings/operation/getSettings"></a>Получить настройки<!-- --> </h2><div class="sc-iERabE dUdgLx"><div html="<p>KV (лимиты CDN, feature flags и т.д.).</p>
|
||||||
" class="sc-iKGpAq sc-cCYyou dXXcln dHaogz"><p>KV (лимиты CDN, feature flags и т.д.).</p>
|
" class="sc-iKGpAq sc-cCYyou dXXcln dHaogz"><p>KV (лимиты CDN, feature flags и т.д.).</p>
|
||||||
@@ -3246,7 +3246,7 @@ data-styled.g138[id="sc-iJQrDi"]{content:"gtHWGb,"}/*!sc*/
|
|||||||
" class="sc-iKGpAq sc-cCYyou dXXcln cFvDiF"><p>Базовый URL инсталляции (замените на свой)</p>
|
" class="sc-iKGpAq sc-cCYyou dXXcln cFvDiF"><p>Базовый URL инсталляции (замените на свой)</p>
|
||||||
</div><div tabindex="0" role="button"><div class="sc-jnsZEx eobUac"><span>https://api.example.com</span>/v1/settings</div></div></div></div></div><div><h3 class="sc-kFuwaQ dEbuTz"> <!-- -->Request samples<!-- --> </h3><div class="sc-cyRfQY lbIFgo" data-rttabs="true"><ul class="react-tabs__tab-list" role="tablist"><li class="react-tabs__tab react-tabs__tab--selected" role="tab" id="tab_R_156lq_0" aria-selected="true" aria-disabled="false" aria-controls="panel_R_156lq_0" tabindex="0" data-rttab="true">Payload</li></ul><div class="react-tabs__tab-panel react-tabs__tab-panel--selected" role="tabpanel" id="panel_R_156lq_0" aria-labelledby="tab_R_156lq_0"><div><div class="sc-cPlDXk gpxHhK"><span class="sc-bCDidX ccmcKc">Content type</span><div class="sc-dQelHO eMpCUl">application/json</div></div><div class="sc-hVkBjf ksuOBo"><div class="sc-cRZddz iLjyyA"><div class="sc-gjTGSz btblAa"><button><div class="sc-jegxcw fJsoyS">Copy</div></button></div><div tabindex="0" class="sc-iKGpAq dXXcln sc-jMAIzW jKIGwd"><div class="redoc-json"><code><span class="token punctuation">{ }</span></code></div></div></div></div></div></div></div></div><div><h3 class="sc-kFuwaQ dEbuTz"> <!-- -->Response samples<!-- --> </h3><div class="sc-cyRfQY lbIFgo" data-rttabs="true"><ul class="react-tabs__tab-list" role="tablist"><li class="tab-success react-tabs__tab--selected" role="tab" id="tab_R_176lq_0" aria-selected="true" aria-disabled="false" aria-controls="panel_R_176lq_0" tabindex="0" data-rttab="true">200</li><li class="tab-error" role="tab" id="tab_R_176lq_1" aria-selected="false" aria-disabled="false" aria-controls="panel_R_176lq_1" data-rttab="true">403</li><li class="tab-error" role="tab" id="tab_R_176lq_2" aria-selected="false" aria-disabled="false" aria-controls="panel_R_176lq_2" data-rttab="true">default</li></ul><div class="react-tabs__tab-panel react-tabs__tab-panel--selected" role="tabpanel" id="panel_R_176lq_0" aria-labelledby="tab_R_176lq_0"><div><div class="sc-cPlDXk gpxHhK"><span class="sc-bCDidX ccmcKc">Content type</span><div class="sc-dQelHO eMpCUl">application/json</div></div><div class="sc-hVkBjf ksuOBo"><div class="sc-cRZddz iLjyyA"><div class="sc-gjTGSz btblAa"><button><div class="sc-jegxcw fJsoyS">Copy</div></button></div><div tabindex="0" class="sc-iKGpAq dXXcln sc-jMAIzW jKIGwd"><div class="redoc-json"><code><span class="token punctuation">{ }</span></code></div></div></div></div></div></div><div class="react-tabs__tab-panel" role="tabpanel" id="panel_R_176lq_1" aria-labelledby="tab_R_176lq_1"></div><div class="react-tabs__tab-panel" role="tabpanel" id="panel_R_176lq_2" aria-labelledby="tab_R_176lq_2"></div></div></div></div></div></div></div><div class="sc-emEvRt jYOHCb"></div></div></div>
|
</div><div tabindex="0" role="button"><div class="sc-jnsZEx eobUac"><span>https://api.example.com</span>/v1/settings</div></div></div></div></div><div><h3 class="sc-kFuwaQ dEbuTz"> <!-- -->Request samples<!-- --> </h3><div class="sc-cyRfQY lbIFgo" data-rttabs="true"><ul class="react-tabs__tab-list" role="tablist"><li class="react-tabs__tab react-tabs__tab--selected" role="tab" id="tab_R_156lq_0" aria-selected="true" aria-disabled="false" aria-controls="panel_R_156lq_0" tabindex="0" data-rttab="true">Payload</li></ul><div class="react-tabs__tab-panel react-tabs__tab-panel--selected" role="tabpanel" id="panel_R_156lq_0" aria-labelledby="tab_R_156lq_0"><div><div class="sc-cPlDXk gpxHhK"><span class="sc-bCDidX ccmcKc">Content type</span><div class="sc-dQelHO eMpCUl">application/json</div></div><div class="sc-hVkBjf ksuOBo"><div class="sc-cRZddz iLjyyA"><div class="sc-gjTGSz btblAa"><button><div class="sc-jegxcw fJsoyS">Copy</div></button></div><div tabindex="0" class="sc-iKGpAq dXXcln sc-jMAIzW jKIGwd"><div class="redoc-json"><code><span class="token punctuation">{ }</span></code></div></div></div></div></div></div></div></div><div><h3 class="sc-kFuwaQ dEbuTz"> <!-- -->Response samples<!-- --> </h3><div class="sc-cyRfQY lbIFgo" data-rttabs="true"><ul class="react-tabs__tab-list" role="tablist"><li class="tab-success react-tabs__tab--selected" role="tab" id="tab_R_176lq_0" aria-selected="true" aria-disabled="false" aria-controls="panel_R_176lq_0" tabindex="0" data-rttab="true">200</li><li class="tab-error" role="tab" id="tab_R_176lq_1" aria-selected="false" aria-disabled="false" aria-controls="panel_R_176lq_1" data-rttab="true">403</li><li class="tab-error" role="tab" id="tab_R_176lq_2" aria-selected="false" aria-disabled="false" aria-controls="panel_R_176lq_2" data-rttab="true">default</li></ul><div class="react-tabs__tab-panel react-tabs__tab-panel--selected" role="tabpanel" id="panel_R_176lq_0" aria-labelledby="tab_R_176lq_0"><div><div class="sc-cPlDXk gpxHhK"><span class="sc-bCDidX ccmcKc">Content type</span><div class="sc-dQelHO eMpCUl">application/json</div></div><div class="sc-hVkBjf ksuOBo"><div class="sc-cRZddz iLjyyA"><div class="sc-gjTGSz btblAa"><button><div class="sc-jegxcw fJsoyS">Copy</div></button></div><div tabindex="0" class="sc-iKGpAq dXXcln sc-jMAIzW jKIGwd"><div class="redoc-json"><code><span class="token punctuation">{ }</span></code></div></div></div></div></div></div><div class="react-tabs__tab-panel" role="tabpanel" id="panel_R_176lq_1" aria-labelledby="tab_R_176lq_1"></div><div class="react-tabs__tab-panel" role="tabpanel" id="panel_R_176lq_2" aria-labelledby="tab_R_176lq_2"></div></div></div></div></div></div></div><div class="sc-emEvRt jYOHCb"></div></div></div>
|
||||||
<script>
|
<script>
|
||||||
const __redoc_state = {"menu":{"activeItemIdx":-1},"spec":{"data":{"openapi":"3.1.0","info":{"title":"EvoBGP Control Plane API","version":"0.1.0","description":"REST API управления префиксами, модулями ingest, ревизиями конфигурации BIRD и задачами (async jobs).\n\n**Соглашения:** префикс `/v1`; идентификаторы - UUID v7 или ULID (строки); время - ISO 8601 UTC.\nОшибки - `application/problem+json` ([RFC 9457](https://www.rfc-editor.org/rfc/rfc9457)).\nПагинация списков - `cursor` + `limit`; ответ содержит `items`, `next_cursor`, `has_more`.\n\n**Роли** (матрица доступа): `viewer`, `editor`, `operator`, `node`. Нода использует отдельные пути и ключ с ролью `node`.\n\nЗаголовок `X-Tenant-Id` допускается только для супер-ролей (явный tenant); иначе tenant берётся из API-ключа.\n","license":{"name":"Proprietary","identifier":"LicenseRef-Proprietary"}},"servers":[{"url":"https://api.example.com","description":"Базовый URL инсталляции (замените на свой)"}],"tags":[{"name":"System","description":"Liveness, readiness и метаданные сборки. Обычно без чувствительных данных; доступ может быть шире."},{"name":"Modules","description":"Экземпляры модулей префиксов (AS, CDN, домены, статические IP-диапазоны) и вложенные записи. Чтение - viewer+; изменение - editor+."},{"name":"DoH profiles","description":"Профили DNS-over-HTTPS для модулей типа домены. Секрет в ответах не возвращается."},{"name":"Communities","description":"Справочник BGP community в скоупе tenant."},{"name":"Peers","description":"BGP-пиры и политики; привязка к спикеру или ко всем спикерам."},{"name":"Speakers","description":"Регистрация и метаданные экземпляров BIRD (master/replica/canary)."},{"name":"Revisions","description":"История ревизий конфигурации, снимки префиксов, превью и откат (часто асинхронно)."},{"name":"Deploy","description":"Применение ревизий на спикерах и опциональный reload BIRD. Требуется роль operator."},{"name":"Jobs","description":"Асинхронные задачи; статус и отмена (best-effort)."},{"name":"Node","description":"API для evobgp-node (бандлы ревизий и enrollment). Отдельный ключ или mTLS, роль node."},{"name":"Settings","description":"Глобальные настройки и feature flags; изменение - только operator."}],"security":[{"bearerAuth":[]}],"externalDocs":{"description":"Черновик и контекст API","url":"./evobgp-api-sketches.md"},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"API-ключ в формате `Bearer <token>`. На edge может заменяться или дополняться mTLS."}},"parameters":{"TenantId":{"name":"X-Tenant-Id","in":"header","required":false,"schema":{"$ref":"#/components/schemas/ResourceId"},"description":"Явный tenant (только супер-роли). Без заголовка tenant определяется по ключу."},"IdempotencyKey":{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":256},"description":"Идемпотентность мутаций. Для `POST` apply и части refresh рекомендуется или обязателен по политике продукта."},"Cursor":{"name":"cursor","in":"query","schema":{"type":"string"},"description":"Непрозрачный курсор из предыдущего ответа."},"Limit":{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":500,"default":50},"description":"Размер страницы (по умолчанию 50)."},"ModuleId":{"name":"module_id","in":"path","required":true,"schema":{"$ref":"#/components/schemas/ResourceId"}},"SpeakerId":{"name":"speaker_id","in":"path","required":true,"schema":{"$ref":"#/components/schemas/ResourceId"}},"RevisionId":{"name":"revision_id","in":"path","required":true,"schema":{"$ref":"#/components/schemas/ResourceId"}},"JobId":{"name":"job_id","in":"path","required":true,"schema":{"$ref":"#/components/schemas/ResourceId"}},"PeerId":{"name":"id","in":"path","required":true,"schema":{"$ref":"#/components/schemas/ResourceId"}},"DohProfileId":{"name":"id","in":"path","required":true,"schema":{"$ref":"#/components/schemas/ResourceId"}},"CommLine truncated
|
const __redoc_state = {"menu":{"activeItemIdx":-1},"spec":{"data":{"openapi":"3.1.0","info":{"title":"EvoBGP Control Plane API","version":"0.1.0","description":"REST API управления префиксами, модулями ingest, ревизиями конфигурации BIRD и задачами (async jobs).\n\n**Соглашения:** префикс `/v1`; идентификаторы - UUID v7 или ULID (строки); время - ISO 8601 UTC.\nОшибки - `application/problem+json` ([RFC 9457](https://www.rfc-editor.org/rfc/rfc9457)).\nПагинация списков - `cursor` + `limit`; ответ содержит `items`, `next_cursor`, `has_more`.\n\n**Роли** (матрица доступа): `viewer`, `editor`, `operator`, `node`. Нода использует отдельные пути и ключ с ролью `node`.\n\nЗаголовок `X-Tenant-Id` допускается только для супер-ролей (явный tenant); иначе tenant берётся из API-ключа.\n","license":{"name":"Proprietary","identifier":"LicenseRef-Proprietary"}},"servers":[{"url":"https://api.example.com","description":"Базовый URL инсталляции (замените на свой)"}],"tags":[{"name":"System","description":"Liveness, readiness и метаданные сборки. Обычно без чувствительных данных; доступ может быть шире."},{"name":"Modules","description":"Экземпляры модулей префиксов (AS, CDN, домены, статические IP-диапазоны) и вложенные записи. Чтение - viewer+; изменение - editor+."},{"name":"DoH profiles","description":"Профили DNS-over-HTTPS для модулей типа домены. Секрет в ответах не возвращается."},{"name":"Communities","description":"Справочник BGP community в скоупе tenant."},{"name":"Peers","description":"BGP-пиры и политики; привязка к спикеру или ко всем спикерам."},{"name":"Speakers","description":"Регистрация и метаданные экземпляров BIRD (master/replica/canary)."},{"name":"Revisions","description":"История ревизий конфигурации, снимки префиксов, превью и откат (часто асинхронно)."},{"name":"Deploy","description":"Применение ревизий на спикерах и опциональный reload BIRD. Требуется роль operator."},{"name":"Jobs","description":"Асинхронные задачи; статус и отмена (best-effort)."},{"name":"Node","description":"API для evobgp-node (бандлы ревизий и enrollment). Отдельный ключ или mTLS, роль node."},{"name":"Settings","description":"Глобальные настройки и feature flags; изменение - только operator."}],"security":[{"bearerAuth":[]}],"externalDocs":{"description":"Черновик и контекст API","url":"./evobgp-api-sketches.md"},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"API-ключ в формате `Bearer <token>`. На edge может заменяться или дополняться mTLS."}},"parameters":{"TenantId":{"name":"X-Tenant-Id","in":"header","required":false,"schema":{"$ref":"#/components/schemas/ResourceId"},"description":"Явный tenant (только супер-роли). Без заголовка tenant определяется по ключу."},"IdempotencyKey":{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":256},"description":"Идемпотентность мутаций. Для `POST` apply и части refresh рекомендуется или обязателен по политике продукта."},"Cursor":{"name":"cursor","in":"query","schema":{"type":"string"},"description":"Непрозрачный курсор из предыдущего ответа."},"Limit":{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":500,"default":50},"description":"Размер страницы (по умолчанию 50)."},"ModuleId":{"name":"module_id","in":"path","required":true,"schema":{"$ref":"#/components/schemas/ResourceId"}},"SpeakerId":{"name":"speaker_id","in":"path","required":true,"schema":{"$ref":"#/components/schemas/ResourceId"}},"RevisionId":{"name":"revision_id","in":"path","required":true,"schema":{"$ref":"#/components/schemas/ResourceId"}},"JobId":{"name":"job_id","in":"path","required":true,"schema":{"$ref":"#/components/schemas/ResourceId"}},"PeerId":{"name":"id","in":"path","required":true,"schema":{"$ref":"#/components/schemas/ResourceId"}},"DohProfileId":{"name":"id","in":"path","required":true,"schema":{"$ref":"#/components/schemas/ResourceId"}},"CommLine truncated
|
||||||
|
|
||||||
var container = document.getElementById('redoc');
|
var container = document.getElementById('redoc');
|
||||||
Redoc.hydrate(__redoc_state, container);
|
Redoc.hydrate(__redoc_state, container);
|
||||||
|
|||||||
+10
-2
@@ -699,7 +699,15 @@ components:
|
|||||||
|
|
||||||
NodeEnrollResponse:
|
NodeEnrollResponse:
|
||||||
type: object
|
type: object
|
||||||
description: Ответ enrollment (плейсхолдер).
|
description: Подтверждение записи enrollment (метаданные спикера обновлены).
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
example: enrolled
|
||||||
|
speaker_id:
|
||||||
|
$ref: "#/components/schemas/ResourceId"
|
||||||
|
tenant_id:
|
||||||
|
$ref: "#/components/schemas/ResourceId"
|
||||||
additionalProperties: true
|
additionalProperties: true
|
||||||
|
|
||||||
DohProfilePatch:
|
DohProfilePatch:
|
||||||
@@ -2259,7 +2267,7 @@ paths:
|
|||||||
$ref: "#/components/schemas/NodeEnrollRequest"
|
$ref: "#/components/schemas/NodeEnrollRequest"
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: Успешная регистрация (плейсхолдер).
|
description: Enrollment записан (node_public_key и время в meta спикера при наличии ключа).
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
|
|||||||
+4
-4
@@ -40,7 +40,7 @@ docker login git.shts.su
|
|||||||
| Образ | Назначение | Страница пакета (пример) | Pull |
|
| Образ | Назначение | Страница пакета (пример) | Pull |
|
||||||
|--------|------------|--------------------------|------|
|
|--------|------------|--------------------------|------|
|
||||||
| `evobgp-api` | HTTP API (с `birdc` в образе) | [packages/…/evobgp-api](https://git.shts.su/denozord/-/packages/container/evobgp-api/latest) | `docker pull git.shts.su/denozord/evobgp-api:latest` |
|
| `evobgp-api` | HTTP API (с `birdc` в образе) | [packages/…/evobgp-api](https://git.shts.su/denozord/-/packages/container/evobgp-api/latest) | `docker pull git.shts.su/denozord/evobgp-api:latest` |
|
||||||
| `evobgp-all` | Монолит microVPS: API + заглушки воркеров в одном процессе | [packages/…/evobgp-all](https://git.shts.su/denozord/-/packages/container/evobgp-all/latest) | `docker pull git.shts.su/denozord/evobgp-all:latest` |
|
| `evobgp-all` | Монолит microVPS: API + in-process воркеры scheduler/ingest/render/deploy | [packages/…/evobgp-all](https://git.shts.su/denozord/-/packages/container/evobgp-all/latest) | `docker pull git.shts.su/denozord/evobgp-all:latest` |
|
||||||
| `evobgp-scheduler` | Планировщик (reference) | [packages/…/evobgp-scheduler](https://git.shts.su/denozord/-/packages/container/evobgp-scheduler/latest) | `docker pull git.shts.su/denozord/evobgp-scheduler:latest` |
|
| `evobgp-scheduler` | Планировщик (reference) | [packages/…/evobgp-scheduler](https://git.shts.su/denozord/-/packages/container/evobgp-scheduler/latest) | `docker pull git.shts.su/denozord/evobgp-scheduler:latest` |
|
||||||
| `evobgp-ingest` | Ingest CDN / ETag | [packages/…/evobgp-ingest](https://git.shts.su/denozord/-/packages/container/evobgp-ingest/latest) | `docker pull git.shts.su/denozord/evobgp-ingest:latest` |
|
| `evobgp-ingest` | Ingest CDN / ETag | [packages/…/evobgp-ingest](https://git.shts.su/denozord/-/packages/container/evobgp-ingest/latest) | `docker pull git.shts.su/denozord/evobgp-ingest:latest` |
|
||||||
| `evobgp-render` | Render | [packages/…/evobgp-render](https://git.shts.su/denozord/-/packages/container/evobgp-render/latest) | `docker pull git.shts.su/denozord/evobgp-render:latest` |
|
| `evobgp-render` | Render | [packages/…/evobgp-render](https://git.shts.su/denozord/-/packages/container/evobgp-render/latest) | `docker pull git.shts.su/denozord/evobgp-render:latest` |
|
||||||
@@ -67,7 +67,7 @@ docker run --rm -p 8080:8080 `
|
|||||||
|
|
||||||
## Вариант 1: Docker, профиль microvps
|
## Вариант 1: Docker, профиль microvps
|
||||||
|
|
||||||
Один процесс `evobgp-all` (HTTP API + in-process заглушки воркеров), PostgreSQL, BIRD2, `evobgp-agent`.
|
Один процесс `evobgp-all` (HTTP API + in-process воркеры scheduler, ingest, render, deploy), PostgreSQL, BIRD2, `evobgp-agent`.
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
cd deploy\compose
|
cd deploy\compose
|
||||||
@@ -99,7 +99,7 @@ docker compose --profile microvps down -v
|
|||||||
|
|
||||||
## Вариант 2: Docker, профиль reference
|
## Вариант 2: Docker, профиль reference
|
||||||
|
|
||||||
Эталонное разбиение: отдельные контейнеры `evobgp-api`, `evobgp-scheduler`, `evobgp-ingest`, `evobgp-render`, `evobgp-deploy`, NATS JetStream, веб UI за nginx, опционально Prometheus.
|
Эталонное разбиение: отдельные контейнеры `evobgp-api`, `evobgp-scheduler`, `evobgp-ingest`, `evobgp-render`, `evobgp-deploy`, сервис **NATS JetStream** (в compose; привязка очереди задач к брокеру — следующая итерация), веб UI за nginx, опционально Prometheus.
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
cd deploy\compose
|
cd deploy\compose
|
||||||
@@ -139,7 +139,7 @@ cd <корень-клона-репозитория>
|
|||||||
go run .\cmd\evobgp-api
|
go run .\cmd\evobgp-api
|
||||||
```
|
```
|
||||||
|
|
||||||
Или монолит **microVPS** (тот же API плюс горутины заглушек scheduler/ingest/render/deploy):
|
Или монолит **microVPS** (тот же API плюс горутины тех же воркеров scheduler/ingest/render/deploy):
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
go run .\cmd\evobgp-all
|
go run .\cmd\evobgp-all
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
// Package broker stubs reference-profile message broker wiring (NATS JetStream / Redis; plan §2).
|
// Package broker holds optional hooks for a future shared message bus (NATS JetStream / Redis).
|
||||||
|
// См. docs/architecture.md: в текущей сборке очередь задач in-process в API; EVOBGP_BROKER_URL зарезервирован.
|
||||||
package broker
|
package broker
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -7,12 +8,12 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// LogConnect logs a one-shot connection attempt when EVOBGP_BROKER_URL is set (full consumer/producer TBD).
|
// LogConnect logs once when EVOBGP_BROKER_URL is set. Multi-process job delivery via a broker is not enabled in this build.
|
||||||
func LogConnect(ctx context.Context, brokerURL string) {
|
func LogConnect(ctx context.Context, brokerURL string) {
|
||||||
u := strings.TrimSpace(brokerURL)
|
u := strings.TrimSpace(brokerURL)
|
||||||
if u == "" {
|
if u == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Printf("broker: EVOBGP_BROKER_URL=%q (stub: no JetStream/Streams consumer in this binary yet)", u)
|
log.Printf("broker: EVOBGP_BROKER_URL=%q set; control plane still uses in-process jobs.Registry (no JetStream/Redis consumer in this binary)", u)
|
||||||
_ = ctx
|
_ = ctx
|
||||||
}
|
}
|
||||||
+1
-16
@@ -25,8 +25,7 @@ func Run(ctx context.Context, deps *Deps) {
|
|||||||
log.Printf("evobgp-deploy: EVOBGP_BIRD_ACTIVE_DIR=%q (apply via API jobs when API has same env)", d)
|
log.Printf("evobgp-deploy: EVOBGP_BIRD_ACTIVE_DIR=%q (apply via API jobs when API has same env)", d)
|
||||||
}
|
}
|
||||||
if deps == nil || deps.Store == nil {
|
if deps == nil || deps.Store == nil {
|
||||||
runStub(ctx)
|
log.Fatalf("evobgp-deploy: missing store (pass deploy.Deps from BootstrapWorkers or evobgp-all)")
|
||||||
return
|
|
||||||
}
|
}
|
||||||
t := time.NewTicker(90 * time.Second)
|
t := time.NewTicker(90 * time.Second)
|
||||||
defer t.Stop()
|
defer t.Stop()
|
||||||
@@ -65,17 +64,3 @@ func logDrift(ctx context.Context, st store.Backend) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func runStub(ctx context.Context) {
|
|
||||||
t := time.NewTicker(60 * time.Second)
|
|
||||||
defer t.Stop()
|
|
||||||
log.Printf("evobgp-deploy: idle stub (no store in Deps)")
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
log.Printf("evobgp-deploy: stopped")
|
|
||||||
return
|
|
||||||
case <-t.C:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,6 +5,8 @@ import (
|
|||||||
"crypto/ed25519"
|
"crypto/ed25519"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
@@ -672,14 +674,72 @@ func (s *Server) handleNodeEnroll(w http.ResponseWriter, r *http.Request) {
|
|||||||
if !s.requireNode(w, a) {
|
if !s.requireNode(w, a) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var req map[string]any
|
var body struct {
|
||||||
_ = json.NewDecoder(r.Body).Decode(&req)
|
SpeakerID string `json:"speaker_id"`
|
||||||
|
PublicKey string `json:"public_key"`
|
||||||
|
}
|
||||||
|
dec := json.NewDecoder(r.Body)
|
||||||
|
if err := dec.Decode(&body); err != nil && !errors.Is(err, io.EOF) {
|
||||||
|
writeProblem(w, http.StatusBadRequest, "Bad Request", "invalid JSON body")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sid := strings.TrimSpace(body.SpeakerID)
|
||||||
|
if sid == "" {
|
||||||
|
writeProblem(w, http.StatusUnprocessableEntity, "Unprocessable Entity", "speaker_id is required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sp, err := s.store.GetSpeaker(a.TenantID, sid)
|
||||||
|
if err != nil {
|
||||||
|
writeProblem(w, http.StatusNotFound, "Not Found", "speaker not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
updates := map[string]any{
|
||||||
|
"node_enrolled_at": time.Now().UTC().Format(time.RFC3339Nano),
|
||||||
|
}
|
||||||
|
if pk := strings.TrimSpace(body.PublicKey); pk != "" {
|
||||||
|
updates["node_public_key"] = pk
|
||||||
|
}
|
||||||
|
meta, err := mergeSpeakerMetaJSON(sp.MetaJSON, updates)
|
||||||
|
if err != nil {
|
||||||
|
writeProblem(w, http.StatusUnprocessableEntity, "Unprocessable Entity", "speaker meta_json must be a JSON object (or empty)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
patch := &store.SpeakerPatch{MetaJSON: &meta}
|
||||||
|
if _, err := s.store.UpdateSpeaker(a.TenantID, sid, patch); err != nil {
|
||||||
|
writeProblem(w, http.StatusInternalServerError, "Internal Error", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
writeJSON(w, http.StatusOK, map[string]any{
|
writeJSON(w, http.StatusOK, map[string]any{
|
||||||
"status": "accepted",
|
"status": "enrolled",
|
||||||
"message": "enrollment stub; operator approval required in production",
|
"speaker_id": sp.ID,
|
||||||
|
"tenant_id": a.TenantID,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func mergeSpeakerMetaJSON(existing string, updates map[string]any) (string, error) {
|
||||||
|
existing = strings.TrimSpace(existing)
|
||||||
|
var m map[string]any
|
||||||
|
if existing != "" {
|
||||||
|
if err := json.Unmarshal([]byte(existing), &m); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if m == nil {
|
||||||
|
return "", errors.New("meta must be a JSON object")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if m == nil {
|
||||||
|
m = make(map[string]any)
|
||||||
|
}
|
||||||
|
for k, v := range updates {
|
||||||
|
m[k] = v
|
||||||
|
}
|
||||||
|
b, err := json.Marshal(m)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
func sortedFragmentKeys(m map[string]string) []string {
|
func sortedFragmentKeys(m map[string]string) []string {
|
||||||
keys := make([]string, 0, len(m))
|
keys := make([]string, 0, len(m))
|
||||||
for k := range m {
|
for k := range m {
|
||||||
|
|||||||
+1
-16
@@ -22,8 +22,7 @@ func Run(ctx context.Context, deps *Deps) {
|
|||||||
cfg := config.Load()
|
cfg := config.Load()
|
||||||
broker.LogConnect(ctx, cfg.BrokerURL)
|
broker.LogConnect(ctx, cfg.BrokerURL)
|
||||||
if deps == nil || deps.Store == nil {
|
if deps == nil || deps.Store == nil {
|
||||||
runStub(ctx)
|
log.Fatalf("evobgp-ingest: missing store (pass ingest.Deps from BootstrapWorkers or evobgp-all)")
|
||||||
return
|
|
||||||
}
|
}
|
||||||
hc := &http.Client{Timeout: 45 * time.Second}
|
hc := &http.Client{Timeout: 45 * time.Second}
|
||||||
t := time.NewTicker(60 * time.Second)
|
t := time.NewTicker(60 * time.Second)
|
||||||
@@ -41,17 +40,3 @@ func Run(ctx context.Context, deps *Deps) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func runStub(ctx context.Context) {
|
|
||||||
t := time.NewTicker(60 * time.Second)
|
|
||||||
defer t.Stop()
|
|
||||||
log.Printf("evobgp-ingest: idle stub (no store in Deps)")
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
log.Printf("evobgp-ingest: stopped")
|
|
||||||
return
|
|
||||||
case <-t.C:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -18,7 +18,7 @@ const (
|
|||||||
StatusCancelled = "cancelled"
|
StatusCancelled = "cancelled"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Job is the API-facing job model (maps to job_audit).
|
// Job is the API-facing job model (поля согласованы со схемой job_audit в миграциях; персистенция в БД пока не подключена).
|
||||||
type Job struct {
|
type Job struct {
|
||||||
ID string
|
ID string
|
||||||
TenantID string
|
TenantID string
|
||||||
@@ -148,7 +148,9 @@ func (j *Job) Snapshot() map[string]any {
|
|||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
// Registry tracks jobs in memory (microVPS-style single process; swap for PG + SKIP LOCKED later).
|
// Registry — in-memory очередь и индекс по idempotency в процессе, где поднят HTTP API (evobgp-api и evobgp-all).
|
||||||
|
// Отдельные воркеры в reference-профиле не разделяют память с API: scheduler дергает refresh по HTTP; см. docs/architecture.md.
|
||||||
|
// Запись задач в PostgreSQL job_audit + SKIP LOCKED / внешний брокер — планируемое расширение (архитектурный план §2, §7.10).
|
||||||
type Registry struct {
|
type Registry struct {
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
byID map[string]*Job
|
byID map[string]*Job
|
||||||
|
|||||||
+1
-16
@@ -22,8 +22,7 @@ func Run(ctx context.Context, deps *Deps) {
|
|||||||
cfg := config.Load()
|
cfg := config.Load()
|
||||||
broker.LogConnect(ctx, cfg.BrokerURL)
|
broker.LogConnect(ctx, cfg.BrokerURL)
|
||||||
if deps == nil || deps.Store == nil {
|
if deps == nil || deps.Store == nil {
|
||||||
runStub(ctx)
|
log.Fatalf("evobgp-render: missing store (pass render.Deps from BootstrapWorkers or evobgp-all)")
|
||||||
return
|
|
||||||
}
|
}
|
||||||
t := time.NewTicker(45 * time.Second)
|
t := time.NewTicker(45 * time.Second)
|
||||||
defer t.Stop()
|
defer t.Stop()
|
||||||
@@ -45,17 +44,3 @@ func Run(ctx context.Context, deps *Deps) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func runStub(ctx context.Context) {
|
|
||||||
t := time.NewTicker(60 * time.Second)
|
|
||||||
defer t.Stop()
|
|
||||||
log.Printf("evobgp-render: idle stub (no store in Deps)")
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
log.Printf("evobgp-render: stopped")
|
|
||||||
return
|
|
||||||
case <-t.C:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
// Package repository will host SQL-backed data access (plan §1: tenant, module, peer, revision, jobs).
|
// Package repository — доступ к PostgreSQL (план §1: tenant, module, peer, revision, сущности модулей и т.д.).
|
||||||
//
|
//
|
||||||
// The in-memory implementation used by the current API lives in internal/store.
|
// In-memory реализация для тестов и режима без БД — в internal/store.
|
||||||
// Migrations: see migrations/ at repository root.
|
// Миграции: каталог migrations/ в корне репозитория.
|
||||||
|
|
||||||
package repository
|
package repository
|
||||||
@@ -1029,7 +1029,7 @@ func (p *Postgres) DeleteCommunity(tenantID, id string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CDN / AS / domain / IP stubs: implement in postgres_sub.go to keep file size manageable.
|
// CDN / AS / domain / IP / settings: см. postgres_entities.go.
|
||||||
|
|
||||||
func strPtrUUID(s *string) *string {
|
func strPtrUUID(s *string) *string {
|
||||||
if s == nil || strings.TrimSpace(*s) == "" {
|
if s == nil || strings.TrimSpace(*s) == "" {
|
||||||
|
|||||||
@@ -556,5 +556,3 @@ func (p *Postgres) PatchGlobalSettings(tenantID string, patch map[string]any) er
|
|||||||
}
|
}
|
||||||
|
|
||||||
var _ store.Backend = (*Postgres)(nil)
|
var _ store.Backend = (*Postgres)(nil)
|
||||||
|
|
||||||
var _ store.Backend = (*Postgres)(nil)
|
|
||||||
@@ -26,17 +26,15 @@ type Deps struct {
|
|||||||
HTTP *http.Client
|
HTTP *http.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run blocks until ctx is cancelled. When deps is nil or incomplete, falls back to connect-only stub logging.
|
// Run blocks until ctx is cancelled. Misconfigured deps terminate the process (no idle fallback).
|
||||||
func Run(ctx context.Context, deps *Deps) {
|
func Run(ctx context.Context, deps *Deps) {
|
||||||
cfg := config.Load()
|
cfg := config.Load()
|
||||||
broker.LogConnect(ctx, cfg.BrokerURL)
|
broker.LogConnect(ctx, cfg.BrokerURL)
|
||||||
if deps == nil || deps.Store == nil {
|
if deps == nil || deps.Store == nil {
|
||||||
runStub(ctx)
|
log.Fatalf("evobgp-scheduler: missing store (pass scheduler.Deps with Store from BootstrapWorkers or evobgp-all)")
|
||||||
return
|
|
||||||
}
|
}
|
||||||
if deps.Jobs == nil && (strings.TrimSpace(deps.APIBase) == "" || strings.TrimSpace(deps.APIToken) == "") {
|
if deps.Jobs == nil && (strings.TrimSpace(deps.APIBase) == "" || strings.TrimSpace(deps.APIToken) == "") {
|
||||||
runStub(ctx)
|
log.Fatalf("evobgp-scheduler: need either in-process Jobs (evobgp-all) or both EVOBGP_CONTROL_PLANE_URL and EVOBGP_SCHEDULER_BEARER")
|
||||||
return
|
|
||||||
}
|
}
|
||||||
t := time.NewTicker(30 * time.Second)
|
t := time.NewTicker(30 * time.Second)
|
||||||
defer t.Stop()
|
defer t.Stop()
|
||||||
@@ -125,17 +123,3 @@ func postModuleRefresh(ctx context.Context, deps *Deps, moduleID, idempotencyKey
|
|||||||
b, _ := io.ReadAll(io.LimitReader(resp.Body, 4096))
|
b, _ := io.ReadAll(io.LimitReader(resp.Body, 4096))
|
||||||
return fmt.Errorf("%s: %s", resp.Status, strings.TrimSpace(string(b)))
|
return fmt.Errorf("%s: %s", resp.Status, strings.TrimSpace(string(b)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func runStub(ctx context.Context) {
|
|
||||||
t := time.NewTicker(60 * time.Second)
|
|
||||||
defer t.Stop()
|
|
||||||
log.Printf("evobgp-scheduler: idle stub (no store/jobs: pass scheduler.Deps from evobgp-all or bootstrap worker)")
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
log.Printf("evobgp-scheduler: stopped")
|
|
||||||
return
|
|
||||||
case <-t.C:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in new issue
Block a user