From cc5b2e34f87bc55841534618ff31d07381037933 Mon Sep 17 00:00:00 2001 From: Quick <31828688+Quick104@users.noreply.github.com> Date: Mon, 14 Sep 2026 18:10:25 +0000 Subject: [PATCH 1/2] feat(network-access): add the network_access_provider.v1 capability Silo-Server/silo-server#1001 settled on shipping overlay-network access (Tailscale via tsnet first) as a plugin that owns the overlay listener and reverse-proxies to the host. The SDK had no capability for it and no way for a plugin to learn the host's local listeners, keep per-instance state, or push status. Add the NetworkAccessProvider service (Connect, Disconnect, GetStatus), a typed NetworkAccessProviderDescriptor on CapabilityDescriptor (field 11), and three RuntimeHost additions: GetHostInfo gains host role, host name, node id, an ingress token, and the listeners to expose; ReadInstanceState and WriteInstanceState give each plugin instance a host-scoped encrypted key-value store sized for tsnet node state; ReportNetworkAccessStatus pushes state changes. runtimehost ships an InstanceStateStore matching the two-method shape of tailscale's ipn.StateStore without importing tailscale, plus typed HostInfo fields and state constants. Manifest validation requires the descriptor on the capability and a path-safe provider slug. A stub example plugin exercises the contract without a tsnet dependency so silo-server can use it as a test fixture. All proto changes are additive. CapabilityServers gains a thirteenth field, so plugins using an unkeyed composite literal must switch to keyed fields; the compat test documents this. Co-Authored-By: Claude Fable 5.1 --- CONTRIBUTING.md | 1 + README.md | 13 +- docs/network-access-provider.md | 180 +++++ docs/runtime-host.md | 5 +- examples/hello-network-access/.gitignore | 1 + examples/hello-network-access/README.md | 27 + examples/hello-network-access/main.go | 166 ++++ examples/hello-network-access/manifest.json | 18 + pkg/pluginproto/silo/plugin/v1/common.pb.go | 115 +-- .../plugin/v1/network_access_provider.pb.go | 454 +++++++++++ .../v1/network_access_provider_grpc.pb.go | 219 ++++++ .../silo/plugin/v1/runtime_host.pb.go | 739 ++++++++++++++---- .../silo/plugin/v1/runtime_host_grpc.pb.go | 132 ++++ pkg/pluginsdk/capability/capability.go | 4 + pkg/pluginsdk/capability/capability_test.go | 16 + pkg/pluginsdk/manifest/manifest.go | 20 + .../manifest/network_access_provider_test.go | 62 ++ .../runtime/capability_servers_compat_test.go | 8 +- .../runtime/network_access_provider_test.go | 44 ++ pkg/pluginsdk/runtime/runtime.go | 11 + pkg/pluginsdk/runtimehost/client_test.go | 46 ++ pkg/pluginsdk/runtimehost/host_info.go | 72 +- pkg/pluginsdk/runtimehost/instance_state.go | 130 +++ .../runtimehost/instance_state_test.go | 140 ++++ pkg/pluginsdk/runtimehost/network_access.go | 32 + proto/silo/plugin/v1/common.proto | 6 + .../plugin/v1/network_access_provider.proto | 72 ++ proto/silo/plugin/v1/runtime_host.proto | 70 ++ 28 files changed, 2590 insertions(+), 213 deletions(-) create mode 100644 docs/network-access-provider.md create mode 100644 examples/hello-network-access/.gitignore create mode 100644 examples/hello-network-access/README.md create mode 100644 examples/hello-network-access/main.go create mode 100644 examples/hello-network-access/manifest.json create mode 100644 pkg/pluginproto/silo/plugin/v1/network_access_provider.pb.go create mode 100644 pkg/pluginproto/silo/plugin/v1/network_access_provider_grpc.pb.go create mode 100644 pkg/pluginsdk/manifest/network_access_provider_test.go create mode 100644 pkg/pluginsdk/runtime/network_access_provider_test.go create mode 100644 pkg/pluginsdk/runtimehost/instance_state.go create mode 100644 pkg/pluginsdk/runtimehost/instance_state_test.go create mode 100644 pkg/pluginsdk/runtimehost/network_access.go create mode 100644 proto/silo/plugin/v1/network_access_provider.proto diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 695a6a7..eda31dc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -33,6 +33,7 @@ go test ./... go vet ./... go build ./examples/hello-scheduled-task go build ./examples/hello-runtime-host +go build ./examples/hello-network-access gofmt -l . ``` diff --git a/README.md b/README.md index 864d133..9914794 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,7 @@ The SDK ships protobuf contracts for every capability the host understands: - `request_router.v1` - `scan_source.v1` - `watch_sync_provider.v1` +- `network_access_provider.v1` - `audiobook_backend.v1` - `ebook_backend.v1` @@ -50,7 +51,7 @@ A typical plugin: 3. Supports the `manifest` subcommand via `pkg/pluginsdk/runtime` so the host can introspect manifests without launching the plugin. 4. Is installed either from a catalog or by uploading a trusted binary to a Silo server. -For a minimal self-describing plugin, see [`examples/hello-scheduled-task`](examples/hello-scheduled-task). For a plugin that calls back into the host via `RuntimeHost` (publishing events, listing libraries), see [`examples/hello-runtime-host`](examples/hello-runtime-host). +For a minimal self-describing plugin, see [`examples/hello-scheduled-task`](examples/hello-scheduled-task). For a plugin that calls back into the host via `RuntimeHost` (publishing events, listing libraries), see [`examples/hello-runtime-host`](examples/hello-runtime-host). For a stub overlay-network provider, see [`examples/hello-network-access`](examples/hello-network-access). ## Operator-facing presentation @@ -204,6 +205,16 @@ and the order of returned watchlist states is the remote list order. Event `list_position` is presence-aware: an explicit zero means the first position, while omission means no requested ordering. +## Network access providers + +`network_access_provider.v1` lets a resident plugin give the deployment an +overlay-network identity (Tailscale, NetBird) and reverse-proxy overlay +traffic to the host's local listeners. The host starts these plugins at boot, +restarts them on crash, stores their per-instance state encrypted, and +aggregates status across the API server and proxy nodes. See +[docs/network-access-provider.md](docs/network-access-provider.md) for the +proxy contract, `GetHostInfo` fields, instance state, and enrollment rules. + ## Scan sources The `scan_source.v1` capability is for Autoscan providers. The host owns the diff --git a/docs/network-access-provider.md b/docs/network-access-provider.md new file mode 100644 index 0000000..d4f3ae3 --- /dev/null +++ b/docs/network-access-provider.md @@ -0,0 +1,180 @@ +# Network access providers (`network_access_provider.v1`) + +A network access provider gives a Silo deployment an overlay-network identity +(Tailscale via tsnet first; NetBird later) so clients reach Silo without port +forwarding or a public reverse proxy. The plugin owns the overlay client and a +reverse proxy in front of the host's local listeners. The host owns +supervision, per-instance state storage, status aggregation, access-path +routing, and the admin API. + +Added in SDK v0.16.0. Server support arrives in stages; check the host's +`/api/v2/network-access/capabilities` before assuming a feature exists. + +## Manifest + +Declare the capability with the typed descriptor. The host lists providers +from the descriptor without launching the plugin. + +```json +{ + "type": "network_access_provider.v1", + "id": "tailscale", + "display_name": "Tailscale", + "network_access_provider": { + "provider": "tailscale", + "display_name": "Tailscale" + } +} +``` + +`provider` is a stable lowercase, path-safe slug; it appears in admin API +paths such as `/api/v2/admin/network-access/{provider}/status`. Reserved +slugs today: `tailscale`, `netbird`. + +## Resident lifecycle + +Plugins declaring this capability are resident. The host: + +- starts them when the API listener binds (and on every proxy node), +- restarts them on exit or failed health with exponential backoff, +- stops them last during shutdown. + +Every host (the API server and each proxy node) runs its own instance of the +same installation. Each instance has its own overlay identity and its own +instance-state scope. Do not assume a single process. + +## gRPC service + +```proto +service NetworkAccessProvider { + rpc Connect(NetworkAccessConnectRequest) returns (NetworkAccessStatus); + rpc Disconnect(NetworkAccessDisconnectRequest) returns (NetworkAccessStatus); + rpc GetStatus(NetworkAccessGetStatusRequest) returns (NetworkAccessStatus); +} +``` + +Register it with `runtime.CapabilityServers{NetworkAccessProvider: ...}`. + +`NetworkAccessStatus.state` is one of `disconnected`, +`awaiting_authorization`, `connecting`, `connected`, `error`. The vocabulary +is open; the host tolerates values it does not know. `Connect` may return +`awaiting_authorization` or `connecting` and finish enrollment in the +background; push each later transition with +`RuntimeHost.ReportNetworkAccessStatus` so the host does not poll. The host +still calls `GetStatus` on demand. + +`desired_connected` is plugin-owned intent. Persist it in instance state (see +below), report it in every status, and reconnect on start when it is true. +The host never writes it. + +## What the host tells you: `GetHostInfo` + +`RuntimeHost.GetHostInfo` carries everything the proxy needs: + +| Field | Meaning | +|---|---| +| `host_role` | `api` or `proxy`. | +| `host_name` | Node name on proxies, server name on the API host. | +| `node_id` | `stream_nodes.id` on proxies, `0` on the API host. | +| `ingress_token` | Per-process-start secret; see below. | +| `listeners` | Local listeners to expose: `name`, loopback `address`, `default_port`. | + +`listeners` always contains `api`. On the API host it also carries `jellyfin` +and `abs` when those listeners are enabled. Proxies report only `api`. +`default_port` is the port the plugin should expose that listener on (443 for +`api` under HTTPS, 8096 for `jellyfin`, 13378 for `abs`); zero means use the +provider default. Expose every listener the host reports and return one +`NetworkAccessListener` per exposed listener in the status; `origin` is the +`api` listener's origin. + +The typed client is `runtimehost.Client.GetHostInfo`, which returns +`runtimehost.HostInfo` with `Listener(name)` for lookup. + +## Proxy contract + +For every request the plugin forwards to a host listener: + +- Preserve the incoming `Host` header. +- Overwrite `X-Forwarded-Proto` with `https`. +- Set `X-Forwarded-For` to the overlay peer address. +- Set `X-Silo-Ingress-Token` to the value from `GetHostInfo`, replacing any + client-supplied value; the host answers 403 when the header carries more + than one value. + +The host validates the token, strips the header, and records the request's +access path so stream URLs point tailnet clients at overlay origins. The +plugin's loopback source is in the host's default trusted-proxy list, so the +forwarded headers are honoured. + +The ingress token rotates on every host process start and is compared in +constant time. Keep it in memory only; never log or persist it. After a host +or plugin restart, call `GetHostInfo` again before proxying; a stale token is +rejected with `403`. + +## Instance state + +Per-instance state (tsnet node keys, profile state, `desired_connected`) +lives in the host, encrypted, scoped to installation plus host. The plugin +never sees the scope and never writes files. + +```proto +rpc ReadInstanceState(ReadInstanceStateRequest) returns (ReadInstanceStateResponse); +rpc WriteInstanceState(WriteInstanceStateRequest) returns (WriteInstanceStateResponse); +``` + +Limits: key up to 256 bytes, value up to 256 KiB, up to 256 keys per scope. +The SDK client checks the key and value limits before the round trip. + +`runtimehost.InstanceStateStore` exposes the RPCs in the two-method shape of +tailscale's `ipn.StateStore`: + +```go +type StateStore interface { + ReadState(key string) ([]byte, error) // runtimehost.ErrStateNotExist when absent + WriteState(key string, value []byte) error +} +``` + +The SDK does not import tailscale, and `ipn.StateStore` takes `ipn.StateKey` +rather than `string`, so wrap it in the plugin: + +```go +type tsStore struct{ inner *runtimehost.InstanceStateStore } + +func (s tsStore) ReadState(k ipn.StateKey) ([]byte, error) { + b, err := s.inner.ReadState(string(k)) + if errors.Is(err, runtimehost.ErrStateNotExist) { + return nil, ipn.ErrStateNotExist + } + return b, err +} + +func (s tsStore) WriteState(k ipn.StateKey, v []byte) error { + return s.inner.WriteState(string(k), v) +} + +srv := &tsnet.Server{Store: tsStore{inner: host.InstanceStateStore()}} +``` + +Each `ReadState`/`WriteState` call uses a 30 s deadline by default; +`WithTimeout` changes it. + +## Enrollment + +Support both an auth key in plugin config (for hands-off enrollment across +many nodes) and an interactive auth URL as the fallback. While enrollment is +pending, report `state: awaiting_authorization` with `auth_url` set. The +`auth_url` is admin-only: never log it, and clear it once enrollment +completes. + +## Logging + +Log state transitions and errors. Never log `auth_url`, `ingress_token`, +auth keys, or instance state values. + +## Example + +[`examples/hello-network-access`](../examples/hello-network-access) is a stub +provider with no overlay network. It shows the manifest, registration, +instance-state persistence of `desired_connected`, and the status push, and +serves as a test fixture for the host. diff --git a/docs/runtime-host.md b/docs/runtime-host.md index ef040a9..b8f1389 100644 --- a/docs/runtime-host.md +++ b/docs/runtime-host.md @@ -11,7 +11,7 @@ plugin, the plugin calls back into the host. | `PublishEvent(name, payload)` | Publish onto the host's event bus. The host stamps `plugin..` in front of `name` server-side. | | `PublishEventTo(target_plugin_id, name, payload)` | Publish an event addressed to one installed plugin by stable `plugin_id`. | | `PublishEventToInstallation(target_installation_id, name, payload)` | Publish an event addressed to one specific plugin installation. | -| `GetHostInfo()` | Return public-safe host URLs for callbacks and plugin-served links. | +| `GetHostInfo()` | Return public-safe host URLs for callbacks and plugin-served links, plus host role, listeners, and the ingress token for network access providers. | | `ListLibraries(user_id)` | Return libraries (optionally scoped to a user). | | `CheckMediaPresence(provider, media_type, ids)` | Batched lookup: which external IDs already exist in the host catalog. v1 supports provider="tmdb" only. | | `ListInstalledPlugins()` | Return installed plugins and their advertised capabilities. | @@ -21,6 +21,9 @@ plugin, the plugin calls back into the host. | `ResolveCatalogImageURLs(paths, variant)` | Resolve stored catalog image paths into host-generated browser URL targets. | | `MintScopedStream(request)` | Mint a short-lived stream grant for plugin-owned public access workflows. | | `CallPluginHTTP(request)` | Invoke another installed plugin's HTTP route through the host control plane. | +| `ReadInstanceState(key)` | Read one key of the calling instance's encrypted, host-scoped state. | +| `WriteInstanceState(key, value)` | Write one key of that state. Key ≤ 256 bytes, value ≤ 256 KiB, ≤ 256 keys per scope. | +| `ReportNetworkAccessStatus(status)` | Push a `network_access_provider.v1` status change so the host does not poll. | ## Using it from a plugin diff --git a/examples/hello-network-access/.gitignore b/examples/hello-network-access/.gitignore new file mode 100644 index 0000000..79d68cb --- /dev/null +++ b/examples/hello-network-access/.gitignore @@ -0,0 +1 @@ +hello-network-access diff --git a/examples/hello-network-access/README.md b/examples/hello-network-access/README.md new file mode 100644 index 0000000..653b743 --- /dev/null +++ b/examples/hello-network-access/README.md @@ -0,0 +1,27 @@ +# Hello Network Access + +A stub `network_access_provider.v1` plugin with no overlay network. It shows +the contract shape and doubles as a host-side test fixture: + +- `Connect` marks the instance connected, builds a fake origin per listener + reported by `RuntimeHost.GetHostInfo`, persists `desired_connected` in + host-provided instance state, and pushes the status with + `ReportNetworkAccessStatus`. +- `Disconnect` reverses it. +- `GetStatus` restores `desired_connected` from instance state on first call + so intent survives a plugin restart. + +There is no tsnet dependency. A real provider is described in +[docs/network-access-provider.md](../../docs/network-access-provider.md). + +## Build + +```sh +go build -o hello-network-access ./examples/hello-network-access +``` + +## Inspect the manifest + +```sh +./hello-network-access manifest +``` diff --git a/examples/hello-network-access/main.go b/examples/hello-network-access/main.go new file mode 100644 index 0000000..25f897c --- /dev/null +++ b/examples/hello-network-access/main.go @@ -0,0 +1,166 @@ +// Command hello-network-access is a stub network_access_provider.v1 plugin. +// +// It has no overlay network: Connect flips an in-memory state to "connected", +// reports a fake origin built from the host's api listener, persists +// desired_connected in host-provided instance state, and Disconnect reverses +// it. It exists so silo-server tests can exercise the resident plugin +// supervisor, the instance state RPCs, and the status push without a tsnet +// dependency. A real provider follows docs/network-access-provider.md. +package main + +import ( + "context" + _ "embed" + "fmt" + "os" + "sync" + + "github.com/hashicorp/go-hclog" + + pluginv1 "github.com/Silo-Server/silo-plugin-sdk/pkg/pluginproto/silo/plugin/v1" + sdkruntime "github.com/Silo-Server/silo-plugin-sdk/pkg/pluginsdk/runtime" + "github.com/Silo-Server/silo-plugin-sdk/pkg/pluginsdk/runtimehost" +) + +//go:embed manifest.json +var manifestJSON []byte + +const ( + version = "0.1.0" + // desiredKey is the instance state key holding plugin-owned intent. + desiredKey = "desired_connected" +) + +type provider struct { + pluginv1.UnimplementedNetworkAccessProviderServer + + logger hclog.Logger + + mu sync.Mutex + connected bool + desired bool + restored bool + hostInfo *runtimehost.HostInfo +} + +func (p *provider) Connect(ctx context.Context, _ *pluginv1.NetworkAccessConnectRequest) (*pluginv1.NetworkAccessStatus, error) { + p.mu.Lock() + defer p.mu.Unlock() + p.restoreLocked(ctx) + host := sdkruntime.Host() + if host != nil { + info, err := host.GetHostInfo(ctx) + if err != nil { + return nil, fmt.Errorf("get host info: %w", err) + } + p.hostInfo = info + } + p.connected = true + p.desired = true + p.persistLocked(ctx, host) + status := p.statusLocked() + p.reportLocked(ctx, host, status) + return status, nil +} + +func (p *provider) Disconnect(ctx context.Context, _ *pluginv1.NetworkAccessDisconnectRequest) (*pluginv1.NetworkAccessStatus, error) { + p.mu.Lock() + defer p.mu.Unlock() + p.restoreLocked(ctx) + host := sdkruntime.Host() + p.connected = false + p.desired = false + p.persistLocked(ctx, host) + status := p.statusLocked() + p.reportLocked(ctx, host, status) + return status, nil +} + +func (p *provider) GetStatus(ctx context.Context, _ *pluginv1.NetworkAccessGetStatusRequest) (*pluginv1.NetworkAccessStatus, error) { + p.mu.Lock() + defer p.mu.Unlock() + p.restoreLocked(ctx) + return p.statusLocked(), nil +} + +// restoreLocked loads desired_connected from instance state once per process +// and, when it was true, resumes the "connection" so the intent survives +// plugin restarts. A real provider would bring the overlay up here. +func (p *provider) restoreLocked(ctx context.Context) { + if p.restored { + return + } + host := sdkruntime.Host() + if host == nil { + return + } + p.restored = true + value, found, err := host.ReadInstanceState(ctx, desiredKey) + if err != nil { + p.logger.Warn("read instance state", "err", err) + return + } + if found && string(value) == "1" { + p.desired = true + p.connected = true + if info, err := host.GetHostInfo(ctx); err == nil { + p.hostInfo = info + } + } +} + +func (p *provider) persistLocked(ctx context.Context, host *runtimehost.Client) { + if host == nil { + return + } + value := []byte("0") + if p.desired { + value = []byte("1") + } + if err := host.WriteInstanceState(ctx, desiredKey, value); err != nil { + p.logger.Warn("write instance state", "err", err) + } +} + +func (p *provider) reportLocked(ctx context.Context, host *runtimehost.Client, status *pluginv1.NetworkAccessStatus) { + if host == nil { + return + } + if err := host.ReportNetworkAccessStatus(ctx, status); err != nil { + p.logger.Warn("report network access status", "err", err) + } +} + +func (p *provider) statusLocked() *pluginv1.NetworkAccessStatus { + status := &pluginv1.NetworkAccessStatus{ + State: runtimehost.NetworkAccessStateDisconnected, + ProviderVersion: "stub " + version, + DesiredConnected: p.desired, + } + if !p.connected { + return status + } + status.State = runtimehost.NetworkAccessStateConnected + status.Hostname = "stub.invalid" + status.Addresses = []string{"100.64.0.1"} + if p.hostInfo != nil { + for _, l := range p.hostInfo.Listeners { + origin := fmt.Sprintf("https://%s:%d", status.Hostname, l.DefaultPort) + status.Listeners = append(status.Listeners, &pluginv1.NetworkAccessListener{Name: l.Name, Origin: origin}) + if l.Name == runtimehost.ListenerAPI { + status.Origin = origin + } + } + } + if status.Origin == "" { + status.Origin = "https://" + status.Hostname + } + return status +} + +func main() { + logger := hclog.New(&hclog.LoggerOptions{Name: "hello-network-access", Output: os.Stderr}) + sdkruntime.ServeManifest(manifestJSON, version, sdkruntime.CapabilityServers{ + NetworkAccessProvider: &provider{logger: logger}, + }) +} diff --git a/examples/hello-network-access/manifest.json b/examples/hello-network-access/manifest.json new file mode 100644 index 0000000..0cf0301 --- /dev/null +++ b/examples/hello-network-access/manifest.json @@ -0,0 +1,18 @@ +{ + "plugin_id": "example.hello-network-access", + "version": "0.1.0", + "checksum": "__CHECKSUM__", + "silo_api_version": "v1", + "capabilities": [ + { + "type": "network_access_provider.v1", + "id": "stub", + "display_name": "Stub Overlay", + "description": "A stub network access provider with no real overlay network. Useful as a host test fixture.", + "network_access_provider": { + "provider": "stub", + "display_name": "Stub Overlay" + } + } + ] +} diff --git a/pkg/pluginproto/silo/plugin/v1/common.pb.go b/pkg/pluginproto/silo/plugin/v1/common.pb.go index 68289e8..267874d 100644 --- a/pkg/pluginproto/silo/plugin/v1/common.pb.go +++ b/pkg/pluginproto/silo/plugin/v1/common.pb.go @@ -977,8 +977,12 @@ type CapabilityDescriptor struct { // of type "watch_sync_provider.v1". Keeping this typed lets the host build // connection and authorization UI without launching the plugin. WatchSyncProvider *WatchSyncProviderDescriptor `protobuf:"bytes,10,opt,name=watch_sync_provider,json=watchSyncProvider,proto3" json:"watch_sync_provider,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // Typed network-access contract metadata. Only meaningful for capabilities + // of type "network_access_provider.v1". The host lists available overlay + // providers from this descriptor without launching the plugin. + NetworkAccessProvider *NetworkAccessProviderDescriptor `protobuf:"bytes,11,opt,name=network_access_provider,json=networkAccessProvider,proto3" json:"network_access_provider,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *CapabilityDescriptor) Reset() { @@ -1081,6 +1085,13 @@ func (x *CapabilityDescriptor) GetWatchSyncProvider() *WatchSyncProviderDescript return nil } +func (x *CapabilityDescriptor) GetNetworkAccessProvider() *NetworkAccessProviderDescriptor { + if x != nil { + return x.NetworkAccessProvider + } + return nil +} + // PluginPresentation describes a plugin for server operators and links them to // its publisher, source, support, and release history. These values are // self-declared by the plugin and never determine catalog approval. @@ -1599,7 +1610,7 @@ var File_silo_plugin_v1_common_proto protoreflect.FileDescriptor const file_silo_plugin_v1_common_proto_rawDesc = "" + "\n" + - "\x1bsilo/plugin/v1/common.proto\x12\x0esilo.plugin.v1\x1a\x1cgoogle/protobuf/struct.proto\x1a(silo/plugin/v1/watch_sync_provider.proto\"7\n" + + "\x1bsilo/plugin/v1/common.proto\x12\x0esilo.plugin.v1\x1a\x1cgoogle/protobuf/struct.proto\x1a,silo/plugin/v1/network_access_provider.proto\x1a(silo/plugin/v1/watch_sync_provider.proto\"7\n" + "\x11SupportedPlatform\x12\x0e\n" + "\x02os\x18\x01 \x01(\tR\x02os\x12\x12\n" + "\x04arch\x18\x02 \x01(\tR\x04arch\"\xd9\x01\n" + @@ -1676,7 +1687,7 @@ const file_silo_plugin_v1_common_proto_rawDesc = "" + "\rPackagedAsset\x12\x12\n" + "\x04path\x18\x01 \x01(\tR\x04path\x12!\n" + "\fcontent_type\x18\x02 \x01(\tR\vcontentType\x12\x1c\n" + - "\tintegrity\x18\x03 \x01(\tR\tintegrity\"\xb4\x03\n" + + "\tintegrity\x18\x03 \x01(\tR\tintegrity\"\x9d\x04\n" + "\x14CapabilityDescriptor\x12\x12\n" + "\x04type\x18\x01 \x01(\tR\x04type\x12\x0e\n" + "\x02id\x18\x02 \x01(\tR\x02id\x12!\n" + @@ -1689,7 +1700,8 @@ const file_silo_plugin_v1_common_proto_rawDesc = "" + "auth_modes\x18\b \x03(\tR\tauthModes\x12\x19\n" + "\bicon_url\x18\t \x01(\tR\aiconUrl\x12[\n" + "\x13watch_sync_provider\x18\n" + - " \x01(\v2+.silo.plugin.v1.WatchSyncProviderDescriptorR\x11watchSyncProvider\"\xa2\x03\n" + + " \x01(\v2+.silo.plugin.v1.WatchSyncProviderDescriptorR\x11watchSyncProvider\x12g\n" + + "\x17network_access_provider\x18\v \x01(\v2/.silo.plugin.v1.NetworkAccessProviderDescriptorR\x15networkAccessProvider\"\xa2\x03\n" + "\x12PluginPresentation\x12!\n" + "\fdisplay_name\x18\x01 \x01(\tR\vdisplayName\x12\x18\n" + "\asummary\x18\x02 \x01(\tR\asummary\x121\n" + @@ -1759,30 +1771,31 @@ func file_silo_plugin_v1_common_proto_rawDescGZIP() []byte { var file_silo_plugin_v1_common_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_silo_plugin_v1_common_proto_msgTypes = make([]protoimpl.MessageInfo, 20) var file_silo_plugin_v1_common_proto_goTypes = []any{ - (AdminFormControl)(0), // 0: silo.plugin.v1.AdminFormControl - (*SupportedPlatform)(nil), // 1: silo.plugin.v1.SupportedPlatform - (*ConfigSchema)(nil), // 2: silo.plugin.v1.ConfigSchema - (*AdminFormOption)(nil), // 3: silo.plugin.v1.AdminFormOption - (*AdminFormField)(nil), // 4: silo.plugin.v1.AdminFormField - (*AdminFormCondition)(nil), // 5: silo.plugin.v1.AdminFormCondition - (*AdminFormValidation)(nil), // 6: silo.plugin.v1.AdminFormValidation - (*AdminFormSection)(nil), // 7: silo.plugin.v1.AdminFormSection - (*AdminFormDescriptor)(nil), // 8: silo.plugin.v1.AdminFormDescriptor - (*ConfigEntry)(nil), // 9: silo.plugin.v1.ConfigEntry - (*HttpRouteDescriptor)(nil), // 10: silo.plugin.v1.HttpRouteDescriptor - (*PackagedAsset)(nil), // 11: silo.plugin.v1.PackagedAsset - (*CapabilityDescriptor)(nil), // 12: silo.plugin.v1.CapabilityDescriptor - (*PluginPresentation)(nil), // 13: silo.plugin.v1.PluginPresentation - (*PluginManifest)(nil), // 14: silo.plugin.v1.PluginManifest - (*GetManifestRequest)(nil), // 15: silo.plugin.v1.GetManifestRequest - (*GetManifestResponse)(nil), // 16: silo.plugin.v1.GetManifestResponse - (*ConfigureRequest)(nil), // 17: silo.plugin.v1.ConfigureRequest - (*ConfigureResponse)(nil), // 18: silo.plugin.v1.ConfigureResponse - (*BindHostBrokerRequest)(nil), // 19: silo.plugin.v1.BindHostBrokerRequest - (*BindHostBrokerResponse)(nil), // 20: silo.plugin.v1.BindHostBrokerResponse - (*structpb.Value)(nil), // 21: google.protobuf.Value - (*structpb.Struct)(nil), // 22: google.protobuf.Struct - (*WatchSyncProviderDescriptor)(nil), // 23: silo.plugin.v1.WatchSyncProviderDescriptor + (AdminFormControl)(0), // 0: silo.plugin.v1.AdminFormControl + (*SupportedPlatform)(nil), // 1: silo.plugin.v1.SupportedPlatform + (*ConfigSchema)(nil), // 2: silo.plugin.v1.ConfigSchema + (*AdminFormOption)(nil), // 3: silo.plugin.v1.AdminFormOption + (*AdminFormField)(nil), // 4: silo.plugin.v1.AdminFormField + (*AdminFormCondition)(nil), // 5: silo.plugin.v1.AdminFormCondition + (*AdminFormValidation)(nil), // 6: silo.plugin.v1.AdminFormValidation + (*AdminFormSection)(nil), // 7: silo.plugin.v1.AdminFormSection + (*AdminFormDescriptor)(nil), // 8: silo.plugin.v1.AdminFormDescriptor + (*ConfigEntry)(nil), // 9: silo.plugin.v1.ConfigEntry + (*HttpRouteDescriptor)(nil), // 10: silo.plugin.v1.HttpRouteDescriptor + (*PackagedAsset)(nil), // 11: silo.plugin.v1.PackagedAsset + (*CapabilityDescriptor)(nil), // 12: silo.plugin.v1.CapabilityDescriptor + (*PluginPresentation)(nil), // 13: silo.plugin.v1.PluginPresentation + (*PluginManifest)(nil), // 14: silo.plugin.v1.PluginManifest + (*GetManifestRequest)(nil), // 15: silo.plugin.v1.GetManifestRequest + (*GetManifestResponse)(nil), // 16: silo.plugin.v1.GetManifestResponse + (*ConfigureRequest)(nil), // 17: silo.plugin.v1.ConfigureRequest + (*ConfigureResponse)(nil), // 18: silo.plugin.v1.ConfigureResponse + (*BindHostBrokerRequest)(nil), // 19: silo.plugin.v1.BindHostBrokerRequest + (*BindHostBrokerResponse)(nil), // 20: silo.plugin.v1.BindHostBrokerResponse + (*structpb.Value)(nil), // 21: google.protobuf.Value + (*structpb.Struct)(nil), // 22: google.protobuf.Struct + (*WatchSyncProviderDescriptor)(nil), // 23: silo.plugin.v1.WatchSyncProviderDescriptor + (*NetworkAccessProviderDescriptor)(nil), // 24: silo.plugin.v1.NetworkAccessProviderDescriptor } var file_silo_plugin_v1_common_proto_depIdxs = []int32{ 8, // 0: silo.plugin.v1.ConfigSchema.admin_form:type_name -> silo.plugin.v1.AdminFormDescriptor @@ -1798,27 +1811,28 @@ var file_silo_plugin_v1_common_proto_depIdxs = []int32{ 2, // 10: silo.plugin.v1.CapabilityDescriptor.config_schema:type_name -> silo.plugin.v1.ConfigSchema 22, // 11: silo.plugin.v1.CapabilityDescriptor.metadata:type_name -> google.protobuf.Struct 23, // 12: silo.plugin.v1.CapabilityDescriptor.watch_sync_provider:type_name -> silo.plugin.v1.WatchSyncProviderDescriptor - 1, // 13: silo.plugin.v1.PluginManifest.supported_platforms:type_name -> silo.plugin.v1.SupportedPlatform - 12, // 14: silo.plugin.v1.PluginManifest.capabilities:type_name -> silo.plugin.v1.CapabilityDescriptor - 2, // 15: silo.plugin.v1.PluginManifest.global_config_schema:type_name -> silo.plugin.v1.ConfigSchema - 2, // 16: silo.plugin.v1.PluginManifest.user_config_schema:type_name -> silo.plugin.v1.ConfigSchema - 10, // 17: silo.plugin.v1.PluginManifest.http_routes:type_name -> silo.plugin.v1.HttpRouteDescriptor - 11, // 18: silo.plugin.v1.PluginManifest.assets:type_name -> silo.plugin.v1.PackagedAsset - 22, // 19: silo.plugin.v1.PluginManifest.metadata:type_name -> google.protobuf.Struct - 13, // 20: silo.plugin.v1.PluginManifest.presentation:type_name -> silo.plugin.v1.PluginPresentation - 14, // 21: silo.plugin.v1.GetManifestResponse.manifest:type_name -> silo.plugin.v1.PluginManifest - 9, // 22: silo.plugin.v1.ConfigureRequest.config:type_name -> silo.plugin.v1.ConfigEntry - 15, // 23: silo.plugin.v1.Runtime.GetManifest:input_type -> silo.plugin.v1.GetManifestRequest - 17, // 24: silo.plugin.v1.Runtime.Configure:input_type -> silo.plugin.v1.ConfigureRequest - 19, // 25: silo.plugin.v1.Runtime.BindHostBroker:input_type -> silo.plugin.v1.BindHostBrokerRequest - 16, // 26: silo.plugin.v1.Runtime.GetManifest:output_type -> silo.plugin.v1.GetManifestResponse - 18, // 27: silo.plugin.v1.Runtime.Configure:output_type -> silo.plugin.v1.ConfigureResponse - 20, // 28: silo.plugin.v1.Runtime.BindHostBroker:output_type -> silo.plugin.v1.BindHostBrokerResponse - 26, // [26:29] is the sub-list for method output_type - 23, // [23:26] is the sub-list for method input_type - 23, // [23:23] is the sub-list for extension type_name - 23, // [23:23] is the sub-list for extension extendee - 0, // [0:23] is the sub-list for field type_name + 24, // 13: silo.plugin.v1.CapabilityDescriptor.network_access_provider:type_name -> silo.plugin.v1.NetworkAccessProviderDescriptor + 1, // 14: silo.plugin.v1.PluginManifest.supported_platforms:type_name -> silo.plugin.v1.SupportedPlatform + 12, // 15: silo.plugin.v1.PluginManifest.capabilities:type_name -> silo.plugin.v1.CapabilityDescriptor + 2, // 16: silo.plugin.v1.PluginManifest.global_config_schema:type_name -> silo.plugin.v1.ConfigSchema + 2, // 17: silo.plugin.v1.PluginManifest.user_config_schema:type_name -> silo.plugin.v1.ConfigSchema + 10, // 18: silo.plugin.v1.PluginManifest.http_routes:type_name -> silo.plugin.v1.HttpRouteDescriptor + 11, // 19: silo.plugin.v1.PluginManifest.assets:type_name -> silo.plugin.v1.PackagedAsset + 22, // 20: silo.plugin.v1.PluginManifest.metadata:type_name -> google.protobuf.Struct + 13, // 21: silo.plugin.v1.PluginManifest.presentation:type_name -> silo.plugin.v1.PluginPresentation + 14, // 22: silo.plugin.v1.GetManifestResponse.manifest:type_name -> silo.plugin.v1.PluginManifest + 9, // 23: silo.plugin.v1.ConfigureRequest.config:type_name -> silo.plugin.v1.ConfigEntry + 15, // 24: silo.plugin.v1.Runtime.GetManifest:input_type -> silo.plugin.v1.GetManifestRequest + 17, // 25: silo.plugin.v1.Runtime.Configure:input_type -> silo.plugin.v1.ConfigureRequest + 19, // 26: silo.plugin.v1.Runtime.BindHostBroker:input_type -> silo.plugin.v1.BindHostBrokerRequest + 16, // 27: silo.plugin.v1.Runtime.GetManifest:output_type -> silo.plugin.v1.GetManifestResponse + 18, // 28: silo.plugin.v1.Runtime.Configure:output_type -> silo.plugin.v1.ConfigureResponse + 20, // 29: silo.plugin.v1.Runtime.BindHostBroker:output_type -> silo.plugin.v1.BindHostBrokerResponse + 27, // [27:30] is the sub-list for method output_type + 24, // [24:27] is the sub-list for method input_type + 24, // [24:24] is the sub-list for extension type_name + 24, // [24:24] is the sub-list for extension extendee + 0, // [0:24] is the sub-list for field type_name } func init() { file_silo_plugin_v1_common_proto_init() } @@ -1826,6 +1840,7 @@ func file_silo_plugin_v1_common_proto_init() { if File_silo_plugin_v1_common_proto != nil { return } + file_silo_plugin_v1_network_access_provider_proto_init() file_silo_plugin_v1_watch_sync_provider_proto_init() type x struct{} out := protoimpl.TypeBuilder{ diff --git a/pkg/pluginproto/silo/plugin/v1/network_access_provider.pb.go b/pkg/pluginproto/silo/plugin/v1/network_access_provider.pb.go new file mode 100644 index 0000000..b3e6455 --- /dev/null +++ b/pkg/pluginproto/silo/plugin/v1/network_access_provider.pb.go @@ -0,0 +1,454 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc (unknown) +// source: silo/plugin/v1/network_access_provider.proto + +package pluginv1 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type NetworkAccessConnectRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *NetworkAccessConnectRequest) Reset() { + *x = NetworkAccessConnectRequest{} + mi := &file_silo_plugin_v1_network_access_provider_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *NetworkAccessConnectRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NetworkAccessConnectRequest) ProtoMessage() {} + +func (x *NetworkAccessConnectRequest) ProtoReflect() protoreflect.Message { + mi := &file_silo_plugin_v1_network_access_provider_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NetworkAccessConnectRequest.ProtoReflect.Descriptor instead. +func (*NetworkAccessConnectRequest) Descriptor() ([]byte, []int) { + return file_silo_plugin_v1_network_access_provider_proto_rawDescGZIP(), []int{0} +} + +type NetworkAccessDisconnectRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *NetworkAccessDisconnectRequest) Reset() { + *x = NetworkAccessDisconnectRequest{} + mi := &file_silo_plugin_v1_network_access_provider_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *NetworkAccessDisconnectRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NetworkAccessDisconnectRequest) ProtoMessage() {} + +func (x *NetworkAccessDisconnectRequest) ProtoReflect() protoreflect.Message { + mi := &file_silo_plugin_v1_network_access_provider_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NetworkAccessDisconnectRequest.ProtoReflect.Descriptor instead. +func (*NetworkAccessDisconnectRequest) Descriptor() ([]byte, []int) { + return file_silo_plugin_v1_network_access_provider_proto_rawDescGZIP(), []int{1} +} + +type NetworkAccessGetStatusRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *NetworkAccessGetStatusRequest) Reset() { + *x = NetworkAccessGetStatusRequest{} + mi := &file_silo_plugin_v1_network_access_provider_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *NetworkAccessGetStatusRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NetworkAccessGetStatusRequest) ProtoMessage() {} + +func (x *NetworkAccessGetStatusRequest) ProtoReflect() protoreflect.Message { + mi := &file_silo_plugin_v1_network_access_provider_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NetworkAccessGetStatusRequest.ProtoReflect.Descriptor instead. +func (*NetworkAccessGetStatusRequest) Descriptor() ([]byte, []int) { + return file_silo_plugin_v1_network_access_provider_proto_rawDescGZIP(), []int{2} +} + +// NetworkAccessStatus is the provider's view of one plugin instance. Every +// host (API server, each proxy node) runs its own instance and reports its own +// status; the host aggregates them. +type NetworkAccessStatus struct { + state protoimpl.MessageState `protogen:"open.v1"` + // One of: disconnected | awaiting_authorization | connecting | connected | error. + State string `protobuf:"bytes,1,opt,name=state,proto3" json:"state,omitempty"` + // Overlay DNS name, e.g. silo.tail1234.ts.net. + Hostname string `protobuf:"bytes,2,opt,name=hostname,proto3" json:"hostname,omitempty"` + // scheme://host[:port] clients should use for the api listener. + Origin string `protobuf:"bytes,3,opt,name=origin,proto3" json:"origin,omitempty"` + // Overlay IP addresses assigned to this instance. + Addresses []string `protobuf:"bytes,4,rep,name=addresses,proto3" json:"addresses,omitempty"` + // One entry per exposed host listener (see RuntimeHost.GetHostInfo). + Listeners []*NetworkAccessListener `protobuf:"bytes,5,rep,name=listeners,proto3" json:"listeners,omitempty"` + // Interactive enrollment URL. Only set while state is awaiting_authorization. + // Admin-only; the plugin must never log it. + AuthUrl string `protobuf:"bytes,6,opt,name=auth_url,json=authUrl,proto3" json:"auth_url,omitempty"` + // Human-readable failure detail when state is error. + Error string `protobuf:"bytes,7,opt,name=error,proto3" json:"error,omitempty"` + // Provider client version shown to admins, e.g. "tsnet 1.102.4". + ProviderVersion string `protobuf:"bytes,8,opt,name=provider_version,json=providerVersion,proto3" json:"provider_version,omitempty"` + // Plugin-owned intent that survives restarts. The plugin persists it in + // instance state and reconnects on start when true. + DesiredConnected bool `protobuf:"varint,9,opt,name=desired_connected,json=desiredConnected,proto3" json:"desired_connected,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *NetworkAccessStatus) Reset() { + *x = NetworkAccessStatus{} + mi := &file_silo_plugin_v1_network_access_provider_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *NetworkAccessStatus) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NetworkAccessStatus) ProtoMessage() {} + +func (x *NetworkAccessStatus) ProtoReflect() protoreflect.Message { + mi := &file_silo_plugin_v1_network_access_provider_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NetworkAccessStatus.ProtoReflect.Descriptor instead. +func (*NetworkAccessStatus) Descriptor() ([]byte, []int) { + return file_silo_plugin_v1_network_access_provider_proto_rawDescGZIP(), []int{3} +} + +func (x *NetworkAccessStatus) GetState() string { + if x != nil { + return x.State + } + return "" +} + +func (x *NetworkAccessStatus) GetHostname() string { + if x != nil { + return x.Hostname + } + return "" +} + +func (x *NetworkAccessStatus) GetOrigin() string { + if x != nil { + return x.Origin + } + return "" +} + +func (x *NetworkAccessStatus) GetAddresses() []string { + if x != nil { + return x.Addresses + } + return nil +} + +func (x *NetworkAccessStatus) GetListeners() []*NetworkAccessListener { + if x != nil { + return x.Listeners + } + return nil +} + +func (x *NetworkAccessStatus) GetAuthUrl() string { + if x != nil { + return x.AuthUrl + } + return "" +} + +func (x *NetworkAccessStatus) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +func (x *NetworkAccessStatus) GetProviderVersion() string { + if x != nil { + return x.ProviderVersion + } + return "" +} + +func (x *NetworkAccessStatus) GetDesiredConnected() bool { + if x != nil { + return x.DesiredConnected + } + return false +} + +// NetworkAccessListener maps one host listener onto its overlay origin. +type NetworkAccessListener struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Listener name from RuntimeHost.GetHostInfo: api | jellyfin | abs. + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // Overlay origin serving that listener, scheme://host[:port]. + Origin string `protobuf:"bytes,2,opt,name=origin,proto3" json:"origin,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *NetworkAccessListener) Reset() { + *x = NetworkAccessListener{} + mi := &file_silo_plugin_v1_network_access_provider_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *NetworkAccessListener) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NetworkAccessListener) ProtoMessage() {} + +func (x *NetworkAccessListener) ProtoReflect() protoreflect.Message { + mi := &file_silo_plugin_v1_network_access_provider_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NetworkAccessListener.ProtoReflect.Descriptor instead. +func (*NetworkAccessListener) Descriptor() ([]byte, []int) { + return file_silo_plugin_v1_network_access_provider_proto_rawDescGZIP(), []int{4} +} + +func (x *NetworkAccessListener) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *NetworkAccessListener) GetOrigin() string { + if x != nil { + return x.Origin + } + return "" +} + +// NetworkAccessProviderDescriptor is the typed manifest metadata for a +// network_access_provider.v1 capability. The host lists providers from it +// without launching the plugin. +type NetworkAccessProviderDescriptor struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Stable provider slug, e.g. "tailscale", "netbird". Lowercase, path-safe. + Provider string `protobuf:"bytes,1,opt,name=provider,proto3" json:"provider,omitempty"` + // Operator-facing name, e.g. "Tailscale". + DisplayName string `protobuf:"bytes,2,opt,name=display_name,json=displayName,proto3" json:"display_name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *NetworkAccessProviderDescriptor) Reset() { + *x = NetworkAccessProviderDescriptor{} + mi := &file_silo_plugin_v1_network_access_provider_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *NetworkAccessProviderDescriptor) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NetworkAccessProviderDescriptor) ProtoMessage() {} + +func (x *NetworkAccessProviderDescriptor) ProtoReflect() protoreflect.Message { + mi := &file_silo_plugin_v1_network_access_provider_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NetworkAccessProviderDescriptor.ProtoReflect.Descriptor instead. +func (*NetworkAccessProviderDescriptor) Descriptor() ([]byte, []int) { + return file_silo_plugin_v1_network_access_provider_proto_rawDescGZIP(), []int{5} +} + +func (x *NetworkAccessProviderDescriptor) GetProvider() string { + if x != nil { + return x.Provider + } + return "" +} + +func (x *NetworkAccessProviderDescriptor) GetDisplayName() string { + if x != nil { + return x.DisplayName + } + return "" +} + +var File_silo_plugin_v1_network_access_provider_proto protoreflect.FileDescriptor + +const file_silo_plugin_v1_network_access_provider_proto_rawDesc = "" + + "\n" + + ",silo/plugin/v1/network_access_provider.proto\x12\x0esilo.plugin.v1\"\x1d\n" + + "\x1bNetworkAccessConnectRequest\" \n" + + "\x1eNetworkAccessDisconnectRequest\"\x1f\n" + + "\x1dNetworkAccessGetStatusRequest\"\xcb\x02\n" + + "\x13NetworkAccessStatus\x12\x14\n" + + "\x05state\x18\x01 \x01(\tR\x05state\x12\x1a\n" + + "\bhostname\x18\x02 \x01(\tR\bhostname\x12\x16\n" + + "\x06origin\x18\x03 \x01(\tR\x06origin\x12\x1c\n" + + "\taddresses\x18\x04 \x03(\tR\taddresses\x12C\n" + + "\tlisteners\x18\x05 \x03(\v2%.silo.plugin.v1.NetworkAccessListenerR\tlisteners\x12\x19\n" + + "\bauth_url\x18\x06 \x01(\tR\aauthUrl\x12\x14\n" + + "\x05error\x18\a \x01(\tR\x05error\x12)\n" + + "\x10provider_version\x18\b \x01(\tR\x0fproviderVersion\x12+\n" + + "\x11desired_connected\x18\t \x01(\bR\x10desiredConnected\"C\n" + + "\x15NetworkAccessListener\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x16\n" + + "\x06origin\x18\x02 \x01(\tR\x06origin\"`\n" + + "\x1fNetworkAccessProviderDescriptor\x12\x1a\n" + + "\bprovider\x18\x01 \x01(\tR\bprovider\x12!\n" + + "\fdisplay_name\x18\x02 \x01(\tR\vdisplayName2\xb8\x02\n" + + "\x15NetworkAccessProvider\x12[\n" + + "\aConnect\x12+.silo.plugin.v1.NetworkAccessConnectRequest\x1a#.silo.plugin.v1.NetworkAccessStatus\x12a\n" + + "\n" + + "Disconnect\x12..silo.plugin.v1.NetworkAccessDisconnectRequest\x1a#.silo.plugin.v1.NetworkAccessStatus\x12_\n" + + "\tGetStatus\x12-.silo.plugin.v1.NetworkAccessGetStatusRequest\x1a#.silo.plugin.v1.NetworkAccessStatusBPZNgithub.com/Silo-Server/silo-plugin-sdk/pkg/pluginproto/silo/plugin/v1;pluginv1b\x06proto3" + +var ( + file_silo_plugin_v1_network_access_provider_proto_rawDescOnce sync.Once + file_silo_plugin_v1_network_access_provider_proto_rawDescData []byte +) + +func file_silo_plugin_v1_network_access_provider_proto_rawDescGZIP() []byte { + file_silo_plugin_v1_network_access_provider_proto_rawDescOnce.Do(func() { + file_silo_plugin_v1_network_access_provider_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_silo_plugin_v1_network_access_provider_proto_rawDesc), len(file_silo_plugin_v1_network_access_provider_proto_rawDesc))) + }) + return file_silo_plugin_v1_network_access_provider_proto_rawDescData +} + +var file_silo_plugin_v1_network_access_provider_proto_msgTypes = make([]protoimpl.MessageInfo, 6) +var file_silo_plugin_v1_network_access_provider_proto_goTypes = []any{ + (*NetworkAccessConnectRequest)(nil), // 0: silo.plugin.v1.NetworkAccessConnectRequest + (*NetworkAccessDisconnectRequest)(nil), // 1: silo.plugin.v1.NetworkAccessDisconnectRequest + (*NetworkAccessGetStatusRequest)(nil), // 2: silo.plugin.v1.NetworkAccessGetStatusRequest + (*NetworkAccessStatus)(nil), // 3: silo.plugin.v1.NetworkAccessStatus + (*NetworkAccessListener)(nil), // 4: silo.plugin.v1.NetworkAccessListener + (*NetworkAccessProviderDescriptor)(nil), // 5: silo.plugin.v1.NetworkAccessProviderDescriptor +} +var file_silo_plugin_v1_network_access_provider_proto_depIdxs = []int32{ + 4, // 0: silo.plugin.v1.NetworkAccessStatus.listeners:type_name -> silo.plugin.v1.NetworkAccessListener + 0, // 1: silo.plugin.v1.NetworkAccessProvider.Connect:input_type -> silo.plugin.v1.NetworkAccessConnectRequest + 1, // 2: silo.plugin.v1.NetworkAccessProvider.Disconnect:input_type -> silo.plugin.v1.NetworkAccessDisconnectRequest + 2, // 3: silo.plugin.v1.NetworkAccessProvider.GetStatus:input_type -> silo.plugin.v1.NetworkAccessGetStatusRequest + 3, // 4: silo.plugin.v1.NetworkAccessProvider.Connect:output_type -> silo.plugin.v1.NetworkAccessStatus + 3, // 5: silo.plugin.v1.NetworkAccessProvider.Disconnect:output_type -> silo.plugin.v1.NetworkAccessStatus + 3, // 6: silo.plugin.v1.NetworkAccessProvider.GetStatus:output_type -> silo.plugin.v1.NetworkAccessStatus + 4, // [4:7] is the sub-list for method output_type + 1, // [1:4] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_silo_plugin_v1_network_access_provider_proto_init() } +func file_silo_plugin_v1_network_access_provider_proto_init() { + if File_silo_plugin_v1_network_access_provider_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_silo_plugin_v1_network_access_provider_proto_rawDesc), len(file_silo_plugin_v1_network_access_provider_proto_rawDesc)), + NumEnums: 0, + NumMessages: 6, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_silo_plugin_v1_network_access_provider_proto_goTypes, + DependencyIndexes: file_silo_plugin_v1_network_access_provider_proto_depIdxs, + MessageInfos: file_silo_plugin_v1_network_access_provider_proto_msgTypes, + }.Build() + File_silo_plugin_v1_network_access_provider_proto = out.File + file_silo_plugin_v1_network_access_provider_proto_goTypes = nil + file_silo_plugin_v1_network_access_provider_proto_depIdxs = nil +} diff --git a/pkg/pluginproto/silo/plugin/v1/network_access_provider_grpc.pb.go b/pkg/pluginproto/silo/plugin/v1/network_access_provider_grpc.pb.go new file mode 100644 index 0000000..2a4bb8b --- /dev/null +++ b/pkg/pluginproto/silo/plugin/v1/network_access_provider_grpc.pb.go @@ -0,0 +1,219 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.6.1 +// - protoc (unknown) +// source: silo/plugin/v1/network_access_provider.proto + +package pluginv1 + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + NetworkAccessProvider_Connect_FullMethodName = "/silo.plugin.v1.NetworkAccessProvider/Connect" + NetworkAccessProvider_Disconnect_FullMethodName = "/silo.plugin.v1.NetworkAccessProvider/Disconnect" + NetworkAccessProvider_GetStatus_FullMethodName = "/silo.plugin.v1.NetworkAccessProvider/GetStatus" +) + +// NetworkAccessProviderClient is the client API for NetworkAccessProvider service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +// +// NetworkAccessProvider gives a Silo deployment an overlay-network identity +// (Tailscale, NetBird, ...). The plugin owns the overlay client and a reverse +// proxy in front of the host listeners it learns from RuntimeHost.GetHostInfo; +// the host owns supervision, per-instance state storage, status aggregation, +// and the admin API. Plugins declaring this capability are resident: the host +// starts them at boot, restarts them on crash, and stops them last. +type NetworkAccessProviderClient interface { + // Connect brings the overlay identity up and starts proxying. It returns the + // status reached so far; enrollment may continue in the background and be + // reported through RuntimeHost.ReportNetworkAccessStatus. + Connect(ctx context.Context, in *NetworkAccessConnectRequest, opts ...grpc.CallOption) (*NetworkAccessStatus, error) + // Disconnect tears the overlay listener down and clears desired_connected. + Disconnect(ctx context.Context, in *NetworkAccessDisconnectRequest, opts ...grpc.CallOption) (*NetworkAccessStatus, error) + // GetStatus returns the current status without changing it. + GetStatus(ctx context.Context, in *NetworkAccessGetStatusRequest, opts ...grpc.CallOption) (*NetworkAccessStatus, error) +} + +type networkAccessProviderClient struct { + cc grpc.ClientConnInterface +} + +func NewNetworkAccessProviderClient(cc grpc.ClientConnInterface) NetworkAccessProviderClient { + return &networkAccessProviderClient{cc} +} + +func (c *networkAccessProviderClient) Connect(ctx context.Context, in *NetworkAccessConnectRequest, opts ...grpc.CallOption) (*NetworkAccessStatus, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(NetworkAccessStatus) + err := c.cc.Invoke(ctx, NetworkAccessProvider_Connect_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *networkAccessProviderClient) Disconnect(ctx context.Context, in *NetworkAccessDisconnectRequest, opts ...grpc.CallOption) (*NetworkAccessStatus, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(NetworkAccessStatus) + err := c.cc.Invoke(ctx, NetworkAccessProvider_Disconnect_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *networkAccessProviderClient) GetStatus(ctx context.Context, in *NetworkAccessGetStatusRequest, opts ...grpc.CallOption) (*NetworkAccessStatus, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(NetworkAccessStatus) + err := c.cc.Invoke(ctx, NetworkAccessProvider_GetStatus_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// NetworkAccessProviderServer is the server API for NetworkAccessProvider service. +// All implementations should embed UnimplementedNetworkAccessProviderServer +// for forward compatibility. +// +// NetworkAccessProvider gives a Silo deployment an overlay-network identity +// (Tailscale, NetBird, ...). The plugin owns the overlay client and a reverse +// proxy in front of the host listeners it learns from RuntimeHost.GetHostInfo; +// the host owns supervision, per-instance state storage, status aggregation, +// and the admin API. Plugins declaring this capability are resident: the host +// starts them at boot, restarts them on crash, and stops them last. +type NetworkAccessProviderServer interface { + // Connect brings the overlay identity up and starts proxying. It returns the + // status reached so far; enrollment may continue in the background and be + // reported through RuntimeHost.ReportNetworkAccessStatus. + Connect(context.Context, *NetworkAccessConnectRequest) (*NetworkAccessStatus, error) + // Disconnect tears the overlay listener down and clears desired_connected. + Disconnect(context.Context, *NetworkAccessDisconnectRequest) (*NetworkAccessStatus, error) + // GetStatus returns the current status without changing it. + GetStatus(context.Context, *NetworkAccessGetStatusRequest) (*NetworkAccessStatus, error) +} + +// UnimplementedNetworkAccessProviderServer should be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedNetworkAccessProviderServer struct{} + +func (UnimplementedNetworkAccessProviderServer) Connect(context.Context, *NetworkAccessConnectRequest) (*NetworkAccessStatus, error) { + return nil, status.Error(codes.Unimplemented, "method Connect not implemented") +} +func (UnimplementedNetworkAccessProviderServer) Disconnect(context.Context, *NetworkAccessDisconnectRequest) (*NetworkAccessStatus, error) { + return nil, status.Error(codes.Unimplemented, "method Disconnect not implemented") +} +func (UnimplementedNetworkAccessProviderServer) GetStatus(context.Context, *NetworkAccessGetStatusRequest) (*NetworkAccessStatus, error) { + return nil, status.Error(codes.Unimplemented, "method GetStatus not implemented") +} +func (UnimplementedNetworkAccessProviderServer) testEmbeddedByValue() {} + +// UnsafeNetworkAccessProviderServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to NetworkAccessProviderServer will +// result in compilation errors. +type UnsafeNetworkAccessProviderServer interface { + mustEmbedUnimplementedNetworkAccessProviderServer() +} + +func RegisterNetworkAccessProviderServer(s grpc.ServiceRegistrar, srv NetworkAccessProviderServer) { + // If the following call panics, it indicates UnimplementedNetworkAccessProviderServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&NetworkAccessProvider_ServiceDesc, srv) +} + +func _NetworkAccessProvider_Connect_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(NetworkAccessConnectRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NetworkAccessProviderServer).Connect(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: NetworkAccessProvider_Connect_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NetworkAccessProviderServer).Connect(ctx, req.(*NetworkAccessConnectRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _NetworkAccessProvider_Disconnect_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(NetworkAccessDisconnectRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NetworkAccessProviderServer).Disconnect(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: NetworkAccessProvider_Disconnect_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NetworkAccessProviderServer).Disconnect(ctx, req.(*NetworkAccessDisconnectRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _NetworkAccessProvider_GetStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(NetworkAccessGetStatusRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NetworkAccessProviderServer).GetStatus(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: NetworkAccessProvider_GetStatus_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NetworkAccessProviderServer).GetStatus(ctx, req.(*NetworkAccessGetStatusRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// NetworkAccessProvider_ServiceDesc is the grpc.ServiceDesc for NetworkAccessProvider service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var NetworkAccessProvider_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "silo.plugin.v1.NetworkAccessProvider", + HandlerType: (*NetworkAccessProviderServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Connect", + Handler: _NetworkAccessProvider_Connect_Handler, + }, + { + MethodName: "Disconnect", + Handler: _NetworkAccessProvider_Disconnect_Handler, + }, + { + MethodName: "GetStatus", + Handler: _NetworkAccessProvider_GetStatus_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "silo/plugin/v1/network_access_provider.proto", +} diff --git a/pkg/pluginproto/silo/plugin/v1/runtime_host.pb.go b/pkg/pluginproto/silo/plugin/v1/runtime_host.pb.go index a7643a5..8569828 100644 --- a/pkg/pluginproto/silo/plugin/v1/runtime_host.pb.go +++ b/pkg/pluginproto/silo/plugin/v1/runtime_host.pb.go @@ -350,8 +350,23 @@ type GetHostInfoResponse struct { PublicBaseUrl string `protobuf:"bytes,1,opt,name=public_base_url,json=publicBaseUrl,proto3" json:"public_base_url,omitempty"` InternalBaseUrl string `protobuf:"bytes,2,opt,name=internal_base_url,json=internalBaseUrl,proto3" json:"internal_base_url,omitempty"` PluginProxyBaseUrl string `protobuf:"bytes,3,opt,name=plugin_proxy_base_url,json=pluginProxyBaseUrl,proto3" json:"plugin_proxy_base_url,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // Role of the process hosting this plugin instance: api | proxy. + HostRole string `protobuf:"bytes,4,opt,name=host_role,json=hostRole,proto3" json:"host_role,omitempty"` + // Node name on proxies, or the server name on the api host. + HostName string `protobuf:"bytes,5,opt,name=host_name,json=hostName,proto3" json:"host_name,omitempty"` + // stream_nodes.id on proxies; 0 on the api host. + NodeId int64 `protobuf:"varint,6,opt,name=node_id,json=nodeId,proto3" json:"node_id,omitempty"` + // Per-process-start secret that a network access provider stamps on + // proxied requests as X-Silo-Ingress-Token. Treat as a credential: keep it + // in memory, never log or persist it, and re-read GetHostInfo after a + // restart because the host rotates it on every start. + IngressToken string `protobuf:"bytes,7,opt,name=ingress_token,json=ingressToken,proto3" json:"ingress_token,omitempty"` + // Host listeners a network access provider should expose. Always contains + // "api"; "jellyfin" and "abs" appear when those listeners are enabled. + // Proxies expose only "api". + Listeners []*HostListener `protobuf:"bytes,8,rep,name=listeners,proto3" json:"listeners,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *GetHostInfoResponse) Reset() { @@ -405,6 +420,107 @@ func (x *GetHostInfoResponse) GetPluginProxyBaseUrl() string { return "" } +func (x *GetHostInfoResponse) GetHostRole() string { + if x != nil { + return x.HostRole + } + return "" +} + +func (x *GetHostInfoResponse) GetHostName() string { + if x != nil { + return x.HostName + } + return "" +} + +func (x *GetHostInfoResponse) GetNodeId() int64 { + if x != nil { + return x.NodeId + } + return 0 +} + +func (x *GetHostInfoResponse) GetIngressToken() string { + if x != nil { + return x.IngressToken + } + return "" +} + +func (x *GetHostInfoResponse) GetListeners() []*HostListener { + if x != nil { + return x.Listeners + } + return nil +} + +// HostListener is one local listener the host asks a network access provider +// to expose on the overlay network. +type HostListener struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Listener name: api | jellyfin | abs. + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // Loopback dial address for the listener, host:port. + Address string `protobuf:"bytes,2,opt,name=address,proto3" json:"address,omitempty"` + // Port the plugin should expose this listener on (e.g. 443 for api under + // HTTPS, 8096 for jellyfin, 13378 for abs). Zero means provider default. + DefaultPort int32 `protobuf:"varint,3,opt,name=default_port,json=defaultPort,proto3" json:"default_port,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *HostListener) Reset() { + *x = HostListener{} + mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *HostListener) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HostListener) ProtoMessage() {} + +func (x *HostListener) ProtoReflect() protoreflect.Message { + mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HostListener.ProtoReflect.Descriptor instead. +func (*HostListener) Descriptor() ([]byte, []int) { + return file_silo_plugin_v1_runtime_host_proto_rawDescGZIP(), []int{8} +} + +func (x *HostListener) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *HostListener) GetAddress() string { + if x != nil { + return x.Address + } + return "" +} + +func (x *HostListener) GetDefaultPort() int32 { + if x != nil { + return x.DefaultPort + } + return 0 +} + type ListLibrariesRequest struct { state protoimpl.MessageState `protogen:"open.v1"` // Optional: scope results to libraries this user is allowed to see. @@ -415,7 +531,7 @@ type ListLibrariesRequest struct { func (x *ListLibrariesRequest) Reset() { *x = ListLibrariesRequest{} - mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[8] + mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -427,7 +543,7 @@ func (x *ListLibrariesRequest) String() string { func (*ListLibrariesRequest) ProtoMessage() {} func (x *ListLibrariesRequest) ProtoReflect() protoreflect.Message { - mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[8] + mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -440,7 +556,7 @@ func (x *ListLibrariesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListLibrariesRequest.ProtoReflect.Descriptor instead. func (*ListLibrariesRequest) Descriptor() ([]byte, []int) { - return file_silo_plugin_v1_runtime_host_proto_rawDescGZIP(), []int{8} + return file_silo_plugin_v1_runtime_host_proto_rawDescGZIP(), []int{9} } func (x *ListLibrariesRequest) GetUserId() string { @@ -459,7 +575,7 @@ type ListLibrariesResponse struct { func (x *ListLibrariesResponse) Reset() { *x = ListLibrariesResponse{} - mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[9] + mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -471,7 +587,7 @@ func (x *ListLibrariesResponse) String() string { func (*ListLibrariesResponse) ProtoMessage() {} func (x *ListLibrariesResponse) ProtoReflect() protoreflect.Message { - mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[9] + mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -484,7 +600,7 @@ func (x *ListLibrariesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListLibrariesResponse.ProtoReflect.Descriptor instead. func (*ListLibrariesResponse) Descriptor() ([]byte, []int) { - return file_silo_plugin_v1_runtime_host_proto_rawDescGZIP(), []int{9} + return file_silo_plugin_v1_runtime_host_proto_rawDescGZIP(), []int{10} } func (x *ListLibrariesResponse) GetLibraries() []*Library { @@ -506,7 +622,7 @@ type Library struct { func (x *Library) Reset() { *x = Library{} - mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[10] + mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -518,7 +634,7 @@ func (x *Library) String() string { func (*Library) ProtoMessage() {} func (x *Library) ProtoReflect() protoreflect.Message { - mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[10] + mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -531,7 +647,7 @@ func (x *Library) ProtoReflect() protoreflect.Message { // Deprecated: Use Library.ProtoReflect.Descriptor instead. func (*Library) Descriptor() ([]byte, []int) { - return file_silo_plugin_v1_runtime_host_proto_rawDescGZIP(), []int{10} + return file_silo_plugin_v1_runtime_host_proto_rawDescGZIP(), []int{11} } func (x *Library) GetId() string { @@ -570,7 +686,7 @@ type CheckMediaPresenceRequest struct { func (x *CheckMediaPresenceRequest) Reset() { *x = CheckMediaPresenceRequest{} - mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[11] + mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -582,7 +698,7 @@ func (x *CheckMediaPresenceRequest) String() string { func (*CheckMediaPresenceRequest) ProtoMessage() {} func (x *CheckMediaPresenceRequest) ProtoReflect() protoreflect.Message { - mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[11] + mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -595,7 +711,7 @@ func (x *CheckMediaPresenceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CheckMediaPresenceRequest.ProtoReflect.Descriptor instead. func (*CheckMediaPresenceRequest) Descriptor() ([]byte, []int) { - return file_silo_plugin_v1_runtime_host_proto_rawDescGZIP(), []int{11} + return file_silo_plugin_v1_runtime_host_proto_rawDescGZIP(), []int{12} } func (x *CheckMediaPresenceRequest) GetProvider() string { @@ -630,7 +746,7 @@ type CheckMediaPresenceResponse struct { func (x *CheckMediaPresenceResponse) Reset() { *x = CheckMediaPresenceResponse{} - mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[12] + mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -642,7 +758,7 @@ func (x *CheckMediaPresenceResponse) String() string { func (*CheckMediaPresenceResponse) ProtoMessage() {} func (x *CheckMediaPresenceResponse) ProtoReflect() protoreflect.Message { - mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[12] + mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -655,7 +771,7 @@ func (x *CheckMediaPresenceResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CheckMediaPresenceResponse.ProtoReflect.Descriptor instead. func (*CheckMediaPresenceResponse) Descriptor() ([]byte, []int) { - return file_silo_plugin_v1_runtime_host_proto_rawDescGZIP(), []int{12} + return file_silo_plugin_v1_runtime_host_proto_rawDescGZIP(), []int{13} } func (x *CheckMediaPresenceResponse) GetPresent() []*MediaPresence { @@ -677,7 +793,7 @@ type MediaPresence struct { func (x *MediaPresence) Reset() { *x = MediaPresence{} - mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[13] + mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -689,7 +805,7 @@ func (x *MediaPresence) String() string { func (*MediaPresence) ProtoMessage() {} func (x *MediaPresence) ProtoReflect() protoreflect.Message { - mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[13] + mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -702,7 +818,7 @@ func (x *MediaPresence) ProtoReflect() protoreflect.Message { // Deprecated: Use MediaPresence.ProtoReflect.Descriptor instead. func (*MediaPresence) Descriptor() ([]byte, []int) { - return file_silo_plugin_v1_runtime_host_proto_rawDescGZIP(), []int{13} + return file_silo_plugin_v1_runtime_host_proto_rawDescGZIP(), []int{14} } func (x *MediaPresence) GetExternalId() string { @@ -741,7 +857,7 @@ type ListInstalledPluginsRequest struct { func (x *ListInstalledPluginsRequest) Reset() { *x = ListInstalledPluginsRequest{} - mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[14] + mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -753,7 +869,7 @@ func (x *ListInstalledPluginsRequest) String() string { func (*ListInstalledPluginsRequest) ProtoMessage() {} func (x *ListInstalledPluginsRequest) ProtoReflect() protoreflect.Message { - mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[14] + mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -766,7 +882,7 @@ func (x *ListInstalledPluginsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListInstalledPluginsRequest.ProtoReflect.Descriptor instead. func (*ListInstalledPluginsRequest) Descriptor() ([]byte, []int) { - return file_silo_plugin_v1_runtime_host_proto_rawDescGZIP(), []int{14} + return file_silo_plugin_v1_runtime_host_proto_rawDescGZIP(), []int{15} } type ListInstalledPluginsResponse struct { @@ -778,7 +894,7 @@ type ListInstalledPluginsResponse struct { func (x *ListInstalledPluginsResponse) Reset() { *x = ListInstalledPluginsResponse{} - mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[15] + mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -790,7 +906,7 @@ func (x *ListInstalledPluginsResponse) String() string { func (*ListInstalledPluginsResponse) ProtoMessage() {} func (x *ListInstalledPluginsResponse) ProtoReflect() protoreflect.Message { - mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[15] + mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -803,7 +919,7 @@ func (x *ListInstalledPluginsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListInstalledPluginsResponse.ProtoReflect.Descriptor instead. func (*ListInstalledPluginsResponse) Descriptor() ([]byte, []int) { - return file_silo_plugin_v1_runtime_host_proto_rawDescGZIP(), []int{15} + return file_silo_plugin_v1_runtime_host_proto_rawDescGZIP(), []int{16} } func (x *ListInstalledPluginsResponse) GetPlugins() []*InstalledPlugin { @@ -826,7 +942,7 @@ type InstalledPlugin struct { func (x *InstalledPlugin) Reset() { *x = InstalledPlugin{} - mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[16] + mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -838,7 +954,7 @@ func (x *InstalledPlugin) String() string { func (*InstalledPlugin) ProtoMessage() {} func (x *InstalledPlugin) ProtoReflect() protoreflect.Message { - mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[16] + mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -851,7 +967,7 @@ func (x *InstalledPlugin) ProtoReflect() protoreflect.Message { // Deprecated: Use InstalledPlugin.ProtoReflect.Descriptor instead. func (*InstalledPlugin) Descriptor() ([]byte, []int) { - return file_silo_plugin_v1_runtime_host_proto_rawDescGZIP(), []int{16} + return file_silo_plugin_v1_runtime_host_proto_rawDescGZIP(), []int{17} } func (x *InstalledPlugin) GetInstallationId() int64 { @@ -899,7 +1015,7 @@ type SetGlobalConfigEntryRequest struct { func (x *SetGlobalConfigEntryRequest) Reset() { *x = SetGlobalConfigEntryRequest{} - mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[17] + mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -911,7 +1027,7 @@ func (x *SetGlobalConfigEntryRequest) String() string { func (*SetGlobalConfigEntryRequest) ProtoMessage() {} func (x *SetGlobalConfigEntryRequest) ProtoReflect() protoreflect.Message { - mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[17] + mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[18] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -924,7 +1040,7 @@ func (x *SetGlobalConfigEntryRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SetGlobalConfigEntryRequest.ProtoReflect.Descriptor instead. func (*SetGlobalConfigEntryRequest) Descriptor() ([]byte, []int) { - return file_silo_plugin_v1_runtime_host_proto_rawDescGZIP(), []int{17} + return file_silo_plugin_v1_runtime_host_proto_rawDescGZIP(), []int{18} } func (x *SetGlobalConfigEntryRequest) GetKey() string { @@ -949,7 +1065,7 @@ type SetGlobalConfigEntryResponse struct { func (x *SetGlobalConfigEntryResponse) Reset() { *x = SetGlobalConfigEntryResponse{} - mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[18] + mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -961,7 +1077,7 @@ func (x *SetGlobalConfigEntryResponse) String() string { func (*SetGlobalConfigEntryResponse) ProtoMessage() {} func (x *SetGlobalConfigEntryResponse) ProtoReflect() protoreflect.Message { - mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[18] + mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[19] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -974,7 +1090,7 @@ func (x *SetGlobalConfigEntryResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SetGlobalConfigEntryResponse.ProtoReflect.Descriptor instead. func (*SetGlobalConfigEntryResponse) Descriptor() ([]byte, []int) { - return file_silo_plugin_v1_runtime_host_proto_rawDescGZIP(), []int{18} + return file_silo_plugin_v1_runtime_host_proto_rawDescGZIP(), []int{19} } type ListLibraryMediaRequest struct { @@ -998,7 +1114,7 @@ type ListLibraryMediaRequest struct { func (x *ListLibraryMediaRequest) Reset() { *x = ListLibraryMediaRequest{} - mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[19] + mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1010,7 +1126,7 @@ func (x *ListLibraryMediaRequest) String() string { func (*ListLibraryMediaRequest) ProtoMessage() {} func (x *ListLibraryMediaRequest) ProtoReflect() protoreflect.Message { - mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[19] + mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1023,7 +1139,7 @@ func (x *ListLibraryMediaRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListLibraryMediaRequest.ProtoReflect.Descriptor instead. func (*ListLibraryMediaRequest) Descriptor() ([]byte, []int) { - return file_silo_plugin_v1_runtime_host_proto_rawDescGZIP(), []int{19} + return file_silo_plugin_v1_runtime_host_proto_rawDescGZIP(), []int{20} } func (x *ListLibraryMediaRequest) GetLibraryIds() []string { @@ -1107,7 +1223,7 @@ type ListLibraryMediaResponse struct { func (x *ListLibraryMediaResponse) Reset() { *x = ListLibraryMediaResponse{} - mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[20] + mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1119,7 +1235,7 @@ func (x *ListLibraryMediaResponse) String() string { func (*ListLibraryMediaResponse) ProtoMessage() {} func (x *ListLibraryMediaResponse) ProtoReflect() protoreflect.Message { - mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[20] + mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[21] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1132,7 +1248,7 @@ func (x *ListLibraryMediaResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListLibraryMediaResponse.ProtoReflect.Descriptor instead. func (*ListLibraryMediaResponse) Descriptor() ([]byte, []int) { - return file_silo_plugin_v1_runtime_host_proto_rawDescGZIP(), []int{20} + return file_silo_plugin_v1_runtime_host_proto_rawDescGZIP(), []int{21} } func (x *ListLibraryMediaResponse) GetItems() []*CatalogMediaItem { @@ -1179,7 +1295,7 @@ type CatalogMediaItem struct { func (x *CatalogMediaItem) Reset() { *x = CatalogMediaItem{} - mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[21] + mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1191,7 +1307,7 @@ func (x *CatalogMediaItem) String() string { func (*CatalogMediaItem) ProtoMessage() {} func (x *CatalogMediaItem) ProtoReflect() protoreflect.Message { - mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[21] + mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[22] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1204,7 +1320,7 @@ func (x *CatalogMediaItem) ProtoReflect() protoreflect.Message { // Deprecated: Use CatalogMediaItem.ProtoReflect.Descriptor instead. func (*CatalogMediaItem) Descriptor() ([]byte, []int) { - return file_silo_plugin_v1_runtime_host_proto_rawDescGZIP(), []int{21} + return file_silo_plugin_v1_runtime_host_proto_rawDescGZIP(), []int{22} } func (x *CatalogMediaItem) GetMediaId() string { @@ -1322,7 +1438,7 @@ type GetCatalogStatsRequest struct { func (x *GetCatalogStatsRequest) Reset() { *x = GetCatalogStatsRequest{} - mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[22] + mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1334,7 +1450,7 @@ func (x *GetCatalogStatsRequest) String() string { func (*GetCatalogStatsRequest) ProtoMessage() {} func (x *GetCatalogStatsRequest) ProtoReflect() protoreflect.Message { - mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[22] + mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[23] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1347,7 +1463,7 @@ func (x *GetCatalogStatsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetCatalogStatsRequest.ProtoReflect.Descriptor instead. func (*GetCatalogStatsRequest) Descriptor() ([]byte, []int) { - return file_silo_plugin_v1_runtime_host_proto_rawDescGZIP(), []int{22} + return file_silo_plugin_v1_runtime_host_proto_rawDescGZIP(), []int{23} } func (x *GetCatalogStatsRequest) GetLibraryIds() []string { @@ -1368,7 +1484,7 @@ type GetCatalogStatsResponse struct { func (x *GetCatalogStatsResponse) Reset() { *x = GetCatalogStatsResponse{} - mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[23] + mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1380,7 +1496,7 @@ func (x *GetCatalogStatsResponse) String() string { func (*GetCatalogStatsResponse) ProtoMessage() {} func (x *GetCatalogStatsResponse) ProtoReflect() protoreflect.Message { - mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[23] + mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[24] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1393,7 +1509,7 @@ func (x *GetCatalogStatsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetCatalogStatsResponse.ProtoReflect.Descriptor instead. func (*GetCatalogStatsResponse) Descriptor() ([]byte, []int) { - return file_silo_plugin_v1_runtime_host_proto_rawDescGZIP(), []int{23} + return file_silo_plugin_v1_runtime_host_proto_rawDescGZIP(), []int{24} } func (x *GetCatalogStatsResponse) GetTotalItems() int32 { @@ -1427,7 +1543,7 @@ type CatalogTypeCount struct { func (x *CatalogTypeCount) Reset() { *x = CatalogTypeCount{} - mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[24] + mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1439,7 +1555,7 @@ func (x *CatalogTypeCount) String() string { func (*CatalogTypeCount) ProtoMessage() {} func (x *CatalogTypeCount) ProtoReflect() protoreflect.Message { - mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[24] + mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[25] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1452,7 +1568,7 @@ func (x *CatalogTypeCount) ProtoReflect() protoreflect.Message { // Deprecated: Use CatalogTypeCount.ProtoReflect.Descriptor instead. func (*CatalogTypeCount) Descriptor() ([]byte, []int) { - return file_silo_plugin_v1_runtime_host_proto_rawDescGZIP(), []int{24} + return file_silo_plugin_v1_runtime_host_proto_rawDescGZIP(), []int{25} } func (x *CatalogTypeCount) GetMediaType() string { @@ -1481,7 +1597,7 @@ type CatalogLibraryCount struct { func (x *CatalogLibraryCount) Reset() { *x = CatalogLibraryCount{} - mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[25] + mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1493,7 +1609,7 @@ func (x *CatalogLibraryCount) String() string { func (*CatalogLibraryCount) ProtoMessage() {} func (x *CatalogLibraryCount) ProtoReflect() protoreflect.Message { - mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[25] + mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[26] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1506,7 +1622,7 @@ func (x *CatalogLibraryCount) ProtoReflect() protoreflect.Message { // Deprecated: Use CatalogLibraryCount.ProtoReflect.Descriptor instead. func (*CatalogLibraryCount) Descriptor() ([]byte, []int) { - return file_silo_plugin_v1_runtime_host_proto_rawDescGZIP(), []int{25} + return file_silo_plugin_v1_runtime_host_proto_rawDescGZIP(), []int{26} } func (x *CatalogLibraryCount) GetLibraryId() string { @@ -1557,7 +1673,7 @@ type ResolveCatalogImageURLsRequest struct { func (x *ResolveCatalogImageURLsRequest) Reset() { *x = ResolveCatalogImageURLsRequest{} - mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[26] + mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1569,7 +1685,7 @@ func (x *ResolveCatalogImageURLsRequest) String() string { func (*ResolveCatalogImageURLsRequest) ProtoMessage() {} func (x *ResolveCatalogImageURLsRequest) ProtoReflect() protoreflect.Message { - mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[26] + mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[27] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1582,7 +1698,7 @@ func (x *ResolveCatalogImageURLsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ResolveCatalogImageURLsRequest.ProtoReflect.Descriptor instead. func (*ResolveCatalogImageURLsRequest) Descriptor() ([]byte, []int) { - return file_silo_plugin_v1_runtime_host_proto_rawDescGZIP(), []int{26} + return file_silo_plugin_v1_runtime_host_proto_rawDescGZIP(), []int{27} } func (x *ResolveCatalogImageURLsRequest) GetPaths() []string { @@ -1611,7 +1727,7 @@ type ResolveCatalogImageURLsResponse struct { func (x *ResolveCatalogImageURLsResponse) Reset() { *x = ResolveCatalogImageURLsResponse{} - mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[27] + mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1623,7 +1739,7 @@ func (x *ResolveCatalogImageURLsResponse) String() string { func (*ResolveCatalogImageURLsResponse) ProtoMessage() {} func (x *ResolveCatalogImageURLsResponse) ProtoReflect() protoreflect.Message { - mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[27] + mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[28] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1636,7 +1752,7 @@ func (x *ResolveCatalogImageURLsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ResolveCatalogImageURLsResponse.ProtoReflect.Descriptor instead. func (*ResolveCatalogImageURLsResponse) Descriptor() ([]byte, []int) { - return file_silo_plugin_v1_runtime_host_proto_rawDescGZIP(), []int{27} + return file_silo_plugin_v1_runtime_host_proto_rawDescGZIP(), []int{28} } func (x *ResolveCatalogImageURLsResponse) GetUrls() map[string]string { @@ -1666,7 +1782,7 @@ type MintScopedStreamRequest struct { func (x *MintScopedStreamRequest) Reset() { *x = MintScopedStreamRequest{} - mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[28] + mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1678,7 +1794,7 @@ func (x *MintScopedStreamRequest) String() string { func (*MintScopedStreamRequest) ProtoMessage() {} func (x *MintScopedStreamRequest) ProtoReflect() protoreflect.Message { - mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[28] + mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[29] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1691,7 +1807,7 @@ func (x *MintScopedStreamRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use MintScopedStreamRequest.ProtoReflect.Descriptor instead. func (*MintScopedStreamRequest) Descriptor() ([]byte, []int) { - return file_silo_plugin_v1_runtime_host_proto_rawDescGZIP(), []int{28} + return file_silo_plugin_v1_runtime_host_proto_rawDescGZIP(), []int{29} } func (x *MintScopedStreamRequest) GetMediaFileId() int64 { @@ -1789,7 +1905,7 @@ type MintScopedStreamResponse struct { func (x *MintScopedStreamResponse) Reset() { *x = MintScopedStreamResponse{} - mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[29] + mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1801,7 +1917,7 @@ func (x *MintScopedStreamResponse) String() string { func (*MintScopedStreamResponse) ProtoMessage() {} func (x *MintScopedStreamResponse) ProtoReflect() protoreflect.Message { - mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[29] + mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[30] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1814,7 +1930,7 @@ func (x *MintScopedStreamResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use MintScopedStreamResponse.ProtoReflect.Descriptor instead. func (*MintScopedStreamResponse) Descriptor() ([]byte, []int) { - return file_silo_plugin_v1_runtime_host_proto_rawDescGZIP(), []int{29} + return file_silo_plugin_v1_runtime_host_proto_rawDescGZIP(), []int{30} } func (x *MintScopedStreamResponse) GetStreamUrl() string { @@ -1852,7 +1968,7 @@ type CallPluginHTTPRequest struct { func (x *CallPluginHTTPRequest) Reset() { *x = CallPluginHTTPRequest{} - mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[30] + mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1864,7 +1980,7 @@ func (x *CallPluginHTTPRequest) String() string { func (*CallPluginHTTPRequest) ProtoMessage() {} func (x *CallPluginHTTPRequest) ProtoReflect() protoreflect.Message { - mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[30] + mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[31] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1877,7 +1993,7 @@ func (x *CallPluginHTTPRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CallPluginHTTPRequest.ProtoReflect.Descriptor instead. func (*CallPluginHTTPRequest) Descriptor() ([]byte, []int) { - return file_silo_plugin_v1_runtime_host_proto_rawDescGZIP(), []int{30} + return file_silo_plugin_v1_runtime_host_proto_rawDescGZIP(), []int{31} } func (x *CallPluginHTTPRequest) GetInstallationId() int32 { @@ -1933,7 +2049,7 @@ type CallPluginHTTPResponse struct { func (x *CallPluginHTTPResponse) Reset() { *x = CallPluginHTTPResponse{} - mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[31] + mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1945,7 +2061,7 @@ func (x *CallPluginHTTPResponse) String() string { func (*CallPluginHTTPResponse) ProtoMessage() {} func (x *CallPluginHTTPResponse) ProtoReflect() protoreflect.Message { - mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[31] + mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[32] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1958,7 +2074,7 @@ func (x *CallPluginHTTPResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CallPluginHTTPResponse.ProtoReflect.Descriptor instead. func (*CallPluginHTTPResponse) Descriptor() ([]byte, []int) { - return file_silo_plugin_v1_runtime_host_proto_rawDescGZIP(), []int{31} + return file_silo_plugin_v1_runtime_host_proto_rawDescGZIP(), []int{32} } func (x *CallPluginHTTPResponse) GetStatusCode() int32 { @@ -1982,11 +2098,280 @@ func (x *CallPluginHTTPResponse) GetBody() []byte { return nil } +type ReadInstanceStateRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // State key, <= 256 bytes. + Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ReadInstanceStateRequest) Reset() { + *x = ReadInstanceStateRequest{} + mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[33] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ReadInstanceStateRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ReadInstanceStateRequest) ProtoMessage() {} + +func (x *ReadInstanceStateRequest) ProtoReflect() protoreflect.Message { + mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[33] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ReadInstanceStateRequest.ProtoReflect.Descriptor instead. +func (*ReadInstanceStateRequest) Descriptor() ([]byte, []int) { + return file_silo_plugin_v1_runtime_host_proto_rawDescGZIP(), []int{33} +} + +func (x *ReadInstanceStateRequest) GetKey() string { + if x != nil { + return x.Key + } + return "" +} + +type ReadInstanceStateResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Stored value; empty when found is false. + Value []byte `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"` + // False when no value is stored under key in this instance's scope. + Found bool `protobuf:"varint,2,opt,name=found,proto3" json:"found,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ReadInstanceStateResponse) Reset() { + *x = ReadInstanceStateResponse{} + mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[34] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ReadInstanceStateResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ReadInstanceStateResponse) ProtoMessage() {} + +func (x *ReadInstanceStateResponse) ProtoReflect() protoreflect.Message { + mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[34] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ReadInstanceStateResponse.ProtoReflect.Descriptor instead. +func (*ReadInstanceStateResponse) Descriptor() ([]byte, []int) { + return file_silo_plugin_v1_runtime_host_proto_rawDescGZIP(), []int{34} +} + +func (x *ReadInstanceStateResponse) GetValue() []byte { + if x != nil { + return x.Value + } + return nil +} + +func (x *ReadInstanceStateResponse) GetFound() bool { + if x != nil { + return x.Found + } + return false +} + +type WriteInstanceStateRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // State key, <= 256 bytes. + Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + // Value to store, <= 256 KiB. + Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *WriteInstanceStateRequest) Reset() { + *x = WriteInstanceStateRequest{} + mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[35] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *WriteInstanceStateRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WriteInstanceStateRequest) ProtoMessage() {} + +func (x *WriteInstanceStateRequest) ProtoReflect() protoreflect.Message { + mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[35] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WriteInstanceStateRequest.ProtoReflect.Descriptor instead. +func (*WriteInstanceStateRequest) Descriptor() ([]byte, []int) { + return file_silo_plugin_v1_runtime_host_proto_rawDescGZIP(), []int{35} +} + +func (x *WriteInstanceStateRequest) GetKey() string { + if x != nil { + return x.Key + } + return "" +} + +func (x *WriteInstanceStateRequest) GetValue() []byte { + if x != nil { + return x.Value + } + return nil +} + +type WriteInstanceStateResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *WriteInstanceStateResponse) Reset() { + *x = WriteInstanceStateResponse{} + mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[36] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *WriteInstanceStateResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WriteInstanceStateResponse) ProtoMessage() {} + +func (x *WriteInstanceStateResponse) ProtoReflect() protoreflect.Message { + mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[36] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WriteInstanceStateResponse.ProtoReflect.Descriptor instead. +func (*WriteInstanceStateResponse) Descriptor() ([]byte, []int) { + return file_silo_plugin_v1_runtime_host_proto_rawDescGZIP(), []int{36} +} + +type ReportNetworkAccessStatusRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Status *NetworkAccessStatus `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ReportNetworkAccessStatusRequest) Reset() { + *x = ReportNetworkAccessStatusRequest{} + mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[37] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ReportNetworkAccessStatusRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ReportNetworkAccessStatusRequest) ProtoMessage() {} + +func (x *ReportNetworkAccessStatusRequest) ProtoReflect() protoreflect.Message { + mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[37] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ReportNetworkAccessStatusRequest.ProtoReflect.Descriptor instead. +func (*ReportNetworkAccessStatusRequest) Descriptor() ([]byte, []int) { + return file_silo_plugin_v1_runtime_host_proto_rawDescGZIP(), []int{37} +} + +func (x *ReportNetworkAccessStatusRequest) GetStatus() *NetworkAccessStatus { + if x != nil { + return x.Status + } + return nil +} + +type ReportNetworkAccessStatusResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ReportNetworkAccessStatusResponse) Reset() { + *x = ReportNetworkAccessStatusResponse{} + mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[38] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ReportNetworkAccessStatusResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ReportNetworkAccessStatusResponse) ProtoMessage() {} + +func (x *ReportNetworkAccessStatusResponse) ProtoReflect() protoreflect.Message { + mi := &file_silo_plugin_v1_runtime_host_proto_msgTypes[38] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ReportNetworkAccessStatusResponse.ProtoReflect.Descriptor instead. +func (*ReportNetworkAccessStatusResponse) Descriptor() ([]byte, []int) { + return file_silo_plugin_v1_runtime_host_proto_rawDescGZIP(), []int{38} +} + var File_silo_plugin_v1_runtime_host_proto protoreflect.FileDescriptor const file_silo_plugin_v1_runtime_host_proto_rawDesc = "" + "\n" + - "!silo/plugin/v1/runtime_host.proto\x12\x0esilo.plugin.v1\x1a\x1cgoogle/protobuf/struct.proto\x1a\x1bsilo/plugin/v1/common.proto\"g\n" + + "!silo/plugin/v1/runtime_host.proto\x12\x0esilo.plugin.v1\x1a\x1cgoogle/protobuf/struct.proto\x1a\x1bsilo/plugin/v1/common.proto\x1a,silo/plugin/v1/network_access_provider.proto\"g\n" + "\x13PublishEventRequest\x12\x1d\n" + "\n" + "event_name\x18\x01 \x01(\tR\teventName\x121\n" + @@ -2004,11 +2389,20 @@ const file_silo_plugin_v1_runtime_host_proto_rawDesc = "" + "event_name\x18\x02 \x01(\tR\teventName\x121\n" + "\apayload\x18\x03 \x01(\v2\x17.google.protobuf.StructR\apayload\"$\n" + "\"PublishEventToInstallationResponse\"\x14\n" + - "\x12GetHostInfoRequest\"\x9c\x01\n" + + "\x12GetHostInfoRequest\"\xd0\x02\n" + "\x13GetHostInfoResponse\x12&\n" + "\x0fpublic_base_url\x18\x01 \x01(\tR\rpublicBaseUrl\x12*\n" + "\x11internal_base_url\x18\x02 \x01(\tR\x0finternalBaseUrl\x121\n" + - "\x15plugin_proxy_base_url\x18\x03 \x01(\tR\x12pluginProxyBaseUrl\"/\n" + + "\x15plugin_proxy_base_url\x18\x03 \x01(\tR\x12pluginProxyBaseUrl\x12\x1b\n" + + "\thost_role\x18\x04 \x01(\tR\bhostRole\x12\x1b\n" + + "\thost_name\x18\x05 \x01(\tR\bhostName\x12\x17\n" + + "\anode_id\x18\x06 \x01(\x03R\x06nodeId\x12#\n" + + "\ringress_token\x18\a \x01(\tR\fingressToken\x12:\n" + + "\tlisteners\x18\b \x03(\v2\x1c.silo.plugin.v1.HostListenerR\tlisteners\"_\n" + + "\fHostListener\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x18\n" + + "\aaddress\x18\x02 \x01(\tR\aaddress\x12!\n" + + "\fdefault_port\x18\x03 \x01(\x05R\vdefaultPort\"/\n" + "\x14ListLibrariesRequest\x12\x17\n" + "\auser_id\x18\x01 \x01(\tR\x06userId\"N\n" + "\x15ListLibrariesResponse\x125\n" + @@ -2153,8 +2547,19 @@ const file_silo_plugin_v1_runtime_host_proto_rawDesc = "" + "\x04body\x18\x03 \x01(\fR\x04body\x1a:\n" + "\fHeadersEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\tR\x05value:\x028\x012\xe7\n" + - "\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\",\n" + + "\x18ReadInstanceStateRequest\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\"G\n" + + "\x19ReadInstanceStateResponse\x12\x14\n" + + "\x05value\x18\x01 \x01(\fR\x05value\x12\x14\n" + + "\x05found\x18\x02 \x01(\bR\x05found\"C\n" + + "\x19WriteInstanceStateRequest\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\fR\x05value\"\x1c\n" + + "\x1aWriteInstanceStateResponse\"_\n" + + " ReportNetworkAccessStatusRequest\x12;\n" + + "\x06status\x18\x01 \x01(\v2#.silo.plugin.v1.NetworkAccessStatusR\x06status\"#\n" + + "!ReportNetworkAccessStatusResponse2\xc1\r\n" + "\vRuntimeHost\x12Y\n" + "\fPublishEvent\x12#.silo.plugin.v1.PublishEventRequest\x1a$.silo.plugin.v1.PublishEventResponse\x12_\n" + "\x0ePublishEventTo\x12%.silo.plugin.v1.PublishEventToRequest\x1a&.silo.plugin.v1.PublishEventToResponse\x12\x83\x01\n" + @@ -2168,7 +2573,10 @@ const file_silo_plugin_v1_runtime_host_proto_rawDesc = "" + "\x0fGetCatalogStats\x12&.silo.plugin.v1.GetCatalogStatsRequest\x1a'.silo.plugin.v1.GetCatalogStatsResponse\x12z\n" + "\x17ResolveCatalogImageURLs\x12..silo.plugin.v1.ResolveCatalogImageURLsRequest\x1a/.silo.plugin.v1.ResolveCatalogImageURLsResponse\x12e\n" + "\x10MintScopedStream\x12'.silo.plugin.v1.MintScopedStreamRequest\x1a(.silo.plugin.v1.MintScopedStreamResponse\x12_\n" + - "\x0eCallPluginHTTP\x12%.silo.plugin.v1.CallPluginHTTPRequest\x1a&.silo.plugin.v1.CallPluginHTTPResponseBPZNgithub.com/Silo-Server/silo-plugin-sdk/pkg/pluginproto/silo/plugin/v1;pluginv1b\x06proto3" + "\x0eCallPluginHTTP\x12%.silo.plugin.v1.CallPluginHTTPRequest\x1a&.silo.plugin.v1.CallPluginHTTPResponse\x12h\n" + + "\x11ReadInstanceState\x12(.silo.plugin.v1.ReadInstanceStateRequest\x1a).silo.plugin.v1.ReadInstanceStateResponse\x12k\n" + + "\x12WriteInstanceState\x12).silo.plugin.v1.WriteInstanceStateRequest\x1a*.silo.plugin.v1.WriteInstanceStateResponse\x12\x80\x01\n" + + "\x19ReportNetworkAccessStatus\x120.silo.plugin.v1.ReportNetworkAccessStatusRequest\x1a1.silo.plugin.v1.ReportNetworkAccessStatusResponseBPZNgithub.com/Silo-Server/silo-plugin-sdk/pkg/pluginproto/silo/plugin/v1;pluginv1b\x06proto3" var ( file_silo_plugin_v1_runtime_host_proto_rawDescOnce sync.Once @@ -2182,7 +2590,7 @@ func file_silo_plugin_v1_runtime_host_proto_rawDescGZIP() []byte { return file_silo_plugin_v1_runtime_host_proto_rawDescData } -var file_silo_plugin_v1_runtime_host_proto_msgTypes = make([]protoimpl.MessageInfo, 35) +var file_silo_plugin_v1_runtime_host_proto_msgTypes = make([]protoimpl.MessageInfo, 42) var file_silo_plugin_v1_runtime_host_proto_goTypes = []any{ (*PublishEventRequest)(nil), // 0: silo.plugin.v1.PublishEventRequest (*PublishEventResponse)(nil), // 1: silo.plugin.v1.PublishEventResponse @@ -2192,83 +2600,99 @@ var file_silo_plugin_v1_runtime_host_proto_goTypes = []any{ (*PublishEventToInstallationResponse)(nil), // 5: silo.plugin.v1.PublishEventToInstallationResponse (*GetHostInfoRequest)(nil), // 6: silo.plugin.v1.GetHostInfoRequest (*GetHostInfoResponse)(nil), // 7: silo.plugin.v1.GetHostInfoResponse - (*ListLibrariesRequest)(nil), // 8: silo.plugin.v1.ListLibrariesRequest - (*ListLibrariesResponse)(nil), // 9: silo.plugin.v1.ListLibrariesResponse - (*Library)(nil), // 10: silo.plugin.v1.Library - (*CheckMediaPresenceRequest)(nil), // 11: silo.plugin.v1.CheckMediaPresenceRequest - (*CheckMediaPresenceResponse)(nil), // 12: silo.plugin.v1.CheckMediaPresenceResponse - (*MediaPresence)(nil), // 13: silo.plugin.v1.MediaPresence - (*ListInstalledPluginsRequest)(nil), // 14: silo.plugin.v1.ListInstalledPluginsRequest - (*ListInstalledPluginsResponse)(nil), // 15: silo.plugin.v1.ListInstalledPluginsResponse - (*InstalledPlugin)(nil), // 16: silo.plugin.v1.InstalledPlugin - (*SetGlobalConfigEntryRequest)(nil), // 17: silo.plugin.v1.SetGlobalConfigEntryRequest - (*SetGlobalConfigEntryResponse)(nil), // 18: silo.plugin.v1.SetGlobalConfigEntryResponse - (*ListLibraryMediaRequest)(nil), // 19: silo.plugin.v1.ListLibraryMediaRequest - (*ListLibraryMediaResponse)(nil), // 20: silo.plugin.v1.ListLibraryMediaResponse - (*CatalogMediaItem)(nil), // 21: silo.plugin.v1.CatalogMediaItem - (*GetCatalogStatsRequest)(nil), // 22: silo.plugin.v1.GetCatalogStatsRequest - (*GetCatalogStatsResponse)(nil), // 23: silo.plugin.v1.GetCatalogStatsResponse - (*CatalogTypeCount)(nil), // 24: silo.plugin.v1.CatalogTypeCount - (*CatalogLibraryCount)(nil), // 25: silo.plugin.v1.CatalogLibraryCount - (*ResolveCatalogImageURLsRequest)(nil), // 26: silo.plugin.v1.ResolveCatalogImageURLsRequest - (*ResolveCatalogImageURLsResponse)(nil), // 27: silo.plugin.v1.ResolveCatalogImageURLsResponse - (*MintScopedStreamRequest)(nil), // 28: silo.plugin.v1.MintScopedStreamRequest - (*MintScopedStreamResponse)(nil), // 29: silo.plugin.v1.MintScopedStreamResponse - (*CallPluginHTTPRequest)(nil), // 30: silo.plugin.v1.CallPluginHTTPRequest - (*CallPluginHTTPResponse)(nil), // 31: silo.plugin.v1.CallPluginHTTPResponse - nil, // 32: silo.plugin.v1.ResolveCatalogImageURLsResponse.UrlsEntry - nil, // 33: silo.plugin.v1.CallPluginHTTPRequest.HeadersEntry - nil, // 34: silo.plugin.v1.CallPluginHTTPResponse.HeadersEntry - (*structpb.Struct)(nil), // 35: google.protobuf.Struct - (*CapabilityDescriptor)(nil), // 36: silo.plugin.v1.CapabilityDescriptor + (*HostListener)(nil), // 8: silo.plugin.v1.HostListener + (*ListLibrariesRequest)(nil), // 9: silo.plugin.v1.ListLibrariesRequest + (*ListLibrariesResponse)(nil), // 10: silo.plugin.v1.ListLibrariesResponse + (*Library)(nil), // 11: silo.plugin.v1.Library + (*CheckMediaPresenceRequest)(nil), // 12: silo.plugin.v1.CheckMediaPresenceRequest + (*CheckMediaPresenceResponse)(nil), // 13: silo.plugin.v1.CheckMediaPresenceResponse + (*MediaPresence)(nil), // 14: silo.plugin.v1.MediaPresence + (*ListInstalledPluginsRequest)(nil), // 15: silo.plugin.v1.ListInstalledPluginsRequest + (*ListInstalledPluginsResponse)(nil), // 16: silo.plugin.v1.ListInstalledPluginsResponse + (*InstalledPlugin)(nil), // 17: silo.plugin.v1.InstalledPlugin + (*SetGlobalConfigEntryRequest)(nil), // 18: silo.plugin.v1.SetGlobalConfigEntryRequest + (*SetGlobalConfigEntryResponse)(nil), // 19: silo.plugin.v1.SetGlobalConfigEntryResponse + (*ListLibraryMediaRequest)(nil), // 20: silo.plugin.v1.ListLibraryMediaRequest + (*ListLibraryMediaResponse)(nil), // 21: silo.plugin.v1.ListLibraryMediaResponse + (*CatalogMediaItem)(nil), // 22: silo.plugin.v1.CatalogMediaItem + (*GetCatalogStatsRequest)(nil), // 23: silo.plugin.v1.GetCatalogStatsRequest + (*GetCatalogStatsResponse)(nil), // 24: silo.plugin.v1.GetCatalogStatsResponse + (*CatalogTypeCount)(nil), // 25: silo.plugin.v1.CatalogTypeCount + (*CatalogLibraryCount)(nil), // 26: silo.plugin.v1.CatalogLibraryCount + (*ResolveCatalogImageURLsRequest)(nil), // 27: silo.plugin.v1.ResolveCatalogImageURLsRequest + (*ResolveCatalogImageURLsResponse)(nil), // 28: silo.plugin.v1.ResolveCatalogImageURLsResponse + (*MintScopedStreamRequest)(nil), // 29: silo.plugin.v1.MintScopedStreamRequest + (*MintScopedStreamResponse)(nil), // 30: silo.plugin.v1.MintScopedStreamResponse + (*CallPluginHTTPRequest)(nil), // 31: silo.plugin.v1.CallPluginHTTPRequest + (*CallPluginHTTPResponse)(nil), // 32: silo.plugin.v1.CallPluginHTTPResponse + (*ReadInstanceStateRequest)(nil), // 33: silo.plugin.v1.ReadInstanceStateRequest + (*ReadInstanceStateResponse)(nil), // 34: silo.plugin.v1.ReadInstanceStateResponse + (*WriteInstanceStateRequest)(nil), // 35: silo.plugin.v1.WriteInstanceStateRequest + (*WriteInstanceStateResponse)(nil), // 36: silo.plugin.v1.WriteInstanceStateResponse + (*ReportNetworkAccessStatusRequest)(nil), // 37: silo.plugin.v1.ReportNetworkAccessStatusRequest + (*ReportNetworkAccessStatusResponse)(nil), // 38: silo.plugin.v1.ReportNetworkAccessStatusResponse + nil, // 39: silo.plugin.v1.ResolveCatalogImageURLsResponse.UrlsEntry + nil, // 40: silo.plugin.v1.CallPluginHTTPRequest.HeadersEntry + nil, // 41: silo.plugin.v1.CallPluginHTTPResponse.HeadersEntry + (*structpb.Struct)(nil), // 42: google.protobuf.Struct + (*CapabilityDescriptor)(nil), // 43: silo.plugin.v1.CapabilityDescriptor + (*NetworkAccessStatus)(nil), // 44: silo.plugin.v1.NetworkAccessStatus } var file_silo_plugin_v1_runtime_host_proto_depIdxs = []int32{ - 35, // 0: silo.plugin.v1.PublishEventRequest.payload:type_name -> google.protobuf.Struct - 35, // 1: silo.plugin.v1.PublishEventToRequest.payload:type_name -> google.protobuf.Struct - 35, // 2: silo.plugin.v1.PublishEventToInstallationRequest.payload:type_name -> google.protobuf.Struct - 10, // 3: silo.plugin.v1.ListLibrariesResponse.libraries:type_name -> silo.plugin.v1.Library - 13, // 4: silo.plugin.v1.CheckMediaPresenceResponse.present:type_name -> silo.plugin.v1.MediaPresence - 16, // 5: silo.plugin.v1.ListInstalledPluginsResponse.plugins:type_name -> silo.plugin.v1.InstalledPlugin - 36, // 6: silo.plugin.v1.InstalledPlugin.capabilities:type_name -> silo.plugin.v1.CapabilityDescriptor - 35, // 7: silo.plugin.v1.SetGlobalConfigEntryRequest.value:type_name -> google.protobuf.Struct - 21, // 8: silo.plugin.v1.ListLibraryMediaResponse.items:type_name -> silo.plugin.v1.CatalogMediaItem - 24, // 9: silo.plugin.v1.GetCatalogStatsResponse.media_type_counts:type_name -> silo.plugin.v1.CatalogTypeCount - 25, // 10: silo.plugin.v1.GetCatalogStatsResponse.library_counts:type_name -> silo.plugin.v1.CatalogLibraryCount - 32, // 11: silo.plugin.v1.ResolveCatalogImageURLsResponse.urls:type_name -> silo.plugin.v1.ResolveCatalogImageURLsResponse.UrlsEntry - 33, // 12: silo.plugin.v1.CallPluginHTTPRequest.headers:type_name -> silo.plugin.v1.CallPluginHTTPRequest.HeadersEntry - 35, // 13: silo.plugin.v1.CallPluginHTTPRequest.query:type_name -> google.protobuf.Struct - 34, // 14: silo.plugin.v1.CallPluginHTTPResponse.headers:type_name -> silo.plugin.v1.CallPluginHTTPResponse.HeadersEntry - 0, // 15: silo.plugin.v1.RuntimeHost.PublishEvent:input_type -> silo.plugin.v1.PublishEventRequest - 2, // 16: silo.plugin.v1.RuntimeHost.PublishEventTo:input_type -> silo.plugin.v1.PublishEventToRequest - 4, // 17: silo.plugin.v1.RuntimeHost.PublishEventToInstallation:input_type -> silo.plugin.v1.PublishEventToInstallationRequest - 6, // 18: silo.plugin.v1.RuntimeHost.GetHostInfo:input_type -> silo.plugin.v1.GetHostInfoRequest - 8, // 19: silo.plugin.v1.RuntimeHost.ListLibraries:input_type -> silo.plugin.v1.ListLibrariesRequest - 11, // 20: silo.plugin.v1.RuntimeHost.CheckMediaPresence:input_type -> silo.plugin.v1.CheckMediaPresenceRequest - 14, // 21: silo.plugin.v1.RuntimeHost.ListInstalledPlugins:input_type -> silo.plugin.v1.ListInstalledPluginsRequest - 17, // 22: silo.plugin.v1.RuntimeHost.SetGlobalConfigEntry:input_type -> silo.plugin.v1.SetGlobalConfigEntryRequest - 19, // 23: silo.plugin.v1.RuntimeHost.ListLibraryMedia:input_type -> silo.plugin.v1.ListLibraryMediaRequest - 22, // 24: silo.plugin.v1.RuntimeHost.GetCatalogStats:input_type -> silo.plugin.v1.GetCatalogStatsRequest - 26, // 25: silo.plugin.v1.RuntimeHost.ResolveCatalogImageURLs:input_type -> silo.plugin.v1.ResolveCatalogImageURLsRequest - 28, // 26: silo.plugin.v1.RuntimeHost.MintScopedStream:input_type -> silo.plugin.v1.MintScopedStreamRequest - 30, // 27: silo.plugin.v1.RuntimeHost.CallPluginHTTP:input_type -> silo.plugin.v1.CallPluginHTTPRequest - 1, // 28: silo.plugin.v1.RuntimeHost.PublishEvent:output_type -> silo.plugin.v1.PublishEventResponse - 3, // 29: silo.plugin.v1.RuntimeHost.PublishEventTo:output_type -> silo.plugin.v1.PublishEventToResponse - 5, // 30: silo.plugin.v1.RuntimeHost.PublishEventToInstallation:output_type -> silo.plugin.v1.PublishEventToInstallationResponse - 7, // 31: silo.plugin.v1.RuntimeHost.GetHostInfo:output_type -> silo.plugin.v1.GetHostInfoResponse - 9, // 32: silo.plugin.v1.RuntimeHost.ListLibraries:output_type -> silo.plugin.v1.ListLibrariesResponse - 12, // 33: silo.plugin.v1.RuntimeHost.CheckMediaPresence:output_type -> silo.plugin.v1.CheckMediaPresenceResponse - 15, // 34: silo.plugin.v1.RuntimeHost.ListInstalledPlugins:output_type -> silo.plugin.v1.ListInstalledPluginsResponse - 18, // 35: silo.plugin.v1.RuntimeHost.SetGlobalConfigEntry:output_type -> silo.plugin.v1.SetGlobalConfigEntryResponse - 20, // 36: silo.plugin.v1.RuntimeHost.ListLibraryMedia:output_type -> silo.plugin.v1.ListLibraryMediaResponse - 23, // 37: silo.plugin.v1.RuntimeHost.GetCatalogStats:output_type -> silo.plugin.v1.GetCatalogStatsResponse - 27, // 38: silo.plugin.v1.RuntimeHost.ResolveCatalogImageURLs:output_type -> silo.plugin.v1.ResolveCatalogImageURLsResponse - 29, // 39: silo.plugin.v1.RuntimeHost.MintScopedStream:output_type -> silo.plugin.v1.MintScopedStreamResponse - 31, // 40: silo.plugin.v1.RuntimeHost.CallPluginHTTP:output_type -> silo.plugin.v1.CallPluginHTTPResponse - 28, // [28:41] is the sub-list for method output_type - 15, // [15:28] is the sub-list for method input_type - 15, // [15:15] is the sub-list for extension type_name - 15, // [15:15] is the sub-list for extension extendee - 0, // [0:15] is the sub-list for field type_name + 42, // 0: silo.plugin.v1.PublishEventRequest.payload:type_name -> google.protobuf.Struct + 42, // 1: silo.plugin.v1.PublishEventToRequest.payload:type_name -> google.protobuf.Struct + 42, // 2: silo.plugin.v1.PublishEventToInstallationRequest.payload:type_name -> google.protobuf.Struct + 8, // 3: silo.plugin.v1.GetHostInfoResponse.listeners:type_name -> silo.plugin.v1.HostListener + 11, // 4: silo.plugin.v1.ListLibrariesResponse.libraries:type_name -> silo.plugin.v1.Library + 14, // 5: silo.plugin.v1.CheckMediaPresenceResponse.present:type_name -> silo.plugin.v1.MediaPresence + 17, // 6: silo.plugin.v1.ListInstalledPluginsResponse.plugins:type_name -> silo.plugin.v1.InstalledPlugin + 43, // 7: silo.plugin.v1.InstalledPlugin.capabilities:type_name -> silo.plugin.v1.CapabilityDescriptor + 42, // 8: silo.plugin.v1.SetGlobalConfigEntryRequest.value:type_name -> google.protobuf.Struct + 22, // 9: silo.plugin.v1.ListLibraryMediaResponse.items:type_name -> silo.plugin.v1.CatalogMediaItem + 25, // 10: silo.plugin.v1.GetCatalogStatsResponse.media_type_counts:type_name -> silo.plugin.v1.CatalogTypeCount + 26, // 11: silo.plugin.v1.GetCatalogStatsResponse.library_counts:type_name -> silo.plugin.v1.CatalogLibraryCount + 39, // 12: silo.plugin.v1.ResolveCatalogImageURLsResponse.urls:type_name -> silo.plugin.v1.ResolveCatalogImageURLsResponse.UrlsEntry + 40, // 13: silo.plugin.v1.CallPluginHTTPRequest.headers:type_name -> silo.plugin.v1.CallPluginHTTPRequest.HeadersEntry + 42, // 14: silo.plugin.v1.CallPluginHTTPRequest.query:type_name -> google.protobuf.Struct + 41, // 15: silo.plugin.v1.CallPluginHTTPResponse.headers:type_name -> silo.plugin.v1.CallPluginHTTPResponse.HeadersEntry + 44, // 16: silo.plugin.v1.ReportNetworkAccessStatusRequest.status:type_name -> silo.plugin.v1.NetworkAccessStatus + 0, // 17: silo.plugin.v1.RuntimeHost.PublishEvent:input_type -> silo.plugin.v1.PublishEventRequest + 2, // 18: silo.plugin.v1.RuntimeHost.PublishEventTo:input_type -> silo.plugin.v1.PublishEventToRequest + 4, // 19: silo.plugin.v1.RuntimeHost.PublishEventToInstallation:input_type -> silo.plugin.v1.PublishEventToInstallationRequest + 6, // 20: silo.plugin.v1.RuntimeHost.GetHostInfo:input_type -> silo.plugin.v1.GetHostInfoRequest + 9, // 21: silo.plugin.v1.RuntimeHost.ListLibraries:input_type -> silo.plugin.v1.ListLibrariesRequest + 12, // 22: silo.plugin.v1.RuntimeHost.CheckMediaPresence:input_type -> silo.plugin.v1.CheckMediaPresenceRequest + 15, // 23: silo.plugin.v1.RuntimeHost.ListInstalledPlugins:input_type -> silo.plugin.v1.ListInstalledPluginsRequest + 18, // 24: silo.plugin.v1.RuntimeHost.SetGlobalConfigEntry:input_type -> silo.plugin.v1.SetGlobalConfigEntryRequest + 20, // 25: silo.plugin.v1.RuntimeHost.ListLibraryMedia:input_type -> silo.plugin.v1.ListLibraryMediaRequest + 23, // 26: silo.plugin.v1.RuntimeHost.GetCatalogStats:input_type -> silo.plugin.v1.GetCatalogStatsRequest + 27, // 27: silo.plugin.v1.RuntimeHost.ResolveCatalogImageURLs:input_type -> silo.plugin.v1.ResolveCatalogImageURLsRequest + 29, // 28: silo.plugin.v1.RuntimeHost.MintScopedStream:input_type -> silo.plugin.v1.MintScopedStreamRequest + 31, // 29: silo.plugin.v1.RuntimeHost.CallPluginHTTP:input_type -> silo.plugin.v1.CallPluginHTTPRequest + 33, // 30: silo.plugin.v1.RuntimeHost.ReadInstanceState:input_type -> silo.plugin.v1.ReadInstanceStateRequest + 35, // 31: silo.plugin.v1.RuntimeHost.WriteInstanceState:input_type -> silo.plugin.v1.WriteInstanceStateRequest + 37, // 32: silo.plugin.v1.RuntimeHost.ReportNetworkAccessStatus:input_type -> silo.plugin.v1.ReportNetworkAccessStatusRequest + 1, // 33: silo.plugin.v1.RuntimeHost.PublishEvent:output_type -> silo.plugin.v1.PublishEventResponse + 3, // 34: silo.plugin.v1.RuntimeHost.PublishEventTo:output_type -> silo.plugin.v1.PublishEventToResponse + 5, // 35: silo.plugin.v1.RuntimeHost.PublishEventToInstallation:output_type -> silo.plugin.v1.PublishEventToInstallationResponse + 7, // 36: silo.plugin.v1.RuntimeHost.GetHostInfo:output_type -> silo.plugin.v1.GetHostInfoResponse + 10, // 37: silo.plugin.v1.RuntimeHost.ListLibraries:output_type -> silo.plugin.v1.ListLibrariesResponse + 13, // 38: silo.plugin.v1.RuntimeHost.CheckMediaPresence:output_type -> silo.plugin.v1.CheckMediaPresenceResponse + 16, // 39: silo.plugin.v1.RuntimeHost.ListInstalledPlugins:output_type -> silo.plugin.v1.ListInstalledPluginsResponse + 19, // 40: silo.plugin.v1.RuntimeHost.SetGlobalConfigEntry:output_type -> silo.plugin.v1.SetGlobalConfigEntryResponse + 21, // 41: silo.plugin.v1.RuntimeHost.ListLibraryMedia:output_type -> silo.plugin.v1.ListLibraryMediaResponse + 24, // 42: silo.plugin.v1.RuntimeHost.GetCatalogStats:output_type -> silo.plugin.v1.GetCatalogStatsResponse + 28, // 43: silo.plugin.v1.RuntimeHost.ResolveCatalogImageURLs:output_type -> silo.plugin.v1.ResolveCatalogImageURLsResponse + 30, // 44: silo.plugin.v1.RuntimeHost.MintScopedStream:output_type -> silo.plugin.v1.MintScopedStreamResponse + 32, // 45: silo.plugin.v1.RuntimeHost.CallPluginHTTP:output_type -> silo.plugin.v1.CallPluginHTTPResponse + 34, // 46: silo.plugin.v1.RuntimeHost.ReadInstanceState:output_type -> silo.plugin.v1.ReadInstanceStateResponse + 36, // 47: silo.plugin.v1.RuntimeHost.WriteInstanceState:output_type -> silo.plugin.v1.WriteInstanceStateResponse + 38, // 48: silo.plugin.v1.RuntimeHost.ReportNetworkAccessStatus:output_type -> silo.plugin.v1.ReportNetworkAccessStatusResponse + 33, // [33:49] is the sub-list for method output_type + 17, // [17:33] is the sub-list for method input_type + 17, // [17:17] is the sub-list for extension type_name + 17, // [17:17] is the sub-list for extension extendee + 0, // [0:17] is the sub-list for field type_name } func init() { file_silo_plugin_v1_runtime_host_proto_init() } @@ -2277,13 +2701,14 @@ func file_silo_plugin_v1_runtime_host_proto_init() { return } file_silo_plugin_v1_common_proto_init() + file_silo_plugin_v1_network_access_provider_proto_init() type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_silo_plugin_v1_runtime_host_proto_rawDesc), len(file_silo_plugin_v1_runtime_host_proto_rawDesc)), NumEnums: 0, - NumMessages: 35, + NumMessages: 42, NumExtensions: 0, NumServices: 1, }, diff --git a/pkg/pluginproto/silo/plugin/v1/runtime_host_grpc.pb.go b/pkg/pluginproto/silo/plugin/v1/runtime_host_grpc.pb.go index 77b0fa6..ee216c3 100644 --- a/pkg/pluginproto/silo/plugin/v1/runtime_host_grpc.pb.go +++ b/pkg/pluginproto/silo/plugin/v1/runtime_host_grpc.pb.go @@ -32,6 +32,9 @@ const ( RuntimeHost_ResolveCatalogImageURLs_FullMethodName = "/silo.plugin.v1.RuntimeHost/ResolveCatalogImageURLs" RuntimeHost_MintScopedStream_FullMethodName = "/silo.plugin.v1.RuntimeHost/MintScopedStream" RuntimeHost_CallPluginHTTP_FullMethodName = "/silo.plugin.v1.RuntimeHost/CallPluginHTTP" + RuntimeHost_ReadInstanceState_FullMethodName = "/silo.plugin.v1.RuntimeHost/ReadInstanceState" + RuntimeHost_WriteInstanceState_FullMethodName = "/silo.plugin.v1.RuntimeHost/WriteInstanceState" + RuntimeHost_ReportNetworkAccessStatus_FullMethodName = "/silo.plugin.v1.RuntimeHost/ReportNetworkAccessStatus" ) // RuntimeHostClient is the client API for RuntimeHost service. @@ -83,6 +86,18 @@ type RuntimeHostClient interface { // through the host control plane. Calls are treated as authenticated // service-to-service traffic, but never as admin traffic. CallPluginHTTP(ctx context.Context, in *CallPluginHTTPRequest, opts ...grpc.CallOption) (*CallPluginHTTPResponse, error) + // ReadInstanceState reads one key of the calling plugin instance's private + // state. The scope (installation plus host) is derived by the host and never + // supplied by the plugin. See docs/network-access-provider.md. + ReadInstanceState(ctx context.Context, in *ReadInstanceStateRequest, opts ...grpc.CallOption) (*ReadInstanceStateResponse, error) + // WriteInstanceState writes one key of the calling plugin instance's private + // state. The host stores values encrypted and never returns them through + // any API. Limits: key <= 256 bytes, value <= 256 KiB, <= 256 keys per scope. + WriteInstanceState(ctx context.Context, in *WriteInstanceStateRequest, opts ...grpc.CallOption) (*WriteInstanceStateResponse, error) + // ReportNetworkAccessStatus pushes a network_access_provider.v1 status + // change to the host so it does not have to poll. Plugins call it on every + // state transition; the host may still call GetStatus on demand. + ReportNetworkAccessStatus(ctx context.Context, in *ReportNetworkAccessStatusRequest, opts ...grpc.CallOption) (*ReportNetworkAccessStatusResponse, error) } type runtimeHostClient struct { @@ -223,6 +238,36 @@ func (c *runtimeHostClient) CallPluginHTTP(ctx context.Context, in *CallPluginHT return out, nil } +func (c *runtimeHostClient) ReadInstanceState(ctx context.Context, in *ReadInstanceStateRequest, opts ...grpc.CallOption) (*ReadInstanceStateResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ReadInstanceStateResponse) + err := c.cc.Invoke(ctx, RuntimeHost_ReadInstanceState_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *runtimeHostClient) WriteInstanceState(ctx context.Context, in *WriteInstanceStateRequest, opts ...grpc.CallOption) (*WriteInstanceStateResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(WriteInstanceStateResponse) + err := c.cc.Invoke(ctx, RuntimeHost_WriteInstanceState_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *runtimeHostClient) ReportNetworkAccessStatus(ctx context.Context, in *ReportNetworkAccessStatusRequest, opts ...grpc.CallOption) (*ReportNetworkAccessStatusResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ReportNetworkAccessStatusResponse) + err := c.cc.Invoke(ctx, RuntimeHost_ReportNetworkAccessStatus_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + // RuntimeHostServer is the server API for RuntimeHost service. // All implementations should embed UnimplementedRuntimeHostServer // for forward compatibility. @@ -272,6 +317,18 @@ type RuntimeHostServer interface { // through the host control plane. Calls are treated as authenticated // service-to-service traffic, but never as admin traffic. CallPluginHTTP(context.Context, *CallPluginHTTPRequest) (*CallPluginHTTPResponse, error) + // ReadInstanceState reads one key of the calling plugin instance's private + // state. The scope (installation plus host) is derived by the host and never + // supplied by the plugin. See docs/network-access-provider.md. + ReadInstanceState(context.Context, *ReadInstanceStateRequest) (*ReadInstanceStateResponse, error) + // WriteInstanceState writes one key of the calling plugin instance's private + // state. The host stores values encrypted and never returns them through + // any API. Limits: key <= 256 bytes, value <= 256 KiB, <= 256 keys per scope. + WriteInstanceState(context.Context, *WriteInstanceStateRequest) (*WriteInstanceStateResponse, error) + // ReportNetworkAccessStatus pushes a network_access_provider.v1 status + // change to the host so it does not have to poll. Plugins call it on every + // state transition; the host may still call GetStatus on demand. + ReportNetworkAccessStatus(context.Context, *ReportNetworkAccessStatusRequest) (*ReportNetworkAccessStatusResponse, error) } // UnimplementedRuntimeHostServer should be embedded to have @@ -320,6 +377,15 @@ func (UnimplementedRuntimeHostServer) MintScopedStream(context.Context, *MintSco func (UnimplementedRuntimeHostServer) CallPluginHTTP(context.Context, *CallPluginHTTPRequest) (*CallPluginHTTPResponse, error) { return nil, status.Error(codes.Unimplemented, "method CallPluginHTTP not implemented") } +func (UnimplementedRuntimeHostServer) ReadInstanceState(context.Context, *ReadInstanceStateRequest) (*ReadInstanceStateResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ReadInstanceState not implemented") +} +func (UnimplementedRuntimeHostServer) WriteInstanceState(context.Context, *WriteInstanceStateRequest) (*WriteInstanceStateResponse, error) { + return nil, status.Error(codes.Unimplemented, "method WriteInstanceState not implemented") +} +func (UnimplementedRuntimeHostServer) ReportNetworkAccessStatus(context.Context, *ReportNetworkAccessStatusRequest) (*ReportNetworkAccessStatusResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ReportNetworkAccessStatus not implemented") +} func (UnimplementedRuntimeHostServer) testEmbeddedByValue() {} // UnsafeRuntimeHostServer may be embedded to opt out of forward compatibility for this service. @@ -574,6 +640,60 @@ func _RuntimeHost_CallPluginHTTP_Handler(srv interface{}, ctx context.Context, d return interceptor(ctx, in, info, handler) } +func _RuntimeHost_ReadInstanceState_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ReadInstanceStateRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RuntimeHostServer).ReadInstanceState(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: RuntimeHost_ReadInstanceState_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RuntimeHostServer).ReadInstanceState(ctx, req.(*ReadInstanceStateRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _RuntimeHost_WriteInstanceState_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(WriteInstanceStateRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RuntimeHostServer).WriteInstanceState(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: RuntimeHost_WriteInstanceState_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RuntimeHostServer).WriteInstanceState(ctx, req.(*WriteInstanceStateRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _RuntimeHost_ReportNetworkAccessStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ReportNetworkAccessStatusRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RuntimeHostServer).ReportNetworkAccessStatus(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: RuntimeHost_ReportNetworkAccessStatus_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RuntimeHostServer).ReportNetworkAccessStatus(ctx, req.(*ReportNetworkAccessStatusRequest)) + } + return interceptor(ctx, in, info, handler) +} + // RuntimeHost_ServiceDesc is the grpc.ServiceDesc for RuntimeHost service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -633,6 +753,18 @@ var RuntimeHost_ServiceDesc = grpc.ServiceDesc{ MethodName: "CallPluginHTTP", Handler: _RuntimeHost_CallPluginHTTP_Handler, }, + { + MethodName: "ReadInstanceState", + Handler: _RuntimeHost_ReadInstanceState_Handler, + }, + { + MethodName: "WriteInstanceState", + Handler: _RuntimeHost_WriteInstanceState_Handler, + }, + { + MethodName: "ReportNetworkAccessStatus", + Handler: _RuntimeHost_ReportNetworkAccessStatus_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "silo/plugin/v1/runtime_host.proto", diff --git a/pkg/pluginsdk/capability/capability.go b/pkg/pluginsdk/capability/capability.go index db5e1af..8eb4698 100644 --- a/pkg/pluginsdk/capability/capability.go +++ b/pkg/pluginsdk/capability/capability.go @@ -15,6 +15,9 @@ const ( EbookBackend = "ebook_backend.v1" ScanSource = "scan_source.v1" WatchSyncProvider = "watch_sync_provider.v1" + // NetworkAccessProvider marks a resident plugin that gives the deployment + // an overlay-network identity (Tailscale, NetBird, ...). Added in v0.16.0. + NetworkAccessProvider = "network_access_provider.v1" ) // KnownTypes lists every capability type recognized by this SDK version. @@ -32,4 +35,5 @@ var KnownTypes = []string{ EbookBackend, ScanSource, WatchSyncProvider, + NetworkAccessProvider, } diff --git a/pkg/pluginsdk/capability/capability_test.go b/pkg/pluginsdk/capability/capability_test.go index bd13191..1bd6a64 100644 --- a/pkg/pluginsdk/capability/capability_test.go +++ b/pkg/pluginsdk/capability/capability_test.go @@ -49,3 +49,19 @@ func TestImageResolverIsKnownType(t *testing.T) { t.Fatalf("ImageResolver (%q) missing from KnownTypes %v", ImageResolver, KnownTypes) } } + +func TestNetworkAccessProviderIsKnownType(t *testing.T) { + if NetworkAccessProvider != "network_access_provider.v1" { + t.Fatalf("NetworkAccessProvider const = %q, want %q", NetworkAccessProvider, "network_access_provider.v1") + } + found := false + for _, k := range KnownTypes { + if k == NetworkAccessProvider { + found = true + break + } + } + if !found { + t.Fatalf("NetworkAccessProvider (%q) missing from KnownTypes %v", NetworkAccessProvider, KnownTypes) + } +} diff --git a/pkg/pluginsdk/manifest/manifest.go b/pkg/pluginsdk/manifest/manifest.go index f6de2c0..b826c83 100644 --- a/pkg/pluginsdk/manifest/manifest.go +++ b/pkg/pluginsdk/manifest/manifest.go @@ -113,6 +113,9 @@ func Validate(manifest *pluginv1.PluginManifest) error { if err := validateWatchSyncCapability(capability); err != nil { return err } + if err := validateNetworkAccessCapability(capability); err != nil { + return err + } } for _, schema := range manifest.GlobalConfigSchema { if err := validateConfigSchema(schema); err != nil { @@ -186,6 +189,23 @@ func validateWatchSyncCapability(descriptor *pluginv1.CapabilityDescriptor) erro return nil } +func validateNetworkAccessCapability(descriptor *pluginv1.CapabilityDescriptor) error { + networkAccess := descriptor.GetNetworkAccessProvider() + if descriptor.GetType() != capability.NetworkAccessProvider { + if networkAccess != nil { + return fmt.Errorf("plugin capability %q: network_access_provider descriptor requires type %q", descriptor.GetId(), capability.NetworkAccessProvider) + } + return nil + } + if networkAccess == nil { + return fmt.Errorf("plugin capability %q: network_access_provider descriptor is required", descriptor.GetId()) + } + if !watchSyncSlugPattern.MatchString(networkAccess.GetProvider()) { + return fmt.Errorf("plugin capability %q: network access provider must be a path-safe lowercase slug", descriptor.GetId()) + } + return nil +} + // ValidateCatalogPresentation applies the stricter presentation contract used // by curated catalogs. The general manifest validator keeps presentation // optional so older and directly uploaded plugins remain compatible. diff --git a/pkg/pluginsdk/manifest/network_access_provider_test.go b/pkg/pluginsdk/manifest/network_access_provider_test.go new file mode 100644 index 0000000..b3f2594 --- /dev/null +++ b/pkg/pluginsdk/manifest/network_access_provider_test.go @@ -0,0 +1,62 @@ +package manifest_test + +import ( + "testing" + + pluginv1 "github.com/Silo-Server/silo-plugin-sdk/pkg/pluginproto/silo/plugin/v1" + publicmanifest "github.com/Silo-Server/silo-plugin-sdk/pkg/pluginsdk/manifest" +) + +func TestLoadNetworkAccessProvider(t *testing.T) { + raw := []byte(`{ + "plugin_id":"silo.tailscale", "version":"1.0.0", "silo_api_version":"v1", + "capabilities":[{ + "type":"network_access_provider.v1", "id":"tailscale", "display_name":"Tailscale", + "network_access_provider":{"provider":"tailscale","display_name":"Tailscale"} + }] + }`) + manifest, err := publicmanifest.Load(raw) + if err != nil { + t.Fatal(err) + } + descriptor := manifest.GetCapabilities()[0].GetNetworkAccessProvider() + if descriptor.GetProvider() != "tailscale" || descriptor.GetDisplayName() != "Tailscale" { + t.Fatalf("network access descriptor = %#v", descriptor) + } +} + +func TestValidateNetworkAccessProviderRejectsMissingDescriptor(t *testing.T) { + manifest := &pluginv1.PluginManifest{ + PluginId: "silo.invalid", Version: "1.0.0", + Capabilities: []*pluginv1.CapabilityDescriptor{{Type: "network_access_provider.v1", Id: "overlay"}}, + } + if err := publicmanifest.Validate(manifest); err == nil { + t.Fatal("expected missing network access descriptor to fail") + } +} + +func TestValidateNetworkAccessProviderRejectsBadSlug(t *testing.T) { + manifest := &pluginv1.PluginManifest{ + PluginId: "silo.invalid", Version: "1.0.0", + Capabilities: []*pluginv1.CapabilityDescriptor{{ + Type: "network_access_provider.v1", Id: "overlay", + NetworkAccessProvider: &pluginv1.NetworkAccessProviderDescriptor{Provider: "Tail Scale"}, + }}, + } + if err := publicmanifest.Validate(manifest); err == nil { + t.Fatal("expected provider slug with spaces and capitals to fail") + } +} + +func TestValidateNetworkAccessProviderRejectsDescriptorOnOtherType(t *testing.T) { + manifest := &pluginv1.PluginManifest{ + PluginId: "silo.invalid", Version: "1.0.0", + Capabilities: []*pluginv1.CapabilityDescriptor{{ + Type: "scheduled_task.v1", Id: "task", + NetworkAccessProvider: &pluginv1.NetworkAccessProviderDescriptor{Provider: "tailscale"}, + }}, + } + if err := publicmanifest.Validate(manifest); err == nil { + t.Fatal("expected network access descriptor on a scheduled task to fail") + } +} diff --git a/pkg/pluginsdk/runtime/capability_servers_compat_test.go b/pkg/pluginsdk/runtime/capability_servers_compat_test.go index 57d193f..706fe09 100644 --- a/pkg/pluginsdk/runtime/capability_servers_compat_test.go +++ b/pkg/pluginsdk/runtime/capability_servers_compat_test.go @@ -1,7 +1,10 @@ package runtime -// Compile-time guard for the released v0.12 unkeyed composite literal shape. -// New optional servers belong behind additive registration APIs, not here. +// Compile-time guard for the CapabilityServers unkeyed composite literal +// shape. v0.12 through v0.15 had twelve fields; v0.16.0 appended +// NetworkAccessProvider as the thirteenth. Plugins should use keyed literals; +// any further optional servers belong behind additive registration APIs +// (see ServeManifestOption), not here. var _ = CapabilityServers{ nil, nil, @@ -15,4 +18,5 @@ var _ = CapabilityServers{ nil, nil, nil, + nil, } diff --git a/pkg/pluginsdk/runtime/network_access_provider_test.go b/pkg/pluginsdk/runtime/network_access_provider_test.go new file mode 100644 index 0000000..c114071 --- /dev/null +++ b/pkg/pluginsdk/runtime/network_access_provider_test.go @@ -0,0 +1,44 @@ +package runtime_test + +import ( + "testing" + + pluginv1 "github.com/Silo-Server/silo-plugin-sdk/pkg/pluginproto/silo/plugin/v1" + runtime "github.com/Silo-Server/silo-plugin-sdk/pkg/pluginsdk/runtime" + "github.com/hashicorp/go-plugin" + "google.golang.org/grpc" +) + +type stubNetworkAccessProvider struct { + pluginv1.UnimplementedNetworkAccessProviderServer +} + +func TestGRPCServerRegistersNetworkAccessProvider(t *testing.T) { + plugins := runtime.DefaultPluginSet(runtime.CapabilityServers{ + Runtime: stubRuntime{}, + NetworkAccessProvider: stubNetworkAccessProvider{}, + }) + p, ok := plugins[runtime.PluginSetName].(plugin.GRPCPlugin) + if !ok { + t.Fatalf("plugin type = %T, want plugin.GRPCPlugin", plugins[runtime.PluginSetName]) + } + srv := grpc.NewServer() + if err := p.GRPCServer(nil, srv); err != nil { + t.Fatalf("GRPCServer = %v, want nil", err) + } + if _, ok := srv.GetServiceInfo()["silo.plugin.v1.NetworkAccessProvider"]; !ok { + t.Fatalf("NetworkAccessProvider service not registered; got %v", srv.GetServiceInfo()) + } +} + +func TestGRPCServerSkipsNilNetworkAccessProvider(t *testing.T) { + plugins := runtime.DefaultPluginSet(runtime.CapabilityServers{Runtime: stubRuntime{}}) + p := plugins[runtime.PluginSetName].(plugin.GRPCPlugin) + srv := grpc.NewServer() + if err := p.GRPCServer(nil, srv); err != nil { + t.Fatalf("GRPCServer = %v, want nil", err) + } + if _, ok := srv.GetServiceInfo()["silo.plugin.v1.NetworkAccessProvider"]; ok { + t.Fatalf("NetworkAccessProvider registered without a server") + } +} diff --git a/pkg/pluginsdk/runtime/runtime.go b/pkg/pluginsdk/runtime/runtime.go index 09d18b8..21ca160 100644 --- a/pkg/pluginsdk/runtime/runtime.go +++ b/pkg/pluginsdk/runtime/runtime.go @@ -35,6 +35,10 @@ type CapabilityServers struct { AuthProvider pluginv1.AuthProviderServer HttpRoutes pluginv1.HttpRoutesServer WatchSyncProvider pluginv1.WatchSyncProviderServer + // NetworkAccessProvider was added in v0.16.0. Plugins declaring + // network_access_provider.v1 register it here; the host treats them as + // resident and starts them at boot. + NetworkAccessProvider pluginv1.NetworkAccessProviderServer } // Client wraps the gRPC connection to a plugin and provides typed accessors @@ -155,6 +159,10 @@ func (c *Client) WatchSyncDeviceAuthorization() pluginv1.WatchSyncDeviceAuthoriz return pluginv1.NewWatchSyncDeviceAuthorizationServiceClient(c.conn) } +func (c *Client) NetworkAccessProvider() pluginv1.NetworkAccessProviderClient { + return pluginv1.NewNetworkAccessProviderClient(c.conn) +} + type GRPCPlugin struct { plugin.Plugin Servers CapabilityServers @@ -216,6 +224,9 @@ func (p *GRPCPlugin) GRPCServer(broker *plugin.GRPCBroker, server *grpc.Server) if p.Servers.WatchSyncProvider != nil { pluginv1.RegisterWatchSyncProviderServer(server, p.Servers.WatchSyncProvider) } + if p.Servers.NetworkAccessProvider != nil { + pluginv1.RegisterNetworkAccessProviderServer(server, p.Servers.NetworkAccessProvider) + } return nil } diff --git a/pkg/pluginsdk/runtimehost/client_test.go b/pkg/pluginsdk/runtimehost/client_test.go index 47a92ce..caa4c24 100644 --- a/pkg/pluginsdk/runtimehost/client_test.go +++ b/pkg/pluginsdk/runtimehost/client_test.go @@ -132,6 +132,11 @@ func (f *fakeServer) CallPluginHTTP(_ context.Context, r *pluginv1.CallPluginHTT } func dial(t *testing.T, srv *fakeServer) *grpc.ClientConn { + t.Helper() + return dialServer(t, srv) +} + +func dialServer(t *testing.T, srv pluginv1.RuntimeHostServer) *grpc.ClientConn { t.Helper() lis := bufconn.Listen(1024 * 1024) g := grpc.NewServer() @@ -231,6 +236,47 @@ func TestGetHostInfo_MapsResponse(t *testing.T) { if got.PublicBaseURL != "https://silo.example" || got.PluginProxyBaseURL == "" { t.Fatalf("bad host info: %+v", got) } + if got.HostRole != "" || got.NodeID != 0 || got.IngressToken != "" || got.Listeners != nil { + t.Fatalf("pre-v0.16 host must map to zero values: %+v", got) + } + if got.Listener(runtimehost.ListenerAPI) != nil { + t.Fatal("Listener(api) on a host without listeners must be nil") + } +} + +func TestGetHostInfo_MapsNetworkAccessFields(t *testing.T) { + srv := &fakeServer{ + hostInfoResp: &pluginv1.GetHostInfoResponse{ + PublicBaseUrl: "https://silo.example", + HostRole: "proxy", + HostName: "proxy-1", + NodeId: 3, + IngressToken: "tok", + Listeners: []*pluginv1.HostListener{ + {Name: "api", Address: "127.0.0.1:8090", DefaultPort: 443}, + }, + }, + } + conn := dial(t, srv) + c := runtimehost.NewClient(conn) + + got, err := c.GetHostInfo(context.Background()) + if err != nil { + t.Fatalf("GetHostInfo: %v", err) + } + if got.HostRole != runtimehost.HostRoleProxy || got.HostName != "proxy-1" || got.NodeID != 3 || got.IngressToken != "tok" { + t.Fatalf("bad host info: %+v", got) + } + want := []runtimehost.HostListener{{Name: "api", Address: "127.0.0.1:8090", DefaultPort: 443}} + if !reflect.DeepEqual(got.Listeners, want) { + t.Fatalf("listeners = %+v, want %+v", got.Listeners, want) + } + if l := got.Listener(runtimehost.ListenerAPI); l == nil || l.Address != "127.0.0.1:8090" { + t.Fatalf("Listener(api) = %+v", l) + } + if got.Listener(runtimehost.ListenerJellyfin) != nil { + t.Fatal("Listener(jellyfin) should be nil when not reported") + } } func TestListLibraries_PassesUserID(t *testing.T) { diff --git a/pkg/pluginsdk/runtimehost/host_info.go b/pkg/pluginsdk/runtimehost/host_info.go index 71e0105..e35a99c 100644 --- a/pkg/pluginsdk/runtimehost/host_info.go +++ b/pkg/pluginsdk/runtimehost/host_info.go @@ -6,10 +6,63 @@ import ( pluginv1 "github.com/Silo-Server/silo-plugin-sdk/pkg/pluginproto/silo/plugin/v1" ) +// Host roles reported in HostInfo.HostRole. +const ( + HostRoleAPI = "api" + HostRoleProxy = "proxy" +) + +// Listener names reported in HostListener.Name. +const ( + ListenerAPI = "api" + ListenerJellyfin = "jellyfin" + ListenerABS = "abs" +) + +// HostInfo is the typed view of RuntimeHost.GetHostInfo. type HostInfo struct { PublicBaseURL string InternalBaseURL string PluginProxyBaseURL string + + // HostRole is "api" or "proxy". Empty on hosts that predate v0.16.0. + HostRole string + // HostName is the node name on proxies or the server name on the api host. + HostName string + // NodeID is stream_nodes.id on proxies and 0 on the api host. + NodeID int64 + // IngressToken is the per-process-start secret a network access provider + // stamps on proxied requests as X-Silo-Ingress-Token. Never log or persist + // it; call GetHostInfo again after a restart. + IngressToken string + // Listeners are the local listeners a network access provider should + // expose. Always contains "api" on hosts that implement v0.16.0. + Listeners []HostListener +} + +// HostListener is one local listener the host asks a network access provider +// to expose on the overlay network. +type HostListener struct { + // Name is "api", "jellyfin", or "abs". + Name string + // Address is the loopback dial address, host:port. + Address string + // DefaultPort is the port the plugin should expose the listener on; zero + // means provider default. + DefaultPort int +} + +// Listener returns the named listener, or nil when the host did not report it. +func (h *HostInfo) Listener(name string) *HostListener { + if h == nil { + return nil + } + for i := range h.Listeners { + if h.Listeners[i].Name == name { + return &h.Listeners[i] + } + } + return nil } func (c *Client) GetHostInfo(ctx context.Context) (*HostInfo, error) { @@ -17,9 +70,24 @@ func (c *Client) GetHostInfo(ctx context.Context) (*HostInfo, error) { if err != nil { return nil, err } - return &HostInfo{ + info := &HostInfo{ PublicBaseURL: resp.GetPublicBaseUrl(), InternalBaseURL: resp.GetInternalBaseUrl(), PluginProxyBaseURL: resp.GetPluginProxyBaseUrl(), - }, nil + HostRole: resp.GetHostRole(), + HostName: resp.GetHostName(), + NodeID: resp.GetNodeId(), + IngressToken: resp.GetIngressToken(), + } + if listeners := resp.GetListeners(); len(listeners) > 0 { + info.Listeners = make([]HostListener, 0, len(listeners)) + for _, l := range listeners { + info.Listeners = append(info.Listeners, HostListener{ + Name: l.GetName(), + Address: l.GetAddress(), + DefaultPort: int(l.GetDefaultPort()), + }) + } + } + return info, nil } diff --git a/pkg/pluginsdk/runtimehost/instance_state.go b/pkg/pluginsdk/runtimehost/instance_state.go new file mode 100644 index 0000000..c881409 --- /dev/null +++ b/pkg/pluginsdk/runtimehost/instance_state.go @@ -0,0 +1,130 @@ +package runtimehost + +import ( + "context" + "errors" + "fmt" + "time" + + pluginv1 "github.com/Silo-Server/silo-plugin-sdk/pkg/pluginproto/silo/plugin/v1" +) + +// Instance state limits enforced by the host. The client checks them before +// the round trip so a plugin gets a clear error instead of a gRPC status. +const ( + MaxInstanceStateKeyBytes = 256 + MaxInstanceStateValueBytes = 256 << 10 + MaxInstanceStateKeys = 256 +) + +// ErrStateNotExist is returned by ReadState when no value is stored under the +// key. It plays the role of tailscale's ipn.ErrStateNotExist: callers compare +// with errors.Is. +var ErrStateNotExist = errors.New("runtimehost: instance state key does not exist") + +// DefaultInstanceStateTimeout bounds each InstanceStateStore round trip when +// the caller does not supply a context (ipn.StateStore has none). +const DefaultInstanceStateTimeout = 30 * time.Second + +// ReadInstanceState reads one key of the calling plugin instance's private +// state. It returns (nil, false, nil) when the key is absent. +func (c *Client) ReadInstanceState(ctx context.Context, key string) ([]byte, bool, error) { + if err := validateInstanceStateKey(key); err != nil { + return nil, false, err + } + resp, err := c.rpc.ReadInstanceState(ctx, &pluginv1.ReadInstanceStateRequest{Key: key}) + if err != nil { + return nil, false, err + } + if !resp.GetFound() { + return nil, false, nil + } + return append([]byte(nil), resp.GetValue()...), true, nil +} + +// WriteInstanceState stores value under key in the calling plugin instance's +// private state. The host encrypts the value and never returns it through any +// API. +func (c *Client) WriteInstanceState(ctx context.Context, key string, value []byte) error { + if err := validateInstanceStateKey(key); err != nil { + return err + } + if len(value) > MaxInstanceStateValueBytes { + return fmt.Errorf("runtimehost: instance state value for %q is %d bytes, limit %d", key, len(value), MaxInstanceStateValueBytes) + } + _, err := c.rpc.WriteInstanceState(ctx, &pluginv1.WriteInstanceStateRequest{Key: key, Value: value}) + return err +} + +func validateInstanceStateKey(key string) error { + if key == "" { + return fmt.Errorf("runtimehost: instance state key is required") + } + if len(key) > MaxInstanceStateKeyBytes { + return fmt.Errorf("runtimehost: instance state key is %d bytes, limit %d", len(key), MaxInstanceStateKeyBytes) + } + return nil +} + +// StateStore is the two-method shape of tailscale's ipn.StateStore, declared +// here so the SDK does not depend on tailscale. ReadState returns +// ErrStateNotExist for an absent key. +// +// ipn.StateStore takes ipn.StateKey (a named string type) rather than string, +// so Go will not accept an InstanceStateStore as an ipn.StateStore directly. +// A tsnet plugin wraps it with a few lines; see docs/network-access-provider.md. +type StateStore interface { + ReadState(key string) ([]byte, error) + WriteState(key string, value []byte) error +} + +// InstanceStateStore implements StateStore over the ReadInstanceState and +// WriteInstanceState RPCs. Every value lives in the host's encrypted +// per-instance scope (installation plus host), so each proxy node keeps its +// own node key without the plugin managing files. +type InstanceStateStore struct { + client *Client + timeout time.Duration +} + +// InstanceStateStore returns a store that scopes every round trip with +// DefaultInstanceStateTimeout. +func (c *Client) InstanceStateStore() *InstanceStateStore { + return &InstanceStateStore{client: c, timeout: DefaultInstanceStateTimeout} +} + +// WithTimeout returns a copy of the store using the given per-call timeout. +// A non-positive timeout disables the deadline. +func (s *InstanceStateStore) WithTimeout(timeout time.Duration) *InstanceStateStore { + return &InstanceStateStore{client: s.client, timeout: timeout} +} + +// ReadState returns the value stored under key or ErrStateNotExist. +func (s *InstanceStateStore) ReadState(key string) ([]byte, error) { + ctx, cancel := s.context() + defer cancel() + value, found, err := s.client.ReadInstanceState(ctx, key) + if err != nil { + return nil, err + } + if !found { + return nil, ErrStateNotExist + } + return value, nil +} + +// WriteState stores value under key. +func (s *InstanceStateStore) WriteState(key string, value []byte) error { + ctx, cancel := s.context() + defer cancel() + return s.client.WriteInstanceState(ctx, key, value) +} + +var _ StateStore = (*InstanceStateStore)(nil) + +func (s *InstanceStateStore) context() (context.Context, context.CancelFunc) { + if s.timeout <= 0 { + return context.WithCancel(context.Background()) + } + return context.WithTimeout(context.Background(), s.timeout) +} diff --git a/pkg/pluginsdk/runtimehost/instance_state_test.go b/pkg/pluginsdk/runtimehost/instance_state_test.go new file mode 100644 index 0000000..d30b2e6 --- /dev/null +++ b/pkg/pluginsdk/runtimehost/instance_state_test.go @@ -0,0 +1,140 @@ +package runtimehost_test + +import ( + "bytes" + "context" + "errors" + "strings" + "testing" + + pluginv1 "github.com/Silo-Server/silo-plugin-sdk/pkg/pluginproto/silo/plugin/v1" + "github.com/Silo-Server/silo-plugin-sdk/pkg/pluginsdk/runtimehost" +) + +type instanceStateServer struct { + pluginv1.UnimplementedRuntimeHostServer + values map[string][]byte + statuses []*pluginv1.NetworkAccessStatus +} + +func newInstanceStateServer() *instanceStateServer { + return &instanceStateServer{values: map[string][]byte{}} +} + +func (s *instanceStateServer) ReadInstanceState(_ context.Context, req *pluginv1.ReadInstanceStateRequest) (*pluginv1.ReadInstanceStateResponse, error) { + value, ok := s.values[req.GetKey()] + if !ok { + return &pluginv1.ReadInstanceStateResponse{}, nil + } + return &pluginv1.ReadInstanceStateResponse{Value: value, Found: true}, nil +} + +func (s *instanceStateServer) WriteInstanceState(_ context.Context, req *pluginv1.WriteInstanceStateRequest) (*pluginv1.WriteInstanceStateResponse, error) { + s.values[req.GetKey()] = append([]byte(nil), req.GetValue()...) + return &pluginv1.WriteInstanceStateResponse{}, nil +} + +func (s *instanceStateServer) ReportNetworkAccessStatus(_ context.Context, req *pluginv1.ReportNetworkAccessStatusRequest) (*pluginv1.ReportNetworkAccessStatusResponse, error) { + s.statuses = append(s.statuses, req.GetStatus()) + return &pluginv1.ReportNetworkAccessStatusResponse{}, nil +} + +func dialInstanceState(t *testing.T, srv *instanceStateServer) *runtimehost.Client { + t.Helper() + return runtimehost.NewClient(dialServer(t, srv)) +} + +func TestInstanceState_RoundTrip(t *testing.T) { + srv := newInstanceStateServer() + c := dialInstanceState(t, srv) + ctx := context.Background() + + _, found, err := c.ReadInstanceState(ctx, "_machinekey") + if err != nil || found { + t.Fatalf("ReadInstanceState before write = found %v, err %v; want absent", found, err) + } + if err := c.WriteInstanceState(ctx, "_machinekey", []byte("secret")); err != nil { + t.Fatalf("WriteInstanceState: %v", err) + } + value, found, err := c.ReadInstanceState(ctx, "_machinekey") + if err != nil || !found || string(value) != "secret" { + t.Fatalf("ReadInstanceState after write = %q found %v err %v", value, found, err) + } + // An empty stored value is still found. + if err := c.WriteInstanceState(ctx, "empty", nil); err != nil { + t.Fatalf("WriteInstanceState empty: %v", err) + } + if _, found, err := c.ReadInstanceState(ctx, "empty"); err != nil || !found { + t.Fatalf("empty value: found %v err %v, want found", found, err) + } +} + +func TestInstanceState_EnforcesLimitsClientSide(t *testing.T) { + srv := newInstanceStateServer() + c := dialInstanceState(t, srv) + ctx := context.Background() + + if _, _, err := c.ReadInstanceState(ctx, ""); err == nil { + t.Fatal("empty key accepted") + } + longKey := strings.Repeat("k", runtimehost.MaxInstanceStateKeyBytes+1) + if err := c.WriteInstanceState(ctx, longKey, []byte("x")); err == nil { + t.Fatal("oversized key accepted") + } + big := bytes.Repeat([]byte("v"), runtimehost.MaxInstanceStateValueBytes+1) + if err := c.WriteInstanceState(ctx, "big", big); err == nil { + t.Fatal("oversized value accepted") + } + if len(srv.values) != 0 { + t.Fatalf("rejected writes reached the host: %v", srv.values) + } + limit := bytes.Repeat([]byte("v"), runtimehost.MaxInstanceStateValueBytes) + if err := c.WriteInstanceState(ctx, "limit", limit); err != nil { + t.Fatalf("value at the limit rejected: %v", err) + } +} + +func TestInstanceStateStore_ImplementsStateStoreShape(t *testing.T) { + srv := newInstanceStateServer() + c := dialInstanceState(t, srv) + var store runtimehost.StateStore = c.InstanceStateStore() + + if _, err := store.ReadState("_profiles"); !errors.Is(err, runtimehost.ErrStateNotExist) { + t.Fatalf("ReadState missing = %v, want ErrStateNotExist", err) + } + if err := store.WriteState("_profiles", []byte(`{}`)); err != nil { + t.Fatalf("WriteState: %v", err) + } + got, err := store.ReadState("_profiles") + if err != nil || string(got) != `{}` { + t.Fatalf("ReadState = %q, %v", got, err) + } + if err := c.InstanceStateStore().WithTimeout(0).WriteState("k", []byte("v")); err != nil { + t.Fatalf("WriteState without deadline: %v", err) + } +} + +func TestReportNetworkAccessStatus(t *testing.T) { + srv := newInstanceStateServer() + c := dialInstanceState(t, srv) + ctx := context.Background() + + if err := c.ReportNetworkAccessStatus(ctx, nil); err == nil { + t.Fatal("nil status accepted") + } + if err := c.ReportNetworkAccessStatus(ctx, &pluginv1.NetworkAccessStatus{}); err == nil { + t.Fatal("status without state accepted") + } + status := &pluginv1.NetworkAccessStatus{ + State: runtimehost.NetworkAccessStateConnected, + Hostname: "silo.tail1234.ts.net", + Origin: "https://silo.tail1234.ts.net", + DesiredConnected: true, + } + if err := c.ReportNetworkAccessStatus(ctx, status); err != nil { + t.Fatalf("ReportNetworkAccessStatus: %v", err) + } + if len(srv.statuses) != 1 || srv.statuses[0].GetHostname() != "silo.tail1234.ts.net" || !srv.statuses[0].GetDesiredConnected() { + t.Fatalf("host received %v", srv.statuses) + } +} diff --git a/pkg/pluginsdk/runtimehost/network_access.go b/pkg/pluginsdk/runtimehost/network_access.go new file mode 100644 index 0000000..25207d7 --- /dev/null +++ b/pkg/pluginsdk/runtimehost/network_access.go @@ -0,0 +1,32 @@ +package runtimehost + +import ( + "context" + "fmt" + + pluginv1 "github.com/Silo-Server/silo-plugin-sdk/pkg/pluginproto/silo/plugin/v1" +) + +// NetworkAccessStatus state values. The vocabulary is open; hosts tolerate +// values they do not recognize. +const ( + NetworkAccessStateDisconnected = "disconnected" + NetworkAccessStateAwaitingAuthorization = "awaiting_authorization" + NetworkAccessStateConnecting = "connecting" + NetworkAccessStateConnected = "connected" + NetworkAccessStateError = "error" +) + +// ReportNetworkAccessStatus pushes a network access status change to the +// host. Call it on every state transition so the host does not have to poll. +// The host logs state transitions only; auth_url is never logged. +func (c *Client) ReportNetworkAccessStatus(ctx context.Context, status *pluginv1.NetworkAccessStatus) error { + if status == nil { + return fmt.Errorf("runtimehost: network access status is required") + } + if status.GetState() == "" { + return fmt.Errorf("runtimehost: network access status state is required") + } + _, err := c.rpc.ReportNetworkAccessStatus(ctx, &pluginv1.ReportNetworkAccessStatusRequest{Status: status}) + return err +} diff --git a/proto/silo/plugin/v1/common.proto b/proto/silo/plugin/v1/common.proto index adf3ef0..dae0ff9 100644 --- a/proto/silo/plugin/v1/common.proto +++ b/proto/silo/plugin/v1/common.proto @@ -5,6 +5,7 @@ package silo.plugin.v1; option go_package = "github.com/Silo-Server/silo-plugin-sdk/pkg/pluginproto/silo/plugin/v1;pluginv1"; import "google/protobuf/struct.proto"; +import "silo/plugin/v1/network_access_provider.proto"; import "silo/plugin/v1/watch_sync_provider.proto"; message SupportedPlatform { @@ -142,6 +143,11 @@ message CapabilityDescriptor { // of type "watch_sync_provider.v1". Keeping this typed lets the host build // connection and authorization UI without launching the plugin. WatchSyncProviderDescriptor watch_sync_provider = 10; + + // Typed network-access contract metadata. Only meaningful for capabilities + // of type "network_access_provider.v1". The host lists available overlay + // providers from this descriptor without launching the plugin. + NetworkAccessProviderDescriptor network_access_provider = 11; } // PluginPresentation describes a plugin for server operators and links them to diff --git a/proto/silo/plugin/v1/network_access_provider.proto b/proto/silo/plugin/v1/network_access_provider.proto new file mode 100644 index 0000000..b6a4050 --- /dev/null +++ b/proto/silo/plugin/v1/network_access_provider.proto @@ -0,0 +1,72 @@ +syntax = "proto3"; + +package silo.plugin.v1; + +option go_package = "github.com/Silo-Server/silo-plugin-sdk/pkg/pluginproto/silo/plugin/v1;pluginv1"; + +// NetworkAccessProvider gives a Silo deployment an overlay-network identity +// (Tailscale, NetBird, ...). The plugin owns the overlay client and a reverse +// proxy in front of the host listeners it learns from RuntimeHost.GetHostInfo; +// the host owns supervision, per-instance state storage, status aggregation, +// and the admin API. Plugins declaring this capability are resident: the host +// starts them at boot, restarts them on crash, and stops them last. +service NetworkAccessProvider { + // Connect brings the overlay identity up and starts proxying. It returns the + // status reached so far; enrollment may continue in the background and be + // reported through RuntimeHost.ReportNetworkAccessStatus. + rpc Connect(NetworkAccessConnectRequest) returns (NetworkAccessStatus); + // Disconnect tears the overlay listener down and clears desired_connected. + rpc Disconnect(NetworkAccessDisconnectRequest) returns (NetworkAccessStatus); + // GetStatus returns the current status without changing it. + rpc GetStatus(NetworkAccessGetStatusRequest) returns (NetworkAccessStatus); +} + +message NetworkAccessConnectRequest {} + +message NetworkAccessDisconnectRequest {} + +message NetworkAccessGetStatusRequest {} + +// NetworkAccessStatus is the provider's view of one plugin instance. Every +// host (API server, each proxy node) runs its own instance and reports its own +// status; the host aggregates them. +message NetworkAccessStatus { + // One of: disconnected | awaiting_authorization | connecting | connected | error. + string state = 1; + // Overlay DNS name, e.g. silo.tail1234.ts.net. + string hostname = 2; + // scheme://host[:port] clients should use for the api listener. + string origin = 3; + // Overlay IP addresses assigned to this instance. + repeated string addresses = 4; + // One entry per exposed host listener (see RuntimeHost.GetHostInfo). + repeated NetworkAccessListener listeners = 5; + // Interactive enrollment URL. Only set while state is awaiting_authorization. + // Admin-only; the plugin must never log it. + string auth_url = 6; + // Human-readable failure detail when state is error. + string error = 7; + // Provider client version shown to admins, e.g. "tsnet 1.102.4". + string provider_version = 8; + // Plugin-owned intent that survives restarts. The plugin persists it in + // instance state and reconnects on start when true. + bool desired_connected = 9; +} + +// NetworkAccessListener maps one host listener onto its overlay origin. +message NetworkAccessListener { + // Listener name from RuntimeHost.GetHostInfo: api | jellyfin | abs. + string name = 1; + // Overlay origin serving that listener, scheme://host[:port]. + string origin = 2; +} + +// NetworkAccessProviderDescriptor is the typed manifest metadata for a +// network_access_provider.v1 capability. The host lists providers from it +// without launching the plugin. +message NetworkAccessProviderDescriptor { + // Stable provider slug, e.g. "tailscale", "netbird". Lowercase, path-safe. + string provider = 1; + // Operator-facing name, e.g. "Tailscale". + string display_name = 2; +} diff --git a/proto/silo/plugin/v1/runtime_host.proto b/proto/silo/plugin/v1/runtime_host.proto index 5932819..80165d7 100644 --- a/proto/silo/plugin/v1/runtime_host.proto +++ b/proto/silo/plugin/v1/runtime_host.proto @@ -6,6 +6,7 @@ option go_package = "github.com/Silo-Server/silo-plugin-sdk/pkg/pluginproto/silo import "google/protobuf/struct.proto"; import "silo/plugin/v1/common.proto"; +import "silo/plugin/v1/network_access_provider.proto"; // RuntimeHost is implemented by the silo host. Plugins dial it via the // go-plugin gRPC broker. See pkg/pluginsdk/runtimehost for a typed client. @@ -64,6 +65,21 @@ service RuntimeHost { // through the host control plane. Calls are treated as authenticated // service-to-service traffic, but never as admin traffic. rpc CallPluginHTTP(CallPluginHTTPRequest) returns (CallPluginHTTPResponse); + + // ReadInstanceState reads one key of the calling plugin instance's private + // state. The scope (installation plus host) is derived by the host and never + // supplied by the plugin. See docs/network-access-provider.md. + rpc ReadInstanceState(ReadInstanceStateRequest) returns (ReadInstanceStateResponse); + + // WriteInstanceState writes one key of the calling plugin instance's private + // state. The host stores values encrypted and never returns them through + // any API. Limits: key <= 256 bytes, value <= 256 KiB, <= 256 keys per scope. + rpc WriteInstanceState(WriteInstanceStateRequest) returns (WriteInstanceStateResponse); + + // ReportNetworkAccessStatus pushes a network_access_provider.v1 status + // change to the host so it does not have to poll. Plugins call it on every + // state transition; the host may still call GetStatus on demand. + rpc ReportNetworkAccessStatus(ReportNetworkAccessStatusRequest) returns (ReportNetworkAccessStatusResponse); } message PublishEventRequest { @@ -102,6 +118,33 @@ message GetHostInfoResponse { string public_base_url = 1; string internal_base_url = 2; string plugin_proxy_base_url = 3; + // Role of the process hosting this plugin instance: api | proxy. + string host_role = 4; + // Node name on proxies, or the server name on the api host. + string host_name = 5; + // stream_nodes.id on proxies; 0 on the api host. + int64 node_id = 6; + // Per-process-start secret that a network access provider stamps on + // proxied requests as X-Silo-Ingress-Token. Treat as a credential: keep it + // in memory, never log or persist it, and re-read GetHostInfo after a + // restart because the host rotates it on every start. + string ingress_token = 7; + // Host listeners a network access provider should expose. Always contains + // "api"; "jellyfin" and "abs" appear when those listeners are enabled. + // Proxies expose only "api". + repeated HostListener listeners = 8; +} + +// HostListener is one local listener the host asks a network access provider +// to expose on the overlay network. +message HostListener { + // Listener name: api | jellyfin | abs. + string name = 1; + // Loopback dial address for the listener, host:port. + string address = 2; + // Port the plugin should expose this listener on (e.g. 443 for api under + // HTTPS, 8096 for jellyfin, 13378 for abs). Zero means provider default. + int32 default_port = 3; } message ListLibrariesRequest { @@ -284,3 +327,30 @@ message CallPluginHTTPResponse { map headers = 2; bytes body = 3; } + +message ReadInstanceStateRequest { + // State key, <= 256 bytes. + string key = 1; +} + +message ReadInstanceStateResponse { + // Stored value; empty when found is false. + bytes value = 1; + // False when no value is stored under key in this instance's scope. + bool found = 2; +} + +message WriteInstanceStateRequest { + // State key, <= 256 bytes. + string key = 1; + // Value to store, <= 256 KiB. + bytes value = 2; +} + +message WriteInstanceStateResponse {} + +message ReportNetworkAccessStatusRequest { + NetworkAccessStatus status = 1; +} + +message ReportNetworkAccessStatusResponse {} From 2da2393c08804b84aec70a8582f1003ad9ab62d1 Mon Sep 17 00:00:00 2001 From: Quick <31828688+Quick104@users.noreply.github.com> Date: Mon, 14 Sep 2026 18:25:17 +0000 Subject: [PATCH 2/2] fix(example): retry state restoration, surface write failures, honor zero DefaultPort Review findings on the hello-network-access stub: restoreLocked marked restoration complete before the read succeeded, so a transient host error turned persisted intent into disconnected for the process lifetime; instance-state write failures were only logged, so an admin's connect or disconnect could report success while the intent was lost; and a listener with DefaultPort zero produced the unusable origin host:0 although zero means provider default. Co-Authored-By: Claude Fable 5.1 --- examples/hello-network-access/main.go | 29 ++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/examples/hello-network-access/main.go b/examples/hello-network-access/main.go index 25f897c..fe9f1a2 100644 --- a/examples/hello-network-access/main.go +++ b/examples/hello-network-access/main.go @@ -57,7 +57,9 @@ func (p *provider) Connect(ctx context.Context, _ *pluginv1.NetworkAccessConnect } p.connected = true p.desired = true - p.persistLocked(ctx, host) + if err := p.persistLocked(ctx, host); err != nil { + return nil, err + } status := p.statusLocked() p.reportLocked(ctx, host, status) return status, nil @@ -70,7 +72,9 @@ func (p *provider) Disconnect(ctx context.Context, _ *pluginv1.NetworkAccessDisc host := sdkruntime.Host() p.connected = false p.desired = false - p.persistLocked(ctx, host) + if err := p.persistLocked(ctx, host); err != nil { + return nil, err + } status := p.statusLocked() p.reportLocked(ctx, host, status) return status, nil @@ -94,12 +98,14 @@ func (p *provider) restoreLocked(ctx context.Context) { if host == nil { return } - p.restored = true value, found, err := host.ReadInstanceState(ctx, desiredKey) if err != nil { + // Leave restored false so the next RPC tries again; a transient + // host error must not turn persisted intent into "disconnected". p.logger.Warn("read instance state", "err", err) return } + p.restored = true if found && string(value) == "1" { p.desired = true p.connected = true @@ -109,17 +115,21 @@ func (p *provider) restoreLocked(ctx context.Context) { } } -func (p *provider) persistLocked(ctx context.Context, host *runtimehost.Client) { +// persistLocked writes desired_connected. A failure is returned to the RPC +// caller: the admin must know the intent did not survive, or the plugin would +// come back in the wrong state after its next restart. +func (p *provider) persistLocked(ctx context.Context, host *runtimehost.Client) error { if host == nil { - return + return nil } value := []byte("0") if p.desired { value = []byte("1") } if err := host.WriteInstanceState(ctx, desiredKey, value); err != nil { - p.logger.Warn("write instance state", "err", err) + return fmt.Errorf("persist desired_connected: %w", err) } + return nil } func (p *provider) reportLocked(ctx context.Context, host *runtimehost.Client, status *pluginv1.NetworkAccessStatus) { @@ -145,7 +155,12 @@ func (p *provider) statusLocked() *pluginv1.NetworkAccessStatus { status.Addresses = []string{"100.64.0.1"} if p.hostInfo != nil { for _, l := range p.hostInfo.Listeners { - origin := fmt.Sprintf("https://%s:%d", status.Hostname, l.DefaultPort) + // Zero asks for the provider default; this stub's default is + // HTTPS on 443, which the origin leaves implicit. + origin := "https://" + status.Hostname + if l.DefaultPort != 0 && l.DefaultPort != 443 { + origin = fmt.Sprintf("https://%s:%d", status.Hostname, l.DefaultPort) + } status.Listeners = append(status.Listeners, &pluginv1.NetworkAccessListener{Name: l.Name, Origin: origin}) if l.Name == runtimehost.ListenerAPI { status.Origin = origin