From a09066c1d3a8bb7399de19279015e37639ca164b Mon Sep 17 00:00:00 2001 From: Edgar Sipki Date: Sat, 24 Oct 2026 00:13:48 +0300 Subject: [PATCH] api-service: plugins publish replaces push, register and pack (v1.2.0) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit operator-cli, plugins, quickstart, architecture, configuration, client-usage and migrating-from-bsr describe `plugins publish` and `plugins build --archive` in place of push, register and pack; the derived command and the sha256 claim in a plugin config; CHECKSUM_MISMATCH as FAILED_PRECONDITION; and republishing, which every replica picks up. upgrading gains a v1.1.x → v1.2.0 section; backup and runbooks repair a checksum mismatch with `publish --force` instead of forbidding it. Co-Authored-By: Claude Opus 5.5 --- content/docs/api-service/architecture.mdx | 48 +++--- content/docs/api-service/architecture.ru.mdx | 48 +++--- content/docs/api-service/backup.mdx | 17 +- content/docs/api-service/backup.ru.mdx | 15 +- content/docs/api-service/client-usage.mdx | 15 +- content/docs/api-service/client-usage.ru.mdx | 15 +- content/docs/api-service/configuration.mdx | 4 +- content/docs/api-service/configuration.ru.mdx | 4 +- .../docs/api-service/migrating-from-bsr.mdx | 14 +- .../api-service/migrating-from-bsr.ru.mdx | 14 +- content/docs/api-service/operator-cli.mdx | 156 ++++++++++------- content/docs/api-service/operator-cli.ru.mdx | 159 ++++++++++-------- content/docs/api-service/plugins.mdx | 104 +++++++----- content/docs/api-service/plugins.ru.mdx | 108 +++++++----- content/docs/api-service/quickstart.mdx | 24 +-- content/docs/api-service/quickstart.ru.mdx | 24 +-- content/docs/api-service/runbooks.mdx | 9 +- content/docs/api-service/runbooks.ru.mdx | 10 +- content/docs/api-service/upgrading.mdx | 42 +++++ content/docs/api-service/upgrading.ru.mdx | 43 +++++ 20 files changed, 559 insertions(+), 314 deletions(-) diff --git a/content/docs/api-service/architecture.mdx b/content/docs/api-service/architecture.mdx index d5c3c0e..7fd6741 100644 --- a/content/docs/api-service/architecture.mdx +++ b/content/docs/api-service/architecture.mdx @@ -100,8 +100,9 @@ attempts, and only when the error text contains `connection refused` or ## Executing the plugin - The executable is `command[0]` from the plugin's registered config, with - `command[1:]` as arguments. It must resolve (after symlinks) to a path inside - `registry.plugins_dir`. + `command[1:]` as arguments. When the registration left `command` out, the + service recorded `{plugins_dir}/{group}/{name}/{version}/plugin`. It must + resolve (after symlinks) to a path inside `registry.plugins_dir`. - The environment is empty except for the `env` map in the plugin's config. The service's own credentials are not inherited. - The request is written to stdin. Stdout is read up to @@ -117,31 +118,38 @@ what it does not cover. ## Plugin storage **Local mode** (no `registry.s3.bucket`): plugin binaries must already be under -`plugins_dir` — built there, mounted, or baked into an image. The registry row -only points at them. +`plugins_dir` — unpacked there by `easyp-svc plugins publish --plugins-dir`, +mounted, or baked into an image. The registry row only points at them. **Object storage mode**: `plugins_dir` is a cache. -1. The operator packs each version directory into - `{group}/{name}/{version}/plugin.tgz` and uploads it (`easyp-svc plugins push`). -2. Registration (`CreatePlugin`) reads the archive from storage and records its - sha256 in the plugin config. If the archive is missing, registration fails - with `FAILED_PRECONDITION` / `BINARY_NOT_UPLOADED`. The client cannot supply - the hash. +1. `easyp-svc plugins publish` packs each version directory into + `{group}/{name}/{version}/plugin.tgz` and uploads it with its sha256 in the + object metadata, then registers it — one version at a time, so nothing is + registered before its archive is in place. +2. Registration (`CreatePlugin`, or `UpdatePlugin` on a republish) checks the + archive is in storage, takes its sha256 from the object metadata (or reads + the object when there is none), and records it in the plugin config along + with the derived command. If the archive is missing, registration fails with + `FAILED_PRECONDITION` / `BINARY_NOT_UPLOADED`. A `sha256` in the request is a + claim: if the stored archive has a different one, registration fails with + `FAILED_PRECONDITION` / `CHECKSUM_MISMATCH`. 3. On the first request for that version, the archive is downloaded to - `plugins_dir/.tmp`, its sha256 compared with the recorded one, and unpacked. - Concurrent misses for the same key share one download. + `plugins_dir/.tmp`, its sha256 compared with the recorded one, and unpacked; + a `.sha256` marker beside the unpacked directory records which archive it + came from. A download that does not match fails with `FAILED_PRECONDITION` / + `CHECKSUM_MISMATCH` (before v1.2.0, `INTERNAL`). Concurrent misses for the + same key share one download. 4. The unpacked directories are evicted least-recently-used once they exceed `registry.cache_max_bytes` (20 GiB). The archive stays in storage. -The order is therefore **build → push → register**. Re-pushing an archive with -`--force` after registration changes the object under a recorded checksum. A -host that already has the version unpacked keeps running the old binary — the -checksum is checked only on download — while a cold cache fails with -`plugin archive checksum mismatch`. Registering again does not help: the -version already exists and `plugins register` skips it. Publish a new version -instead, or `DeletePlugin` the version (which removes the row, the archive and -the cached copy), push, and register. +Republishing a version (`plugins publish --force`) uploads the new archive and +replaces the recorded checksum through `UpdatePlugin`. On next use every replica +sees that its `.sha256` marker no longer matches the registry and refetches; a +directory without a marker (unpacked before v1.2.0) counts as current. An +archive overwritten in storage any other way no longer has the recorded +checksum, and its next download fails with `CHECKSUM_MISMATCH` — republish it +with `publish --force`, or publish a new version. A storage outage, or an archive that has disappeared from the bucket, fails generation with `UNAVAILABLE` / `STORAGE_UNAVAILABLE`. diff --git a/content/docs/api-service/architecture.ru.mdx b/content/docs/api-service/architecture.ru.mdx index dc2ca27..6ffce39 100644 --- a/content/docs/api-service/architecture.ru.mdx +++ b/content/docs/api-service/architecture.ru.mdx @@ -101,8 +101,9 @@ gRPC-статусы, и конвертация снаружи переписал ## Исполнение плагина - Исполняемый файл — `command[0]` из зарегистрированного конфига плагина, - аргументы — `command[1:]`. После разрешения симлинков он обязан лежать внутри - `registry.plugins_dir`. + аргументы — `command[1:]`. Если при регистрации `command` не задан, сервис + записал `{plugins_dir}/{group}/{name}/{version}/plugin`. После разрешения + симлинков он обязан лежать внутри `registry.plugins_dir`. - Окружение пустое, кроме карты `env` из конфига плагина. Учётные данные самого сервиса не наследуются. - Запрос пишется в stdin. Stdout читается до `registry.max_output_size` @@ -118,30 +119,39 @@ gRPC-статусы, и конвертация снаружи переписал ## Хранение плагинов **Локальный режим** (нет `registry.s3.bucket`): бинарники плагинов уже должны -лежать в `plugins_dir` — собраны там, смонтированы или вшиты в образ. Запись в -реестре только указывает на них. +лежать в `plugins_dir` — распакованы туда `easyp-svc plugins publish +--plugins-dir`, смонтированы или вшиты в образ. Запись в реестре только +указывает на них. **Режим объектного хранилища**: `plugins_dir` — это кеш. -1. Оператор упаковывает каталог каждой версии в - `{group}/{name}/{version}/plugin.tgz` и загружает его (`easyp-svc plugins push`). -2. Регистрация (`CreatePlugin`) читает архив из хранилища и записывает его - sha256 в конфиг плагина. Если архива нет, регистрация падает с - `FAILED_PRECONDITION` / `BINARY_NOT_UPLOADED`. Клиент не может подставить хеш. +1. `easyp-svc plugins publish` упаковывает каталог каждой версии в + `{group}/{name}/{version}/plugin.tgz` и загружает его с sha256 в метаданных + объекта, а затем регистрирует — по одной версии за раз, так что ничего не + регистрируется раньше, чем архив окажется на месте. +2. Регистрация (`CreatePlugin` или, при повторной публикации, `UpdatePlugin`) + проверяет, что архив лежит в хранилище, берёт его sha256 из метаданных + объекта (или читает сам объект, если их нет) и записывает её в конфиг плагина + вместе с выведенной командой. Если архива нет, регистрация падает с + `FAILED_PRECONDITION` / `BINARY_NOT_UPLOADED`. `sha256` в запросе — это + утверждение: если у архива в хранилище другая сумма, регистрация падает с + `FAILED_PRECONDITION` / `CHECKSUM_MISMATCH`. 3. При первом запросе этой версии архив скачивается в `plugins_dir/.tmp`, его - sha256 сверяется с записанным, и он распаковывается. Одновременные промахи - по одному ключу делят одно скачивание. + sha256 сверяется с записанным, и он распаковывается; маркер `.sha256` рядом с + распакованным каталогом запоминает, из какого архива он получен. Несовпавшее + скачивание падает с `FAILED_PRECONDITION` / `CHECKSUM_MISMATCH` (до v1.2.0 — + `INTERNAL`). Одновременные промахи по одному ключу делят одно скачивание. 4. Распакованные каталоги вытесняются по LRU, когда их объём превышает `registry.cache_max_bytes` (20 ГиБ). Архив в хранилище остаётся. -Поэтому порядок — **build → push → register**. Повторная загрузка архива с -`--force` после регистрации меняет объект под записанной контрольной суммой. -Хост, у которого эта версия уже распакована, продолжает запускать старый -бинарник — сумма проверяется только при скачивании, — а при холодном кеше -скачивание падает с `plugin archive checksum mismatch`. Повторная регистрация не -помогает: версия уже существует, и `plugins register` её пропускает. Выпустите -новую версию либо удалите эту через `DeletePlugin` (удаляется строка, архив и -закешированная копия), затем push и register. +Повторная публикация версии (`plugins publish --force`) загружает новый архив и +заменяет записанную контрольную сумму через `UpdatePlugin`. При следующем +обращении каждая реплика видит, что её маркер `.sha256` больше не совпадает с +реестром, и скачивает архив заново; каталог без маркера (распакованный до +v1.2.0) считается актуальным. Архив, перезаписанный в хранилище любым другим +способом, уже не имеет записанной контрольной суммы, и его следующее скачивание +падает с `CHECKSUM_MISMATCH` — перепубликуйте его через `publish --force` или +выпустите новую версию. Недоступность хранилища или исчезнувший из бакета архив дают при генерации `UNAVAILABLE` / `STORAGE_UNAVAILABLE`. diff --git a/content/docs/api-service/backup.mdx b/content/docs/api-service/backup.mdx index d0228dc..a25f3d8 100644 --- a/content/docs/api-service/backup.mdx +++ b/content/docs/api-service/backup.mdx @@ -25,13 +25,16 @@ matters: a `plugins` row without its archive fails generation with the missing key. An archive with no row is merely orphaned. **So prefer a bucket snapshot slightly newer than the database one** — never older. -"Newer" is safe only while archives are never overwritten. If an archive was -re-pushed with `--force` between the two snapshots, the newer bucket holds an -object whose sha256 is not the one the restored row recorded, and that plugin -fails with `plugin archive checksum mismatch` on its first download. Two ways to -rule it out: never overwrite a registered version (publish a new one instead), -or enable object versioning on the bucket and restore the object version that -existed at the database snapshot's time. +"Newer" is safe only while archives are never overwritten. If a version was +republished with `plugins publish --force` between the two snapshots, the newer +bucket holds an object whose sha256 is not the one the restored row recorded, +and that plugin fails with `FAILED_PRECONDITION` / `CHECKSUM_MISMATCH` on its +first download. Republishing it once more after the restore — +`plugins publish --force --filter '/:'` — records the +checksum of what the bucket holds. To rule it out instead: never overwrite a +registered version (publish a new one), or enable object versioning on the +bucket and restore the object version that existed at the database snapshot's +time. ## How much history to keep diff --git a/content/docs/api-service/backup.ru.mdx b/content/docs/api-service/backup.ru.mdx index 8e5dd8e..a11c68e 100644 --- a/content/docs/api-service/backup.ru.mdx +++ b/content/docs/api-service/backup.ru.mdx @@ -26,12 +26,15 @@ description: "Что обязано попасть в бэкап API Service, с брать чуть свежее снимка базы** — но никогда не старее. «Свежее» безопасно, только пока архивы не перезаписываются. Если между двумя -снимками архив перезалили с `--force`, в более свежем бакете лежит объект, чей -sha256 не совпадает с записанным в восстановленной строке, и этот плагин падает -с `plugin archive checksum mismatch` при первом скачивании. Исключить это можно -двумя способами: никогда не перезаписывать зарегистрированную версию (выпускать -новую) или включить версионирование объектов в бакете и восстанавливать ту -версию объекта, что существовала на момент снимка базы. +снимками версию перепубликовали через `plugins publish --force`, в более свежем +бакете лежит объект, чей sha256 не совпадает с записанным в восстановленной +строке, и этот плагин падает с `FAILED_PRECONDITION` / `CHECKSUM_MISMATCH` при +первом скачивании. Ещё одна перепубликация после восстановления — +`plugins publish --force --filter '/:'` — запишет +контрольную сумму того, что лежит в бакете. Исключить это заранее можно двумя +способами: никогда не перезаписывать зарегистрированную версию (выпускать новую) +или включить версионирование объектов в бакете и восстанавливать ту версию +объекта, что существовала на момент снимка базы. ## Сколько хранить историю diff --git a/content/docs/api-service/client-usage.mdx b/content/docs/api-service/client-usage.mdx index 1826340..b39e218 100644 --- a/content/docs/api-service/client-usage.mdx +++ b/content/docs/api-service/client-usage.mdx @@ -116,7 +116,7 @@ func main() { | `WithToken(token)` | — | `authorization: Bearer` on every call. Needed for writes, and for reads when the service requires authentication. | | `WithGenerateCodeTimeout(d)` | 30 s | Per-call deadline when the context has none. Raise it towards the server's `generation_timeout`. | | `WithListPluginsTimeout(d)` | 10 s | Spans the whole paginated walk. | -| `WithCreatePluginTimeout(d)` | 120 s | Registration reads the archive from storage. | +| `WithCreatePluginTimeout(d)` | 120 s | Registration checks the archive in storage, and reads it when the object carries no checksum. | | `WithMaxRetries(n)`, `WithRetryBaseDelay(d)`, `WithRetryMaxDelay(d)` | 3, 100 ms, 5 s | Exponential backoff. | | `WithMaxRecvMsgSize(n)` | 64 MiB | Matches the server's output limit. | | `WithHealthCheck(interval)` | 30 s | Background connection-state check. | @@ -131,6 +131,16 @@ re-runs a generation that already used its full timeout. Methods: `GenerateCode`, `ListPlugins` (walks every page), `CreatePlugin`, `UpdatePlugin`, `DeletePlugin`, `Close`. +`CreatePlugin` and `UpdatePlugin` take a `config` struct. On a v1.2.0 or newer +service `command` may be omitted: the service derives +`{plugins_dir}/{group}/{name}/{version}/plugin`, so a client need not know the +server's `plugins_dir` (older services require it). A `sha256` key is a claim +about the archive in storage — registration fails with `FAILED_PRECONDITION` / +`CHECKSUM_MISMATCH` unless the stored archive has exactly that checksum; without +it the service records the stored archive's checksum itself. A config holding +only `sha256` is what `easyp-svc plugins publish` sends. All keys: +[Plugins](/docs/api-service/plugins#registered-configuration). + ## gRPC directly The contract is `easyp.generator.v1.GeneratorAPI` in @@ -173,7 +183,8 @@ stable `reason`. Branch on the reason, not the message. | `SERVER_OVERLOADED` | `RESOURCE_EXHAUSTED` | Worker or generation queue full. | | `MAX_PLUGINS_EXCEEDED` | `RESOURCE_EXHAUSTED` | Community ceiling of 10 versions. | | `ALREADY_EXISTS` | `ALREADY_EXISTS` | Version already registered. | -| `BINARY_NOT_UPLOADED` | `FAILED_PRECONDITION` | Registering before `plugins push`. | +| `BINARY_NOT_UPLOADED` | `FAILED_PRECONDITION` | Registering a version whose archive is not in storage. | +| `CHECKSUM_MISMATCH` | `FAILED_PRECONDITION` | The config's `sha256` is not the stored archive's, or a downloaded archive does not match the registered checksum (before v1.2.0 the latter was `INTERNAL`). Republish the version. | | `STORAGE_UNAVAILABLE` | `UNAVAILABLE` | Object storage down, or the archive is gone. | | `SHUTTING_DOWN` | `UNAVAILABLE` | The service is draining. | | `FEATURE_DENIED` | `PERMISSION_DENIED` | Licence does not include the feature. | diff --git a/content/docs/api-service/client-usage.ru.mdx b/content/docs/api-service/client-usage.ru.mdx index dff53a0..8d981ac 100644 --- a/content/docs/api-service/client-usage.ru.mdx +++ b/content/docs/api-service/client-usage.ru.mdx @@ -116,7 +116,7 @@ func main() { | `WithToken(token)` | — | `authorization: Bearer` в каждом вызове. Нужен для записи и для чтения, если сервис требует аутентификацию. | | `WithGenerateCodeTimeout(d)` | 30 с | Дедлайн вызова, если в контексте его нет. Поднимайте к `generation_timeout` сервера. | | `WithListPluginsTimeout(d)` | 10 с | На весь постраничный обход. | -| `WithCreatePluginTimeout(d)` | 120 с | Регистрация читает архив из хранилища. | +| `WithCreatePluginTimeout(d)` | 120 с | Регистрация проверяет архив в хранилище и читает его, если у объекта нет контрольной суммы. | | `WithMaxRetries(n)`, `WithRetryBaseDelay(d)`, `WithRetryMaxDelay(d)` | 3, 100 мс, 5 с | Экспоненциальная задержка. | | `WithMaxRecvMsgSize(n)` | 64 МиБ | Совпадает с лимитом вывода сервера. | | `WithHealthCheck(interval)` | 30 с | Фоновая проверка состояния соединения. | @@ -131,6 +131,16 @@ func main() { Методы: `GenerateCode`, `ListPlugins` (обходит все страницы), `CreatePlugin`, `UpdatePlugin`, `DeletePlugin`, `Close`. +`CreatePlugin` и `UpdatePlugin` принимают структуру `config`. На сервисе v1.2.0 +или новее `command` можно не указывать: сервис выводит +`{plugins_dir}/{group}/{name}/{version}/plugin`, так что клиенту не нужно знать +`plugins_dir` сервера (более старые сервисы его требуют). Ключ `sha256` — это +утверждение об архиве в хранилище: регистрация падает с `FAILED_PRECONDITION` / +`CHECKSUM_MISMATCH`, если у архива в хранилище не ровно эта сумма; без него +сервис сам записывает сумму архива из хранилища. Конфиг из одного `sha256` — это +то, что отправляет `easyp-svc plugins publish`. Все ключи — +[Плагины](/ru/docs/api-service/plugins#registered-configuration). + ## gRPC напрямую Контракт — `easyp.generator.v1.GeneratorAPI` в @@ -173,7 +183,8 @@ grpcurl -protoset api.protoset -plaintext localhost:23410 \ | `SERVER_OVERLOADED` | `RESOURCE_EXHAUSTED` | Очередь воркеров или генераций заполнена. | | `MAX_PLUGINS_EXCEEDED` | `RESOURCE_EXHAUSTED` | Потолок Community в 10 версий. | | `ALREADY_EXISTS` | `ALREADY_EXISTS` | Версия уже зарегистрирована. | -| `BINARY_NOT_UPLOADED` | `FAILED_PRECONDITION` | Регистрация до `plugins push`. | +| `BINARY_NOT_UPLOADED` | `FAILED_PRECONDITION` | Регистрация версии, архива которой нет в хранилище. | +| `CHECKSUM_MISMATCH` | `FAILED_PRECONDITION` | `sha256` в конфиге не совпадает с суммой архива в хранилище, или скачанный архив не совпадает с зарегистрированной суммой (до v1.2.0 во втором случае был `INTERNAL`). Перепубликуйте версию. | | `STORAGE_UNAVAILABLE` | `UNAVAILABLE` | Объектное хранилище недоступно или архив пропал. | | `SHUTTING_DOWN` | `UNAVAILABLE` | Сервис останавливается. | | `FEATURE_DENIED` | `PERMISSION_DENIED` | Функции нет в лицензии. | diff --git a/content/docs/api-service/configuration.mdx b/content/docs/api-service/configuration.mdx index 7721e4a..80293c5 100644 --- a/content/docs/api-service/configuration.mdx +++ b/content/docs/api-service/configuration.mdx @@ -34,7 +34,7 @@ one `configuration resolved` line. | Variable | Read by | Meaning | |---|---|---| | `EASYP_CONFIG` | every command that takes `--cfg` | Path to the YAML file. Lets the image's `HEALTHCHECK` (`easyp-svc health`) find the same file as the service. | -| `EASYP_TOKEN` | `plugins register` | Write token, when `--token` is not given. | +| `EASYP_TOKEN` | `plugins publish` | Write token, when `--token` is not given. | | `OTEL_EXPORTER_OTLP_ENDPOINT` | the service | Alias of `TELEMETRY_OTLP_ENDPOINT`. | ## Durations and sizes @@ -69,7 +69,7 @@ Durations use Go syntax: `30s`, `10m`, `6h`. Sizes are bytes as integers. | YAML key | Environment | Type | Default | Description | |---|---|---|---|---| -| `registry.plugins_dir` | `REGISTRY_PLUGINS_DIR` | string | `/plugins` | Where plugin executables live (local mode) or are unpacked (object storage mode). Must be writable. A plugin's `command[0]` must resolve inside it. | +| `registry.plugins_dir` | `REGISTRY_PLUGINS_DIR` | string | `/plugins` | Where plugin executables live (local mode) or are unpacked (object storage mode). Must be writable. A plugin's `command[0]` must resolve inside it; a registration without `command` gets `{plugins_dir}/{group}/{name}/{version}/plugin`. | | `registry.max_output_size` | `REGISTRY_MAX_OUTPUT_SIZE` | int64 | `67108864` | Largest plugin stdout, bytes. Larger output fails the generation. | | `registry.cache_max_bytes` | `REGISTRY_CACHE_MAX_BYTES` | int64 | `21474836480` | Bound on unpacked plugins on disk; least-recently-used versions are evicted beyond it. `0` disables eviction. Meaningful only with object storage. | | `registry.s3.endpoint` | `REGISTRY_S3_ENDPOINT` | string | — | S3 endpoint URL, e.g. `http://minio:9000`. Empty uses AWS. | diff --git a/content/docs/api-service/configuration.ru.mdx b/content/docs/api-service/configuration.ru.mdx index a375e18..4b4710d 100644 --- a/content/docs/api-service/configuration.ru.mdx +++ b/content/docs/api-service/configuration.ru.mdx @@ -34,7 +34,7 @@ easyp-svc config print --cfg config.yml --changed # только отлич | Переменная | Кто читает | Смысл | |---|---|---| | `EASYP_CONFIG` | каждая команда с `--cfg` | Путь к YAML-файлу. Позволяет `HEALTHCHECK` образа (`easyp-svc health`) найти тот же файл, что и сервис. | -| `EASYP_TOKEN` | `plugins register` | Токен записи, если не задан `--token`. | +| `EASYP_TOKEN` | `plugins publish` | Токен записи, если не задан `--token`. | | `OTEL_EXPORTER_OTLP_ENDPOINT` | сервис | Синоним `TELEMETRY_OTLP_ENDPOINT`. | ## Длительности и размеры @@ -69,7 +69,7 @@ easyp-svc config print --cfg config.yml --changed # только отлич | Ключ YAML | Переменная | Тип | По умолчанию | Описание | |---|---|---|---|---| -| `registry.plugins_dir` | `REGISTRY_PLUGINS_DIR` | string | `/plugins` | Где лежат исполняемые файлы плагинов (локальный режим) или куда они распаковываются (режим объектного хранилища). Должен быть доступен на запись. `command[0]` плагина обязан разрешаться внутрь него. | +| `registry.plugins_dir` | `REGISTRY_PLUGINS_DIR` | string | `/plugins` | Где лежат исполняемые файлы плагинов (локальный режим) или куда они распаковываются (режим объектного хранилища). Должен быть доступен на запись. `command[0]` плагина обязан разрешаться внутрь него; регистрация без `command` получает `{plugins_dir}/{group}/{name}/{version}/plugin`. | | `registry.max_output_size` | `REGISTRY_MAX_OUTPUT_SIZE` | int64 | `67108864` | Максимальный stdout плагина, байт. Больше — генерация падает. | | `registry.cache_max_bytes` | `REGISTRY_CACHE_MAX_BYTES` | int64 | `21474836480` | Предел распакованных плагинов на диске; сверх него вытесняются давно не использованные версии. `0` отключает вытеснение. Имеет смысл только с объектным хранилищем. | | `registry.s3.endpoint` | `REGISTRY_S3_ENDPOINT` | string | — | URL S3-эндпоинта, например `http://minio:9000`. Пусто — AWS. | diff --git a/content/docs/api-service/migrating-from-bsr.mdx b/content/docs/api-service/migrating-from-bsr.mdx index c40cf1b..42486e8 100644 --- a/content/docs/api-service/migrating-from-bsr.mdx +++ b/content/docs/api-service/migrating-from-bsr.mdx @@ -43,15 +43,19 @@ A plugin missing from the EasyP catalogue needs a recipe of your own — see Always pin versions. An unpinned EasyP reference resolves to the version that sorts last as a string, not the newest release. -## 2. Build, push and register +## 2. Build and publish ```bash -easyp-svc plugins build registry --filter 'protocolbuffers/go:v1.36.10' -easyp-svc plugins build registry --filter 'grpc/go:v1.5.1' -easyp-svc plugins push plugins --cfg config.yml -EASYP_TOKEN=… easyp-svc plugins register --addr plugins.example.com:443 plugins +export EASYP_TOKEN=… +easyp-svc plugins publish registry --filter 'protocolbuffers/go:v1.36.10' \ + --cfg config.yml --addr plugins.example.com:443 +easyp-svc plugins publish registry --filter 'grpc/go:v1.5.1' \ + --cfg config.yml --addr plugins.example.com:443 ``` +`publish` builds, verifies, uploads and registers each version in one pass, and +skips what the service already has. It needs a v1.2.0 or newer service. + On a Community licence at most 10 versions can be registered. Count the distinct plugin versions across all your `buf.gen.yaml` files first. diff --git a/content/docs/api-service/migrating-from-bsr.ru.mdx b/content/docs/api-service/migrating-from-bsr.ru.mdx index 5265e66..6bbab65 100644 --- a/content/docs/api-service/migrating-from-bsr.ru.mdx +++ b/content/docs/api-service/migrating-from-bsr.ru.mdx @@ -43,15 +43,19 @@ description: "Перенос генерации кода с remote plugins Buf Всегда фиксируйте версии. Незафиксированная ссылка EasyP разрешается в версию, последнюю при сравнении строк, а не в самый новый релиз. -## 2. Собрать, загрузить и зарегистрировать +## 2. Собрать и опубликовать ```bash -easyp-svc plugins build registry --filter 'protocolbuffers/go:v1.36.10' -easyp-svc plugins build registry --filter 'grpc/go:v1.5.1' -easyp-svc plugins push plugins --cfg config.yml -EASYP_TOKEN=… easyp-svc plugins register --addr plugins.example.com:443 plugins +export EASYP_TOKEN=… +easyp-svc plugins publish registry --filter 'protocolbuffers/go:v1.36.10' \ + --cfg config.yml --addr plugins.example.com:443 +easyp-svc plugins publish registry --filter 'grpc/go:v1.5.1' \ + --cfg config.yml --addr plugins.example.com:443 ``` +`publish` собирает, проверяет, загружает и регистрирует каждую версию за один +проход и пропускает то, что у сервиса уже есть. Нужен сервис v1.2.0 или новее. + С лицензией Community можно зарегистрировать не больше 10 версий. Сначала посчитайте различные версии плагинов во всех ваших `buf.gen.yaml`. diff --git a/content/docs/api-service/operator-cli.mdx b/content/docs/api-service/operator-cli.mdx index 443c8af..e3fefac 100644 --- a/content/docs/api-service/operator-cli.mdx +++ b/content/docs/api-service/operator-cli.mdx @@ -8,7 +8,7 @@ description: "easyp-svc: every command and flag of the binary that is both the s ``` easyp-svc service start -easyp-svc plugins build | pack | push | register +easyp-svc plugins build | publish easyp-svc auth new-token easyp-svc api descriptor easyp-svc config validate | print @@ -32,90 +32,113 @@ Starts the gRPC, metrics, health and (if enabled) MCP listeners. `easyp-svc plugins build [options] ` -Builds plugin binaries from registry Dockerfiles. Needs Docker. Each version -listed in a `plugin.yaml` is built with `--build-arg VERSION=…` and the image -filesystem is extracted to `/{group}/{name}/{version}/`. +Builds plugin binaries from registry Dockerfiles without uploading anything. +Needs Docker. Each version listed in a `plugin.yaml` is built with +`--build-arg VERSION=…` and the image filesystem is extracted to +`/{group}/{name}/{version}/`. Every version is then verified the way the +service will run it — packed and unpacked with the service's unpacker, and +smoke-run in the runtime image; a version that fails is emptied down to its +`build.log`. + +With `--archive`, only each version's verified +`/{group}/{name}/{version}/plugin.tgz` is kept — the tree +`plugins publish --archives` takes, for building on one machine and publishing +from another. | Flag | Default | Meaning | |---|---|---| | `--output`, `-o` | `plugins` | Output directory. | +| `--archive` | false | Keep only each version's verified `plugin.tgz` instead of the unpacked bundle. | | `--filter` | — | Glob on `group/name[:version]`, e.g. `protocolbuffers/*`, `grpc/go:v1.5.1`. | | `--parallel`, `-p` | `4` | Concurrent Docker builds. | -| `--force` | false | Rebuild even if the binary exists. | +| `--force` | false | Rebuild even if the version is already built. A version built by an older `easyp-svc` was not verified the way this one verifies, and is only rebuilt with this. | | `--dry-run` | false | List what would be built. | | `--non-interactive` | false | No interactive UI or progress bars. | -| `--keep-going` | false | Continue after a failed build. | - -## plugins pack - -`easyp-svc plugins pack [options] [path]` - -Writes each version directory under `path` to -`{out}/{group}/{name}/{version}/plugin.tgz` — the same layout as the S3 keys, so -the tree can be uploaded later with `push --packed`. - -| Flag | Meaning | -|---|---| -| `--out` | Directory to write archives to. | -| `--filter` | Glob filter. | -| `--force` | Re-pack existing archives. | -| `--dry-run` | Print the plan only. | -| `--non-interactive` | No interactive UI. | - -## plugins push - -`easyp-svc plugins push [options] [path]` - -Packs each version directory and uploads it to -`{prefix}{group}/{name}/{version}/plugin.tgz`. Run before `register`. Re-pushing -an already registered version with `--force` invalidates its recorded checksum, -and `register` will not fix that — it skips versions that exist. Publish a new -version, or delete the old one first (see [Plugins](/docs/api-service/plugins#push-force-after-register)). +| `--keep-going` | **true** | Continue after a failed build. | +| `--runtime-image` | `debian:trixie-slim` | Image each built plugin is smoke-tested in; must match the base of the service image. | +| `--no-smoke` | false | Skip the smoke run. The archive is still checked to unpack the way the service unpacks it. | + +## plugins publish + +`easyp-svc plugins publish [options] [registry-path]` + +Takes each version through in one pass: build it from the registry (or take its +`plugin.tgz` from `--archives`), verify it, upload the archive to +`{prefix}{group}/{name}/{version}/plugin.tgz` with its sha256 in the object +metadata, register it — `CreatePlugin`, or `UpdatePlugin` for a version the +service already has — and delete the local copy before the next version needs +the disk. Needs a v1.2.0 or newer server; against an older one it stops at the +first registration and says so. + +Source — exactly one of: + +- a **registry path**, built version by version; +- `--archives `, a `{group}/{name}/{version}/plugin.tgz` tree written by + `plugins build --archive`; +- neither, with `--plugins-dir`: what is already unpacked in that directory is + registered. + +Destination: object storage, configured by the S3 flags and `--cfg`; or, with +`--plugins-dir `, that directory — the `plugins_dir` of a service on the +same host running without object storage — into which each archive is unpacked +instead of uploaded. + +It is idempotent. Without `--force`, a version that is registered and in +storage is skipped, one in storage but not registered is only registered, and an +upload is skipped whenever storage already holds the same archive; an +interrupted run resumes by re-running it. `--force` rebuilds and republishes +everything it matches (see [Plugins](/docs/api-service/plugins#republishing)). +Before anything is built, `publish` checks that storage answers, that the +service accepts the token, and which versions the service already has. | Flag | Default | Meaning | |---|---|---| +| `--archives` | — | Publish this `plugin.tgz` tree instead of building. | +| `--plugins-dir` | — | Unpack into this directory instead of uploading. With no other source, register what is already in it. | +| `--workdir` | `$TMPDIR/easyp-publish` | Where versions are built; each is removed once published. | +| `--keep` | false | Leave built versions in `--workdir` after publishing them. | +| `--filter` | — | Glob filter. | +| `--force` | false | Publish versions the service already has, rebuilding them and replacing their config. | +| `--build-parallel` | `4` | Concurrent Docker builds. | +| `--parallel`, `-p` | `8` | Versions uploading and registering at once. Keep it at or below the server's `rate_limit.max_concurrent_per_ip`. | +| `--dry-run` | false | List what would be published without building or contacting anything. | +| `--non-interactive` | false | No interactive UI. | +| `--runtime-image`, `--no-smoke` | as for `build` | Verification. | | `--cfg` | — | A **full** service config; `registry.s3` fills settings not given as flags. A fragment containing only `registry.s3` is refused. | | `--bucket` | — | Bucket (overrides the config). | | `--endpoint` | — | Endpoint URL, e.g. `http://localhost:9000`. | | `--region` | — | Region. | | `--prefix` | — | Key prefix. | | `--force-path-style` | false | Path-style addressing (MinIO, RustFS). | -| `--filter` | — | Glob filter. | -| `--packed` | false | `path` is a tree written by `plugins pack`. | -| `--parallel`, `-p` | `8` | Concurrent uploads. | -| `--force` | false | Re-upload archives already in storage. | -| `--dry-run` | false | Print the plan without contacting storage. | -| `--non-interactive` | false | No interactive UI. | - -Credentials come from the standard AWS chain (`AWS_ACCESS_KEY_ID`, -`AWS_SECRET_ACCESS_KEY`, profiles). An interrupted run resumes by re-running it. - -## plugins register - -`easyp-svc plugins register [options] [path]` - -Calls `CreatePlugin` for every version under `path` with its command path as the -service sees it. With `--packed`, `path` is a `pack` tree, which is enough to -register from a machine that never built the plugins as long as the archives are -already in storage. - -| Flag | Default | Meaning | -|---|---|---| | `--addr` | `localhost:23410` | Service gRPC address. | -| `--cfg` | — | Service config; `registry.plugins_dir` is used as `--plugins-prefix` when that flag is not set. | -| `--plugins-prefix` | `/plugins` | `plugins_dir` on the server. | | `--token` | `$EASYP_TOKEN` | Write token. | | `--tls-ca` | system store | CA that signed the server certificate. | | `--tls-cert`, `--tls-key` | — | Client certificate and key, for mutual TLS. | | `--insecure` | false | Plaintext. Local development only. | -| `--filter` | — | Glob filter. | -| `--packed` | false | `path` is a `pack` tree. | -| `--parallel`, `-p` | `8` | Concurrent registrations. The server reads each archive to checksum it; keep this at or below the server's `rate_limit.max_concurrent_per_ip`. | -| `--fail-on-error` | **true** | Exit non-zero if any registration failed. | -| `--dry-run` | false | Print planned calls without contacting the server. | -| `--non-interactive` | false | No interactive UI. | -A version that is already registered is reported as skipped, not failed. +Storage credentials come from the standard AWS chain (`AWS_ACCESS_KEY_ID`, +`AWS_SECRET_ACCESS_KEY`, profiles). Object storage commonly caps a single +connection well below the link, so throughput comes from `--parallel` streams; +`publish` backs off on the server's rate and concurrency limits and stops +starting new versions at the licence tier's plugin cap. It exits non-zero if +any version failed. + +```bash +# Build on one machine… +easyp-svc plugins build registry --archive --output plugin-archives + +# …publish from anywhere with the storage credentials and a write token. +export AWS_ACCESS_KEY_ID=… AWS_SECRET_ACCESS_KEY=… EASYP_TOKEN=… +easyp-svc plugins publish --archives plugin-archives \ + --endpoint https://storage.example.com --bucket easyp-plugins --force-path-style \ + --addr plugins.example.com:443 --parallel 24 +``` + + +Removed in v1.2.0: `plugins push` and `plugins register` became +`plugins publish`, `plugins pack` became `plugins build --archive`, and +`--packed` and `--plugins-prefix` are gone — the service derives the command. + ## auth new-token @@ -163,6 +186,9 @@ then the environment, then the default 23412. ## Taskfile shortcuts The service repository's `Taskfile.yml` wraps these for development: -`task build-plugins`, `FILTER=… task build-plugins-filter`, `task push-plugins`, -`task register-plugins`, `task up`, `task up-minimal`, `task run`, `task run-local`. -They assume the development stack's addresses and credentials. +`task build-plugins`, `FILTER=… task build-plugins-filter`, `task publish-plugins` +(build, upload and register into the development stack; `FILTER=…` for a slice), +`task publish-archives` (a `build --archive` tree to any service and storage), +`task up`, `task up-minimal`, `task run`, `task run-local`. Except +`publish-archives`, they assume the development stack's addresses and +credentials. diff --git a/content/docs/api-service/operator-cli.ru.mdx b/content/docs/api-service/operator-cli.ru.mdx index 54b4e25..f2a802c 100644 --- a/content/docs/api-service/operator-cli.ru.mdx +++ b/content/docs/api-service/operator-cli.ru.mdx @@ -8,7 +8,7 @@ description: "easyp-svc: все команды и флаги бинарника, ``` easyp-svc service start -easyp-svc plugins build | pack | push | register +easyp-svc plugins build | publish easyp-svc auth new-token easyp-svc api descriptor easyp-svc config validate | print @@ -32,92 +32,114 @@ easyp-svc health `easyp-svc plugins build [options] ` -Собирает бинарники плагинов по Dockerfile'ам реестра. Нужен Docker. Каждая -версия из `plugin.yaml` собирается с `--build-arg VERSION=…`, а файловая система -образа распаковывается в `/{group}/{name}/{version}/`. +Собирает бинарники плагинов по Dockerfile'ам реестра, ничего не загружая. Нужен +Docker. Каждая версия из `plugin.yaml` собирается с `--build-arg VERSION=…`, а +файловая система образа распаковывается в `/{group}/{name}/{version}/`. +Затем каждая версия проверяется так, как её будет запускать сервис: упаковывается +и распаковывается распаковщиком сервиса и пробно запускается в runtime-образе; +от версии, не прошедшей проверку, остаётся только `build.log`. + +С `--archive` от каждой версии остаётся только проверенный +`/{group}/{name}/{version}/plugin.tgz` — дерево, которое принимает +`plugins publish --archives`, чтобы собирать на одной машине, а публиковать с +другой. | Флаг | По умолчанию | Смысл | |---|---|---| | `--output`, `-o` | `plugins` | Каталог результата. | +| `--archive` | false | Оставить от каждой версии только проверенный `plugin.tgz` вместо распакованного бандла. | | `--filter` | — | Glob по `group/name[:version]`, например `protocolbuffers/*`, `grpc/go:v1.5.1`. | | `--parallel`, `-p` | `4` | Одновременных Docker-сборок. | -| `--force` | false | Пересобрать, даже если бинарник есть. | +| `--force` | false | Пересобрать, даже если версия уже собрана. Версия, собранная более старым `easyp-svc`, не проверялась так, как проверяет этот, и пересобирается только с этим флагом. | | `--dry-run` | false | Показать, что будет собрано. | | `--non-interactive` | false | Без интерактивного интерфейса и прогресс-баров. | -| `--keep-going` | false | Продолжать после неудачной сборки. | - -## plugins pack - -`easyp-svc plugins pack [options] [path]` - -Пишет каталог каждой версии из `path` в -`{out}/{group}/{name}/{version}/plugin.tgz` — ту же раскладку, что и ключи S3, -так что дерево можно потом загрузить через `push --packed`. - -| Флаг | Смысл | -|---|---| -| `--out` | Куда писать архивы. | -| `--filter` | Glob-фильтр. | -| `--force` | Переупаковать существующие архивы. | -| `--dry-run` | Только показать план. | -| `--non-interactive` | Без интерактивного интерфейса. | - -## plugins push - -`easyp-svc plugins push [options] [path]` - -Упаковывает каталог каждой версии и загружает его в -`{prefix}{group}/{name}/{version}/plugin.tgz`. Запускайте до `register`. -Повторная загрузка уже зарегистрированной версии с `--force` делает -недействительной записанную контрольную сумму, и `register` это не исправит — он -пропускает существующие версии. Выпустите новую версию или сначала удалите старую -(см. [Плагины](/ru/docs/api-service/plugins#push-force-after-register)). +| `--keep-going` | **true** | Продолжать после неудачной сборки. | +| `--runtime-image` | `debian:trixie-slim` | Образ, в котором пробно запускается каждый собранный плагин; должен совпадать с базой образа сервиса. | +| `--no-smoke` | false | Не запускать плагин. Архив всё равно проверяется на распаковку тем же способом, что в сервисе. | + +## plugins publish + +`easyp-svc plugins publish [options] [registry-path]` + +Проводит каждую версию за один проход: собирает её из реестра (или берёт её +`plugin.tgz` из `--archives`), проверяет, загружает архив в +`{prefix}{group}/{name}/{version}/plugin.tgz` с sha256 в метаданных объекта, +регистрирует — `CreatePlugin`, или `UpdatePlugin` для версии, которая у сервиса +уже есть, — и удаляет локальную копию, прежде чем место понадобится следующей +версии. Нужен сервер v1.2.0 или новее; на более старом команда останавливается на +первой регистрации и сообщает об этом. + +Источник — ровно один из: + +- **путь к реестру**, собираемый версия за версией; +- `--archives ` — дерево `{group}/{name}/{version}/plugin.tgz`, записанное + `plugins build --archive`; +- ни того ни другого, но `--plugins-dir`: регистрируется то, что уже распаковано + в этом каталоге. + +Назначение: объектное хранилище, заданное S3-флагами и `--cfg`; или, с +`--plugins-dir `, этот каталог — `plugins_dir` сервиса на той же машине, +работающего без объектного хранилища, — куда каждый архив распаковывается вместо +загрузки. + +Команда идемпотентна. Без `--force` версия, которая зарегистрирована и лежит в +хранилище, пропускается; лежащая в хранилище, но не зарегистрированная — только +регистрируется; загрузка пропускается, если в хранилище уже тот же архив. +Прерванный запуск продолжается повторным запуском. `--force` пересобирает и +перепубликует всё, что подходит под фильтр (см. +[Плагины](/ru/docs/api-service/plugins#republishing)). Прежде чем что-то +собирать, `publish` проверяет, что хранилище отвечает, что сервис принимает +токен, и какие версии у сервиса уже есть. | Флаг | По умолчанию | Смысл | |---|---|---| +| `--archives` | — | Опубликовать это дерево `plugin.tgz` вместо сборки. | +| `--plugins-dir` | — | Распаковывать в этот каталог вместо загрузки. Без другого источника — зарегистрировать то, что в нём уже есть. | +| `--workdir` | `$TMPDIR/easyp-publish` | Где собираются версии; каждая удаляется после публикации. | +| `--keep` | false | Оставлять собранные версии в `--workdir` после публикации. | +| `--filter` | — | Glob-фильтр. | +| `--force` | false | Публиковать версии, которые у сервиса уже есть: пересобрать и заменить их конфиг. | +| `--build-parallel` | `4` | Одновременных Docker-сборок. | +| `--parallel`, `-p` | `8` | Версий, одновременно загружаемых и регистрируемых. Держите не выше `rate_limit.max_concurrent_per_ip` сервера. | +| `--dry-run` | false | Показать, что будет опубликовано, ничего не собирая и никуда не обращаясь. | +| `--non-interactive` | false | Без интерактивного интерфейса. | +| `--runtime-image`, `--no-smoke` | как у `build` | Проверка. | | `--cfg` | — | **Полный** конфиг сервиса; `registry.s3` заполняет то, что не передано флагами. Фрагмент только с `registry.s3` отвергается. | | `--bucket` | — | Бакет (переопределяет конфиг). | | `--endpoint` | — | URL эндпоинта, например `http://localhost:9000`. | | `--region` | — | Регион. | | `--prefix` | — | Префикс ключей. | | `--force-path-style` | false | Path-style адресация (MinIO, RustFS). | -| `--filter` | — | Glob-фильтр. | -| `--packed` | false | `path` — дерево, записанное `plugins pack`. | -| `--parallel`, `-p` | `8` | Одновременных загрузок. | -| `--force` | false | Загрузить заново архивы, которые уже есть в хранилище. | -| `--dry-run` | false | Показать план, не обращаясь к хранилищу. | -| `--non-interactive` | false | Без интерактивного интерфейса. | - -Учётные данные берутся из стандартной цепочки AWS (`AWS_ACCESS_KEY_ID`, -`AWS_SECRET_ACCESS_KEY`, профили). Прерванный запуск продолжается повторным -запуском. - -## plugins register - -`easyp-svc plugins register [options] [path]` - -Вызывает `CreatePlugin` для каждой версии в `path` с путём к команде в том виде, -в каком его видит сервис. С `--packed` `path` — дерево `pack`; этого достаточно, -чтобы регистрировать с машины, которая плагины не собирала, если архивы уже в -хранилище. - -| Флаг | По умолчанию | Смысл | -|---|---|---| | `--addr` | `localhost:23410` | gRPC-адрес сервиса. | -| `--cfg` | — | Конфиг сервиса; `registry.plugins_dir` используется как `--plugins-prefix`, если флаг не задан. | -| `--plugins-prefix` | `/plugins` | `plugins_dir` на сервере. | | `--token` | `$EASYP_TOKEN` | Токен записи. | | `--tls-ca` | системное хранилище | CA, подписавший сертификат сервера. | | `--tls-cert`, `--tls-key` | — | Клиентский сертификат и ключ для взаимного TLS. | | `--insecure` | false | Без шифрования. Только для локальной разработки. | -| `--filter` | — | Glob-фильтр. | -| `--packed` | false | `path` — дерево `pack`. | -| `--parallel`, `-p` | `8` | Одновременных регистраций. Сервер читает каждый архив, чтобы посчитать контрольную сумму; держите не выше `rate_limit.max_concurrent_per_ip` сервера. | -| `--fail-on-error` | **true** | Ненулевой код выхода, если хоть одна регистрация не удалась. | -| `--dry-run` | false | Показать запланированные вызовы, не обращаясь к серверу. | -| `--non-interactive` | false | Без интерактивного интерфейса. | -Уже зарегистрированная версия считается пропущенной, а не ошибкой. +Учётные данные хранилища берутся из стандартной цепочки AWS +(`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, профили). Объектные хранилища +обычно ограничивают одно соединение намного ниже ширины канала, поэтому +пропускную способность дают потоки `--parallel`; `publish` отступает при +упоре в лимиты частоты и конкурентности сервера и перестаёт начинать новые +версии, когда достигнут лимит плагинов лицензии. Если хоть одна версия не +опубликована, код выхода ненулевой. + +```bash +# Собрать на одной машине… +easyp-svc plugins build registry --archive --output plugin-archives + +# …и опубликовать откуда угодно, где есть доступ к хранилищу и токен записи. +export AWS_ACCESS_KEY_ID=… AWS_SECRET_ACCESS_KEY=… EASYP_TOKEN=… +easyp-svc plugins publish --archives plugin-archives \ + --endpoint https://storage.example.com --bucket easyp-plugins --force-path-style \ + --addr plugins.example.com:443 --parallel 24 +``` + + +Удалено в v1.2.0: `plugins push` и `plugins register` заменены на +`plugins publish`, `plugins pack` — на `plugins build --archive`, флаги +`--packed` и `--plugins-prefix` убраны — команду выводит сервис. + ## auth new-token @@ -166,6 +188,9 @@ PostgreSQL, а перезапуск контейнера при каждом с ## Сокращения в Taskfile `Taskfile.yml` репозитория сервиса оборачивает эти команды для разработки: -`task build-plugins`, `FILTER=… task build-plugins-filter`, `task push-plugins`, -`task register-plugins`, `task up`, `task up-minimal`, `task run`, `task run-local`. -Они рассчитаны на адреса и учётные данные стенда разработки. +`task build-plugins`, `FILTER=… task build-plugins-filter`, `task publish-plugins` +(собрать, загрузить и зарегистрировать на стенде разработки; `FILTER=…` — для +части каталога), `task publish-archives` (дерево `build --archive` в любой сервис +и хранилище), `task up`, `task up-minimal`, `task run`, `task run-local`. Все, +кроме `publish-archives`, рассчитаны на адреса и учётные данные стенда +разработки. diff --git a/content/docs/api-service/plugins.mdx b/content/docs/api-service/plugins.mdx index 587a3a8..103fa71 100644 --- a/content/docs/api-service/plugins.mdx +++ b/content/docs/api-service/plugins.mdx @@ -89,13 +89,16 @@ sequenceDiagram participant S3 as S3 participant Svc as EasyP service participant C as Client - B->>B: plugins build → plugins/group/name/version/ - B->>S3: plugins push → plugin.tgz - B->>Svc: plugins register (CreatePlugin) - Svc->>S3: read archive, record sha256 - Svc-->>B: registered + loop plugins publish, per version + B->>B: docker build, verify (unpack + smoke run) + B->>S3: upload plugin.tgz (sha256 in object metadata) + B->>Svc: CreatePlugin with config.sha256 + Svc->>S3: check the archive is there with that sha256 + Svc-->>B: registered (command derived) + B->>B: delete the local copy + end C->>Svc: GenerateCode - opt cache miss + opt cache miss, or archive republished Svc->>S3: download archive Svc->>Svc: verify sha256, unpack end @@ -103,36 +106,54 @@ sequenceDiagram Svc-->>C: CodeGeneratorResponse ``` -1. **build** — `easyp-svc plugins build ` runs a Docker build - per version and extracts the result to `--output` (`plugins/` by default). - Needs Docker. Produces Linux binaries. -2. **push** — packs each version directory into `plugin.tgz` and uploads it. - Skipped in local mode. `plugins pack` + `plugins push --packed` split this - across machines. -3. **register** — calls `CreatePlugin` for each version with the command path as - the service will see it (`--plugins-prefix`, default `/plugins`). Needs a - write token. - -**Push before register.** With object storage enabled, registration of a version -whose archive is absent fails with `FAILED_PRECONDITION` / -`BINARY_NOT_UPLOADED`. The service computes the checksum itself, so a client -cannot register a hash of its choosing. - -### `push --force` after register breaks the checksum [#push-force-after-register] - -The object changes under -the recorded hash. A service that already has the version unpacked keeps running -the old binary, because the checksum is checked only on download; one with a cold -cache fails with `plugin archive checksum mismatch`. Registering again does not -help — `CreatePlugin` answers `ALREADY_EXISTS` and `plugins register` counts the -version as skipped. Either publish the change as a new version, or delete the -version first (`DeletePlugin` removes the row, the archive and the cached copy), -then push and register. `UpdatePlugin` with a config recomputes the checksum -but leaves an already unpacked copy in place. - -In **local mode** (no bucket) step 2 is skipped and the files must be present -under `plugins_dir` on the service's disk — mounted, copied, or built into a -derived image. +`easyp-svc plugins publish ` does the whole thing, one version at +a time, so a catalogue never has to fit on the publishing machine and nothing is +registered before its archive is in place: + +1. **build** — a Docker build of the version. Needs Docker. Produces Linux + binaries. +2. **verify** — the bundle is packed and unpacked with the service's own + unpacker, and `plugin` is smoke-run in the service's base image. A version + that fails goes no further. +3. **upload** — `plugin.tgz` goes to storage with its sha256 in the object + metadata. Skipped when storage already holds the same archive. +4. **register** — `CreatePlugin` (or `UpdatePlugin`, if the service already has + the version) with the sha256 in the config. The service checks the archive is + in storage with that checksum and derives the command + (`{plugins_dir}/{group}/{name}/{version}/plugin`) itself. Needs a write token + and a v1.2.0 or newer service. +5. **delete** the local copy. + +Re-running is safe: a version that is registered and in storage is skipped, one +in storage but not registered is only registered. `plugins build --archive` + +`plugins publish --archives` split building and publishing across machines. + +With object storage enabled, a version whose archive is absent cannot be +registered (`FAILED_PRECONDITION` / `BINARY_NOT_UPLOADED`), and one whose stored +archive is not the `sha256` the config names fails with `FAILED_PRECONDITION` / +`CHECKSUM_MISMATCH`. The client can name a checksum, but only the one the stored +archive actually has. + +### Republishing a version [#republishing] + +`plugins publish --force` rebuilds a version the service already has, uploads +the new archive and replaces the version's config with the new checksum through +`UpdatePlugin`. Every replica then refetches its unpacked copy on next use: each +keeps a `.sha256` marker beside the unpacked directory and compares it with the +registry. Directories unpacked before v1.2.0 have no marker and count as current. +The config `publish` sends is the whole config, so a hand-edited `env` or +`timeout` does not survive republishing. + +Overwriting an archive in storage any other way still breaks the version: the +object no longer has the recorded checksum, and the next download fails with +`FAILED_PRECONDITION` / `CHECKSUM_MISMATCH`. Run `publish --force` for it, or +publish the change as a new version. + +In **local mode** (no bucket) the files must be present under `plugins_dir` on +the service's disk. On the same host, `plugins publish --plugins-dir ` +unpacks each version there instead of uploading it; given no registry and no +`--archives`, it registers what is already in that directory. Otherwise mount, +copy, or build the files into a derived image, then register them. All commands and flags: [Operator CLI](/docs/api-service/operator-cli). @@ -151,14 +172,15 @@ Each registered version is one row in the `plugins` table. Its `config` is: | Field | Meaning | |---|---| -| `command` | Executable and arguments. `command[0]` must resolve inside `registry.plugins_dir`, checked at registration and again before each run after resolving symlinks. | +| `command` | Executable and arguments. Optional since v1.2.0: when omitted, the service derives `{plugins_dir}/{group}/{name}/{version}/plugin`. When given — for a plugin that needs arguments — `command[0]` must resolve inside `registry.plugins_dir`, checked at registration and again before each run after resolving symlinks. | | `env` | The plugin's entire environment. Nothing from the service is inherited. | | `timeout` | Optional per-plugin limit, applied inside `worker_pool.generation_timeout`. | -| `sha256` | Written by the service from the archive in storage. Absent in local mode. | +| `sha256` | The archive's checksum. When the request names one, registration fails with `CHECKSUM_MISMATCH` unless the stored archive has exactly it; when not, the service records the stored archive's checksum itself. Absent in local mode. | -`UpdatePlugin` takes a field mask (`config`, `tags`); an update recomputes the -checksum. `DeletePlugin` removes the row and, with object storage, the archive -and the cached directory. +`UpdatePlugin` takes a field mask (`config`, `tags`); a config update records the +checksum the same way, and a changed `sha256` makes every replica refetch the +archive. `DeletePlugin` removes the row and, with object storage, the archive and +the cached directory. ## Adding a plugin diff --git a/content/docs/api-service/plugins.ru.mdx b/content/docs/api-service/plugins.ru.mdx index 2ed2eae..b5f5438 100644 --- a/content/docs/api-service/plugins.ru.mdx +++ b/content/docs/api-service/plugins.ru.mdx @@ -88,13 +88,16 @@ sequenceDiagram participant S3 as S3 participant Svc as Сервис EasyP participant C as Клиент - B->>B: plugins build → plugins/group/name/version/ - B->>S3: plugins push → plugin.tgz - B->>Svc: plugins register (CreatePlugin) - Svc->>S3: читает архив, записывает sha256 - Svc-->>B: зарегистрирован + loop plugins publish, по версии + B->>B: docker build, проверка (распаковка + пробный запуск) + B->>S3: загрузить plugin.tgz (sha256 в метаданных объекта) + B->>Svc: CreatePlugin с config.sha256 + Svc->>S3: проверить, что архив есть и sha256 совпадает + Svc-->>B: зарегистрирован (команда выведена) + B->>B: удалить локальную копию + end C->>Svc: GenerateCode - opt cache miss + opt cache miss или архив перепубликован Svc->>S3: скачать архив Svc->>Svc: сверить sha256, распаковать end @@ -102,40 +105,60 @@ sequenceDiagram Svc-->>C: CodeGeneratorResponse ``` -1. **build** — `easyp-svc plugins build ` запускает Docker-сборку - на каждую версию и распаковывает результат в `--output` (по умолчанию - `plugins/`). Нужен Docker. Получаются линуксовые бинарники. -2. **push** — упаковывает каталог каждой версии в `plugin.tgz` и загружает. В - локальном режиме пропускается. `plugins pack` + `plugins push --packed` - позволяют разнести это по разным машинам. -3. **register** — вызывает `CreatePlugin` для каждой версии с путём к команде в - том виде, в каком его увидит сервис (`--plugins-prefix`, по умолчанию - `/plugins`). Нужен токен записи. - -**Push — до register.** С включённым объектным хранилищем регистрация версии без -архива падает с `FAILED_PRECONDITION` / `BINARY_NOT_UPLOADED`. Контрольную сумму -сервис считает сам, так что клиент не может зарегистрировать хеш по своему -выбору. - -### `push --force` после register ломает контрольную сумму [#push-force-after-register] - -Объект меняется под -записанным хешем. Сервис, у которого версия уже распакована, продолжает запускать -старый бинарник: сумма проверяется только при скачивании; при холодном кеше -скачивание падает с `plugin archive checksum mismatch`. Повторная регистрация не -помогает — `CreatePlugin` отвечает `ALREADY_EXISTS`, а `plugins register` считает -версию пропущенной. Либо выпустите изменение новой версией, либо сначала удалите -версию (`DeletePlugin` удаляет строку, архив и закешированную копию), затем push и -register. `UpdatePlugin` с конфигом пересчитывает сумму, но уже распакованную -копию не трогает. - -В **локальном режиме** (без бакета) шаг 2 пропускается, а файлы должны лежать в -`plugins_dir` на диске сервиса — смонтированные, скопированные или вшитые в -производный образ. +`easyp-svc plugins publish ` делает всё целиком, по одной версии +за раз, так что каталог никогда не обязан помещаться на публикующей машине и +ничего не регистрируется раньше, чем его архив окажется на месте: + +1. **build** — Docker-сборка версии. Нужен Docker. Получаются линуксовые + бинарники. +2. **verify** — бандл упаковывается и распаковывается распаковщиком самого + сервиса, а `plugin` пробно запускается в базовом образе сервиса. Версия, не + прошедшая проверку, дальше не идёт. +3. **upload** — `plugin.tgz` загружается в хранилище с sha256 в метаданных + объекта. Пропускается, если в хранилище уже тот же архив. +4. **register** — `CreatePlugin` (или `UpdatePlugin`, если версия у сервиса уже + есть) с sha256 в конфиге. Сервис проверяет, что архив лежит в хранилище с этой + контрольной суммой, и сам выводит команду + (`{plugins_dir}/{group}/{name}/{version}/plugin`). Нужны токен записи и сервис + v1.2.0 или новее. +5. **delete** — локальная копия удаляется. + +Повторный запуск безопасен: версия, которая зарегистрирована и лежит в +хранилище, пропускается; лежащая в хранилище, но не зарегистрированная — только +регистрируется. `plugins build --archive` + `plugins publish --archives` +позволяют собирать и публиковать на разных машинах. + +С включённым объектным хранилищем версию без архива зарегистрировать нельзя +(`FAILED_PRECONDITION` / `BINARY_NOT_UPLOADED`), а версия, чей архив в хранилище +не совпадает с `sha256` из конфига, падает с `FAILED_PRECONDITION` / +`CHECKSUM_MISMATCH`. Клиент может назвать контрольную сумму, но только ту, +которая действительно у архива в хранилище. + +### Повторная публикация версии [#republishing] + +`plugins publish --force` пересобирает версию, которая у сервиса уже есть, +загружает новый архив и заменяет конфиг версии новой контрольной суммой через +`UpdatePlugin`. После этого каждая реплика при следующем обращении заново +скачивает распакованную копию: рядом с распакованным каталогом каждая хранит +маркер `.sha256` и сверяет его с реестром. Каталоги, распакованные до v1.2.0, +маркера не имеют и считаются актуальными. `publish` отправляет конфиг целиком, +поэтому вручную изменённые `env` или `timeout` повторную публикацию не +переживают. + +Перезапись архива в хранилище любым другим способом по-прежнему ломает версию: +у объекта уже не та контрольная сумма, что записана, и следующее скачивание +падает с `FAILED_PRECONDITION` / `CHECKSUM_MISMATCH`. Запустите для неё +`publish --force` или выпустите изменение новой версией. + +В **локальном режиме** (без бакета) файлы должны лежать в `plugins_dir` на диске +сервиса. На той же машине `plugins publish --plugins-dir ` распаковывает +туда каждую версию вместо загрузки; без реестра и без `--archives` регистрирует +то, что в этом каталоге уже есть. Иначе смонтируйте файлы, скопируйте или вшейте +в производный образ, а затем зарегистрируйте. Все команды и флаги — в [CLI оператора](/ru/docs/api-service/operator-cli). -## Зарегистрированная конфигурация +## Зарегистрированная конфигурация [#registered-configuration] Каждая зарегистрированная версия — одна строка таблицы `plugins`. Её `config`: @@ -150,14 +173,15 @@ register. `UpdatePlugin` с конфигом пересчитывает сумм | Поле | Смысл | |---|---| -| `command` | Исполняемый файл и аргументы. `command[0]` должен разрешаться внутрь `registry.plugins_dir`; проверяется при регистрации и повторно перед каждым запуском после разрешения симлинков. | +| `command` | Исполняемый файл и аргументы. С v1.2.0 необязателен: если не задан, сервис выводит `{plugins_dir}/{group}/{name}/{version}/plugin`. Если задан — для плагина, которому нужны аргументы, — `command[0]` должен разрешаться внутрь `registry.plugins_dir`; проверяется при регистрации и повторно перед каждым запуском после разрешения симлинков. | | `env` | Всё окружение плагина. От сервиса ничего не наследуется. | | `timeout` | Необязательный лимит плагина внутри `worker_pool.generation_timeout`. | -| `sha256` | Записывается сервисом по архиву в хранилище. В локальном режиме отсутствует. | +| `sha256` | Контрольная сумма архива. Если запрос её указывает, регистрация падает с `CHECKSUM_MISMATCH`, если у архива в хранилище не ровно она; если нет — сервис сам записывает сумму архива из хранилища. В локальном режиме отсутствует. | -`UpdatePlugin` принимает маску полей (`config`, `tags`); изменение пересчитывает -контрольную сумму. `DeletePlugin` удаляет строку и, при объектном хранилище, -архив и закешированный каталог. +`UpdatePlugin` принимает маску полей (`config`, `tags`); изменение конфига +записывает контрольную сумму так же, а изменившийся `sha256` заставляет каждую +реплику заново скачать архив. `DeletePlugin` удаляет строку и, при объектном +хранилище, архив и закешированный каталог. ## Добавление плагина [#adding-a-plugin] diff --git a/content/docs/api-service/quickstart.mdx b/content/docs/api-service/quickstart.mdx index 80e5f54..20a4b3b 100644 --- a/content/docs/api-service/quickstart.mdx +++ b/content/docs/api-service/quickstart.mdx @@ -63,7 +63,7 @@ services: retries: 30 service: - image: ghcr.io/easyp-tech/service:v1.0.2 + image: ghcr.io/easyp-tech/service:v1.2.0 command: ["service", "start"] depends_on: postgres: @@ -99,15 +99,19 @@ without object storage, and a read-only mount stops it with ```bash EASYP_TOKEN= \ -go run ./cmd/easyp-svc plugins register \ +go run ./cmd/easyp-svc plugins publish \ + --plugins-dir plugins \ --addr localhost:23410 \ - --insecure \ - plugins + --insecure ``` -`register` walks `plugins/`, and for each version calls `CreatePlugin` with the -command path as the service sees it (`/plugins/protocolbuffers/go/v1.36.10/plugin`). -Without the token every registration fails with `Unauthenticated`. +With neither a registry path nor `--archives`, `publish` takes `--plugins-dir` +as its source: it walks `plugins/` and calls `CreatePlugin` for each version +found there. The service derives the command path itself +(`/plugins/protocolbuffers/go/v1.36.10/plugin`), so the client never needs to +know where the directory is mounted. Without the token `publish` stops before +registering anything, because the service refuses it. Needs a v1.2.0 or newer +service. ## 5. Generate code @@ -143,10 +147,8 @@ mutual TLS, and Grafana, Loki, Tempo, Mimir and Pyroscope. The service repository's `Taskfile.yml` drives it: ```bash -FILTER='protocolbuffers/go:v1.36.10' task build-plugins-filter -task up # generates dev certificates, starts the stack -task push-plugins # uploads archives to the object store -task register-plugins # registers them through Traefik +task up # generates dev certificates, starts the stack +FILTER='protocolbuffers/go:v1.36.10' task publish-plugins # builds, uploads and registers through Traefik ``` There the gRPC port is reached through Traefik at `easyp.api.localhost:4443`, diff --git a/content/docs/api-service/quickstart.ru.mdx b/content/docs/api-service/quickstart.ru.mdx index 38e2ece..1a512a8 100644 --- a/content/docs/api-service/quickstart.ru.mdx +++ b/content/docs/api-service/quickstart.ru.mdx @@ -63,7 +63,7 @@ services: retries: 30 service: - image: ghcr.io/easyp-tech/service:v1.0.2 + image: ghcr.io/easyp-tech/service:v1.2.0 command: ["service", "start"] depends_on: postgres: @@ -99,16 +99,18 @@ curl -i http://localhost:23412/ # 200, как только отвечае ```bash EASYP_TOKEN=<токен из шага 2> \ -go run ./cmd/easyp-svc plugins register \ +go run ./cmd/easyp-svc plugins publish \ + --plugins-dir plugins \ --addr localhost:23410 \ - --insecure \ - plugins + --insecure ``` -`register` обходит `plugins/` и для каждой версии вызывает `CreatePlugin` с путём -к команде в том виде, в каком его видит сервис -(`/plugins/protocolbuffers/go/v1.36.10/plugin`). Без токена каждая регистрация -завершается ошибкой `Unauthenticated`. +Без пути к реестру и без `--archives` источником для `publish` служит +`--plugins-dir`: команда обходит `plugins/` и для каждой найденной версии +вызывает `CreatePlugin`. Путь к команде сервис выводит сам +(`/plugins/protocolbuffers/go/v1.36.10/plugin`), так что клиенту не нужно знать, +куда смонтирован каталог. Без токена `publish` останавливается, ничего не +зарегистрировав: сервис его отвергает. Нужен сервис v1.2.0 или новее. ## 5. Сгенерировать код @@ -145,10 +147,8 @@ Grafana, Loki, Tempo, Mimir и Pyroscope. Им управляет `Taskfile.yml` репозитория сервиса: ```bash -FILTER='protocolbuffers/go:v1.36.10' task build-plugins-filter -task up # генерирует dev-сертификаты, поднимает стенд -task push-plugins # загружает архивы в хранилище -task register-plugins # регистрирует их через Traefik +task up # генерирует dev-сертификаты, поднимает стенд +FILTER='protocolbuffers/go:v1.36.10' task publish-plugins # собирает, загружает и регистрирует через Traefik ``` Там gRPC доступен через Traefik на `easyp.api.localhost:4443`, а Grafana — на diff --git a/content/docs/api-service/runbooks.mdx b/content/docs/api-service/runbooks.mdx index 7384a34..45aa5c8 100644 --- a/content/docs/api-service/runbooks.mdx +++ b/content/docs/api-service/runbooks.mdx @@ -205,9 +205,12 @@ More than `prometheusRule.generationErrorRatio` of generations are failing. always one plugin, not a general fault. `error_type` is `transient` (the error text contained `connection refused` or `temporary failure` and was retried) or `permanent` (everything else). -2. If the logs say `plugin archive checksum mismatch`, the archive in object - storage does not match what was recorded at registration. Do not "fix" it by - re-registering: find out why it changed. +2. If the logs say `plugin archive checksum mismatch` (reason + `CHECKSUM_MISMATCH`), the archive in object storage does not match what was + recorded at registration. Find out why it changed first. If the stored + archive is the right one, `plugins publish --force --filter ''` + records its checksum; if it is not, the same command with the right archive + (`--archives`) replaces it — every replica refetches on next use. 3. `output limit exceeded` means the plugin wrote more than `config.registry.maxOutputSize`. 4. Otherwise, run the plugin by hand with the same request. Plugin failures are diff --git a/content/docs/api-service/runbooks.ru.mdx b/content/docs/api-service/runbooks.ru.mdx index aa04c67..cb418be 100644 --- a/content/docs/api-service/runbooks.ru.mdx +++ b/content/docs/api-service/runbooks.ru.mdx @@ -213,9 +213,13 @@ COMMIT; один плагин, а не общая неисправность. `error_type` — `transient` (в тексте ошибки было `connection refused` или `temporary failure`, и её повторяли) или `permanent` (всё остальное). -2. Если в логах `plugin archive checksum mismatch`, архив в объектном хранилище - не соответствует записанному при регистрации. Не «чините» это - перерегистрацией: выясните, почему он изменился. +2. Если в логах `plugin archive checksum mismatch` (reason + `CHECKSUM_MISMATCH`), архив в объектном хранилище не соответствует + записанному при регистрации. Сначала выясните, почему он изменился. Если в + хранилище правильный архив, `plugins publish --force --filter ''` + запишет его контрольную сумму; если нет — та же команда с правильным архивом + (`--archives`) заменит его, и каждая реплика перекачает его при следующем + использовании. 3. `output limit exceeded` означает, что плагин вывел больше `config.registry.maxOutputSize`. 4. Иначе запустите плагин руками с тем же запросом. Отказы плагинов diff --git a/content/docs/api-service/upgrading.mdx b/content/docs/api-service/upgrading.mdx index 09d71a7..ca58edc 100644 --- a/content/docs/api-service/upgrading.mdx +++ b/content/docs/api-service/upgrading.mdx @@ -34,6 +34,48 @@ A rename is handled without breaking anyone: the old configuration key keeps working for one release while warning, naming both spellings and the line to change, and only then stops. +## v1.1.x → v1.2.0 + +### `plugins publish` replaces `push`, `register` and `pack` + +The three commands, `--packed` and `--plugins-prefix` are gone, and so are the +`push-plugins`, `register-plugins` and `push-archives` tasks. One command takes +each version from source to registered: + +| Before | Now | +|---|---| +| `plugins build` → `plugins push` → `plugins register` | `plugins publish ` | +| `plugins pack --out ` | `plugins build --archive --output ` | +| `plugins push --packed` + `plugins register --packed` | `plugins publish --archives ` | +| `plugins register` against a `plugins_dir` | `plugins publish --plugins-dir ` | + +`publish` needs a v1.2.0 server: it registers without a `command`, which older +servers refuse — it says so and stops. Upgrade the service first. + +### Registration derives the command and checks a claimed checksum + +`command` in a plugin config is optional and defaults to +`{plugins_dir}/{group}/{name}/{version}/plugin`; an explicit one still works and +is still held to `plugins_dir`. A `sha256` in the config is now checked against +the stored archive instead of being overwritten, and a mismatch is +`FAILED_PRECONDITION` with the new reason `CHECKSUM_MISMATCH`. The service takes +the checksum from the object's `x-amz-meta-sha256` when the uploader set it, so +registering no longer reads the whole archive. + +### A checksum mismatch is `FAILED_PRECONDITION`, not `INTERNAL` + +A download that does not match the registry used to reach clients as +`INTERNAL`. It is now `FAILED_PRECONDITION` / `CHECKSUM_MISMATCH`: the fix is to +republish, not to retry. A client matching on reasons gains one; one matching on +`INTERNAL` for this case must change. + +### Republishing reaches every replica + +`plugins publish --force` replaces a version's config with the new checksum, and +each replica refetches its unpacked copy on next use — it keeps a `.sha256` +marker beside it. Copies unpacked by an older service have no marker and count +as current; republish a version once to bring its copies in line. + ## v1.0.0 → v1.0.2 ### The image checks its own health diff --git a/content/docs/api-service/upgrading.ru.mdx b/content/docs/api-service/upgrading.ru.mdx index 099139c..99721c5 100644 --- a/content/docs/api-service/upgrading.ru.mdx +++ b/content/docs/api-service/upgrading.ru.mdx @@ -34,6 +34,49 @@ description: "Заметки по обновлению API Service от рели работать один релиз, предупреждая и называя оба написания и строку, которую надо поправить, и только после этого перестаёт. +## v1.1.x → v1.2.0 + +### `plugins publish` вместо `push`, `register` и `pack` + +Трёх команд больше нет, как и флагов `--packed` и `--plugins-prefix`, и задач +`push-plugins`, `register-plugins` и `push-archives`. Одна команда проводит +каждую версию от исходников до регистрации: + +| Было | Стало | +|---|---| +| `plugins build` → `plugins push` → `plugins register` | `plugins publish ` | +| `plugins pack --out ` | `plugins build --archive --output ` | +| `plugins push --packed` + `plugins register --packed` | `plugins publish --archives ` | +| `plugins register` поверх `plugins_dir` | `plugins publish --plugins-dir ` | + +`publish` нужен сервер v1.2.0: он регистрирует без `command`, а старые серверы +такое отклоняют — команда об этом скажет и остановится. Сначала обновите сервис. + +### Регистрация выводит команду и проверяет заявленную контрольную сумму + +`command` в конфигурации плагина необязателен, по умолчанию это +`{plugins_dir}/{group}/{name}/{version}/plugin`; явная команда по-прежнему +работает и по-прежнему обязана лежать в `plugins_dir`. `sha256` в конфигурации +теперь сверяется с архивом в хранилище, а не перезаписывается, и расхождение — +это `FAILED_PRECONDITION` с новым reason `CHECKSUM_MISMATCH`. Контрольную сумму +сервис берёт из `x-amz-meta-sha256` объекта, если её записал загрузчик, так что +регистрация больше не читает архив целиком. + +### Несовпадение контрольной суммы — `FAILED_PRECONDITION`, а не `INTERNAL` + +Скачанный архив, не совпавший с реестром, раньше доходил до клиента как +`INTERNAL`. Теперь это `FAILED_PRECONDITION` / `CHECKSUM_MISMATCH`: лечится +перепубликацией, а не повтором. Клиент, сверяющий reason, получает ещё один; +тот, что ждал здесь `INTERNAL`, нужно поправить. + +### Перепубликация доходит до каждой реплики + +`plugins publish --force` заменяет конфигурацию версии новой контрольной суммой, +и каждая реплика перекачивает свою распакованную копию при следующем +использовании — рядом с ней лежит маркер `.sha256`. Копии, распакованные старым +сервисом, маркера не имеют и считаются актуальными; перепубликуйте версию один +раз, чтобы выровнять их. + ## v1.0.0 → v1.0.2 ### Образ проверяет своё здоровье сам