EasyP

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.

  • group and name: lowercase letters, digits and -, starting with a letter.
  • version: vX.Y or vX.Y.Z (protobuf's own releases are v33.1-style), or latest.

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:

GroupPlugins
protocolbuffers12
grpc13
bufbuild15
connectrpc9
community23
grpc-ecosystem2
apple2
anthropics2
googlecloudplatform1
pluginrpc1

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

Dockerfile — takes ARG VERSION, produces /plugin
plugin.yaml — the versions to build
# registry/protocolbuffers/go/plugin.yaml
build_args:
  GO_MODULE: google.golang.org/protobuf/cmd/protoc-gen-go
versions:
  - v1.36.10
  - v1.36.9

Optional 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 a CodeGeneratorRequest on stdin, writes a CodeGeneratorResponse on 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 scratch or distroless final stage, running as nobody, 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
  1. 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.
  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

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…"
}
FieldMeaning
commandExecutable and arguments. command[0] must resolve inside registry.plugins_dir, checked at registration and again before each run after resolving symlinks.
envThe plugin's entire environment. Nothing from the service is inherited.
timeoutOptional per-plugin limit, applied inside worker_pool.generation_timeout.
sha256Written 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.

On this page