EasyP

Client Usage

Pointing the easyp CLI at the service, using the Go SDK, and calling the gRPC API directly.

easyp CLI

A plugin runs on the service when its entry in generate.plugins uses remote: instead of name:, path: or command::

generate:
  inputs:
    - directory:
        path: .
        root: proto
  plugins:
    - remote: "plugins.example.com/protocolbuffers/go:v1.36.10"
      out: gen/go
      opts:
        paths: source_relative
    - remote: "plugins.example.com/grpc/go:v1.6.2"
      out: gen/go
      opts:
        paths: source_relative
        require_unimplemented_servers: false

The remote: value

[http://|https://]host[:port]/group/name[:version]

PartBehaviour
schemeOptional. https:// or no scheme → TLS; http:// → plaintext.
hostA host containing localhost or 127.0.0.1 is always plaintext unless https:// is given.
portOptional. Without it the gRPC client's default applies (443 for TLS); the Helm chart's ingress serves on 443, a bare service listens on 23410.
group/nameMust match a registered plugin.
versionOptional. Omitted → latest, which is the version that sorts last as a string, not the newest semantic version.

opts are joined into the CodeGeneratorRequest parameter (paths=source_relative,require_unimplemented_servers=false), exactly as for local plugins.

What the CLI does and does not support

  • It parses and resolves .proto files locally, including dependencies from deps. Only the resulting CodeGeneratorRequest is sent.
  • One gRPC call per plugin, each on a new connection, with a fixed 30-second deadline. The service's own limit is 120 s; a plugin that needs longer than 30 s fails on the client side with DeadlineExceeded while the server may still be running it.
  • TLS uses the system trust store. There is no option for a private CA: install it into the system store of the machine running easyp.
  • No token and no client certificate. The CLI cannot talk to a service with auth.require_authentication: true or a listener that requires mutual TLS. Anonymous reads over TLS are the supported setup.
  • The protocol is EasyP's easyp.generator.v1.GeneratorAPI. A remote: value pointing at buf.build does not reach Buf's remote plugins — Buf's registry does not implement this API.

command: takes precedence over remote:; a plugin entry must have exactly one source.

Go SDK

github.com/easyp-tech/service/sdk, Apache-2.0.

package main

import (
	"context"
	"log"
	"time"

	"github.com/easyp-tech/service/sdk"
	"google.golang.org/protobuf/types/pluginpb"
)

func main() {
	client, err := sdk.NewClient(
		"plugins.example.com:443",
		sdk.WithGenerateCodeTimeout(2*time.Minute),
	)
	if err != nil {
		log.Fatal(err)
	}
	defer client.Close()

	ctx := context.Background()

	plugins, err := client.ListPlugins(ctx,
		sdk.WithFilter(sdk.PluginFilter{Group: "protocolbuffers"}))
	if err != nil {
		log.Fatal(err)
	}
	log.Printf("%d protocolbuffers plugins", len(plugins))

	var req *pluginpb.CodeGeneratorRequest // built from your descriptors
	resp, err := client.GenerateCode(ctx, "protocolbuffers/go:v1.36.10", req)
	if err != nil {
		log.Fatal(err)
	}
	log.Printf("%d files", len(resp.GetFile()))
}

Options

OptionDefaultPurpose
(none)TLS, system rootsTransport.
WithInsecure()—Plaintext, for a local service.
WithTransportCredentials(creds)—Private CA, client certificate (mTLS).
WithToken(token)—authorization: Bearer on every call. Needed for writes, and for reads when the service requires authentication.
WithGenerateCodeTimeout(d)30 sPer-call deadline when the context has none. Raise it towards the server's generation_timeout.
WithListPluginsTimeout(d)10 sSpans the whole paginated walk.
WithCreatePluginTimeout(d)120 sRegistration reads the archive from storage.
WithMaxRetries(n), WithRetryBaseDelay(d), WithRetryMaxDelay(d)3, 100 ms, 5 sExponential backoff.
WithMaxRecvMsgSize(n)64 MiBMatches the server's output limit.
WithHealthCheck(interval)30 sBackground connection-state check.
WithKeepaliveParams(p)—gRPC keepalive.
WithUnaryInterceptor(i), WithLoggingInterceptor(l), WithMetricsInterceptor(c)—Hooks.

Only UNAVAILABLE is retried. RESOURCE_EXHAUSTED (overload, rate limit, plugin ceiling) and DEADLINE_EXCEEDED are returned immediately: retrying the first adds load exactly when the server has none to spare, and retrying the second re-runs a generation that already used its full timeout.

Methods: GenerateCode, ListPlugins (walks every page), CreatePlugin, UpdatePlugin, DeletePlugin, Close.

gRPC directly

The contract is easyp.generator.v1.GeneratorAPI in api/easyp/generator/v1/generator.proto:

service GeneratorAPI {
  rpc GenerateCode(GenerateCodeRequest) returns (GenerateCodeResponse);
  rpc Plugins(PluginsRequest) returns (PluginsResponse);
  rpc CreatePlugin(CreatePluginRequest) returns (CreatePluginResponse);
  rpc UpdatePlugin(UpdatePluginRequest) returns (UpdatePluginResponse);
  rpc DeletePlugin(DeletePluginRequest) returns (DeletePluginResponse);
}

GenerateCodeRequest carries code_generator_request and plugin_name (group/name:version; the version is required at this level). The server does not serve reflection; write a descriptor set with the operator CLI:

easyp-svc api descriptor -o api.protoset
grpcurl -protoset api.protoset -plaintext localhost:23410 \
  easyp.generator.v1.GeneratorAPI/Plugins

Plugins is paginated: page_size defaults to 100 and is capped at 1000; pass next_page_token back as page_token with the same filters.

Errors

Every non-OK status carries google.rpc.ErrorInfo with domain easyp.tech and a stable reason. Branch on the reason, not the message.

ReasongRPC codeTypical cause
NOT_FOUNDNOT_FOUNDNo such plugin or version.
INVALID_PLUGIN_NAMEINVALID_ARGUMENTName not group/name:version.
INVALID_CONFIGINVALID_ARGUMENTBad plugin config on create/update.
GENERATION_FAILEDINTERNALThe plugin exited non-zero, wrote invalid output, or exceeded max_output_size.
SERVER_OVERLOADEDRESOURCE_EXHAUSTEDWorker or generation queue full.
MAX_PLUGINS_EXCEEDEDRESOURCE_EXHAUSTEDCommunity ceiling of 10 versions.
ALREADY_EXISTSALREADY_EXISTSVersion already registered.
BINARY_NOT_UPLOADEDFAILED_PRECONDITIONRegistering before plugins push.
STORAGE_UNAVAILABLEUNAVAILABLEObject storage down, or the archive is gone.
SHUTTING_DOWNUNAVAILABLEThe service is draining.
FEATURE_DENIEDPERMISSION_DENIEDLicence does not include the feature.
DEADLINE_EXCEEDEDDEADLINE_EXCEEDEDPlugin ran past its timeout.
CANCELEDCANCELLEDClient went away.
INTERNALINTERNALAnything else.

Rate-limit and per-client concurrency refusals come from interceptors: they are RESOURCE_EXHAUSTED without an ErrorInfo reason. Missing or unknown tokens are UNAUTHENTICATED.

On this page