Plugins
The plugin catalogue, how a plugin version gets from a Dockerfile into the service, and how to add your own.
Names
A plugin is addressed as group/name:version, for example
protocolbuffers/go:v1.36.10.
groupandname: lowercase letters, digits and-, starting with a letter.version:vX.YorvX.Y.Z(protobuf's own releases arev33.1-style), orlatest.
latest picks the registered version that sorts last as a string. With
v1.36.9 and v1.36.10 registered it returns v1.36.9. Pin versions in every
configuration that matters.
The catalogue
The service repository's registry/ directory holds a build recipe for every
plugin easyp-tech publishes. In v1.0.2 that is 80 plugins that build 1522 distinct versions (1743 are listed; see the catalog for why)
in ten groups:
| Group | Plugins |
|---|---|
protocolbuffers | 12 |
grpc | 13 |
bufbuild | 15 |
connectrpc | 9 |
community | 23 |
grpc-ecosystem | 2 |
apple | 2 |
anthropics | 2 |
googlecloudplatform | 1 |
pluginrpc | 1 |
Every plugin and version is listed in the Plugin catalog, generated from these files.
The catalogue is a recipe book, not a set of prebuilt artifacts: a deployment builds and registers the versions it wants. A Community licence allows 10 registered versions in total.
One plugin directory
# registry/protocolbuffers/go/plugin.yaml
build_args:
GO_MODULE: google.golang.org/protobuf/cmd/protoc-gen-go
versions:
- v1.36.10
- v1.36.9Optional keys in plugin.yaml: build_args (passed to every build),
dockerfile (a different file), args (arguments for the entrypoint). A
version entry may be a mapping that overrides these for itself or sets
skip: true.
What the Dockerfile must produce
- An executable at
/plugin. It reads aCodeGeneratorRequeston stdin, writes aCodeGeneratorResponseon stdout, and exits non-zero on failure. - Anything else it needs next to it. The whole image filesystem is
extracted into the version directory and shipped together: a jar, a
node_modules, a shared library. - One recipe for every version, selected by
ARG VERSION. - By convention in this repository: a
scratchor distroless final stage, running asnobody, base images pinned by digest, static binaries where the language allows.
The service does not run the image. Docker is used only at build time to produce
a directory of files; at run time the service executes plugin from that
directory as an ordinary process.
From recipe to runnable plugin
sequenceDiagram
participant B as Build machine / CI
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
C->>Svc: GenerateCode
opt cache miss
Svc->>S3: download archive
Svc->>Svc: verify sha256, unpack
end
Svc->>Svc: run plugin
Svc-->>C: CodeGeneratorResponse
- build —
easyp-svc plugins build <registry-path>runs a Docker build per version and extracts the result to--output(plugins/by default). Needs Docker. Produces Linux binaries. - push — packs each version directory into
plugin.tgzand uploads it. Skipped in local mode.plugins pack+plugins push --packedsplit this across machines. - register — calls
CreatePluginfor 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
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.
All commands and flags: Operator CLI.
Registered configuration
Each registered version is one row in the plugins table. Its config is:
{
"command": ["/plugins/protocolbuffers/go/v1.36.10/plugin"],
"env": {},
"timeout": "60s",
"sha256": "9f2c…"
}| 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. |
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. |
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.
Adding a plugin
To the public catalogue: add registry/<group>/<name>/ with a Dockerfile
and a plugin.yaml — community/<author>-<tool> if the project is not one of
the named groups — and open a pull request against
easyp-tech/service. The repository's
issue templates include a plugin request.
Only for your deployment: the same two files in your own directory tree.
plugins build takes any registry path, and registration does not care where a
recipe came from. Private plugins never have to be published anywhere.
Before registering a plugin someone else wrote, read Security: the plugin runs with the service's privileges.
Relationship to bufbuild/plugins
The group layout and the plugin set follow
bufbuild/plugins (Apache-2.0), the
catalogue behind Buf's remote plugins: the same ten owners, and the same
community/<author>-<tool> naming. The recipes are written for this service's
build (one Dockerfile per plugin taking ARG VERSION, a plugin.yaml version
list, /plugin as the entrypoint) rather than copied per version. The two
catalogues are maintained separately and their version lists differ.