transport

package
v0.2026282.1115 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 9, 2026 License: MIT Imports: 18 Imported by: 0

Documentation

Overview

Package transport (github.com/opencharly/spec/transport, #55 step1) provides transport-neutral gRPC connections to Charly targets. This file holds the go-plugin serve/dispense surface relocated from the github.com/opencharly/sdk root package: the handshake, the Serve/PluginMap/Conn trio, and the channel streaming types an out-of-process plugin + the charly host share over the broker. It is a fabric slice of the spec contract module — a host primitive a plugin cannot hold as a shared library (the gRPC dial/serve over the go-plugin stdio carrier) — so by #55 Rule 1 it lives in a contained spec slice whose only heavy dep is google.golang.org/grpc + hashicorp/go-plugin + spec/proto (Rule 2). charly core imports this slice INSTEAD of the sdk root; the sdk root keeps a thin re-export during cutover then is deleted.

Package transport (github.com/opencharly/spec/transport, #55 step1) provides transport-neutral gRPC connections to Charly targets. It is a fabric slice of the spec contract module — a host primitive a plugin cannot hold as a shared library (the gRPC dial/serve over a process stdio carrier) — so by #55 Rule 1 it lives in a contained spec slice whose only heavy dep is google.golang.org/grpc + spec/proto (Rule 2). SSH is only a process transport: gRPC still owns the protocol, and the target chain carried by CUE-generated spec.TargetSpec owns nested routing.

Index

Constants

View Source
const (
	ChannelOpen     = "open"
	ChannelStdin    = "stdin"
	ChannelStdout   = "stdout"
	ChannelStderr   = "stderr"
	ChannelTerminal = "terminal"
	ChannelStatus   = "status"
	ChannelResize   = "resize"
	ChannelSignal   = "signal"
	ChannelAck      = "ack"
	ChannelCancel   = "cancel"
	ChannelExit     = "exit"
	ChannelError    = "error"
	ChannelResync   = "resync"
)

Channel frame kinds are transport vocabulary, not runtime semantics. Runtime- specific events travel as CUE-generated JSON in ChannelFrame.PayloadJson.

View Source
const DispenseKey = "charly"

DispenseKey is the single go-plugin plugin name; charly serves/dispenses ONE gRPC plugin exposing the uniform Provider + PluginMeta services.

Variables

View Source
var Handshake = plugin.HandshakeConfig{
	ProtocolVersion:  2,
	MagicCookieKey:   "CHARLY_PLUGIN",
	MagicCookieValue: "charly-plugin-v1",
}

Handshake is the magic-cookie handshake charly and every plugin MUST share. A plugin server refuses to serve unless launched with CHARLY_PLUGIN set, so a plugin binary run by hand prints the "not meant to be executed directly" notice instead of hanging.

ProtocolVersion is hashicorp/go-plugin's OWN transport-family negotiation: its checkProtoVersion runs at Connect, before Describe. The value is FROZEN at 2 and MUST NOT be changed without rebuilding every plugin binary — go-plugin refuses a plugin whose advertised version differs, and that would break plugins whose WIREFRAME is perfectly compatible. It is NOT the wireframe gate: the wireframe contract is the CUE schema a plugin serves over Describe and the host splices onto its base (registerPluginUnitSchema), where a real capability gap is a hard schema error.

View Source
var ServedBroker *plugin.GRPCBroker

ServedBroker is the go-plugin GRPCBroker captured when this plugin's gRPC server starts (grpcPlugin.GRPCServer). A deploy/step/builder plugin dials the host's E3b reverse-channel ExecutorService through it. One broker per plugin process (go-plugin's model), so a package var is the natural home. Exported so the sdk package's executor/checkverb helpers can read it after relocation.

Functions

func CopyChannel added in v0.2026213.2047

func CopyChannel(dst interface{ Send(*pb.ChannelFrame) error }, src interface {
	Recv() (*pb.ChannelFrame, error)
}) error

CopyChannel relays frames until EOF or cancellation. It is intentionally a byte-preserving transport primitive; it does not inspect agent or terminal payloads.

func DialProcessProvider

func DialProcessProvider(ctx context.Context, process spec.Process, opts DialOptions) (*grpc.ClientConn, pb.ProviderClient, error)

DialProcessProvider opens gRPC over a process supplied by a deployment executor. This is the placement-neutral sibling of DialProvider: the same protocol runs over local exec, SSH, containers, VMs, and recursively nested venues without teaching the gRPC layer about any of them.

func DialProvider

func DialProvider(ctx context.Context, target spec.TargetSpec, opts DialOptions) (*grpc.ClientConn, pb.ProviderClient, error)

DialProvider opens gRPC over a local-exec or SSH stdio process. The SSH remote argv is fixed to `charly __agent-target serve --stdio`; nested target hops are carried as TargetSpec on the first Provider.Channel frame and are resolved by the responsible Charly node.

func IsServeMode added in v0.2026213.2047

func IsServeMode() bool

IsServeMode reports whether this process was launched by charly as a go-plugin gRPC SERVER (the handshake magic-cookie env is present) rather than invoked directly as a CLI. charly sets the cookie ONLY when it execs a plugin to connect over gRPC (LocalTransport, for a verb/kind/deploy/step/builder capability); a COMMAND plugin fork/exec'd as a CLI passthrough (charly's syscall.Exec command dispatch strips the cookie) sees it absent and runs in CLI mode. The single switch a dual-mode plugin's main() pivots on.

func Main added in v0.2026213.2047

func Main(providerSrv pb.ProviderServer, metaSrv pb.PluginMetaServer, cli func(args []string) int)

Main is the dual-mode entry point a plugin's main() delegates to. In SERVE mode (charly launched it over go-plugin gRPC) it serves the plugin's Provider + PluginMeta (its verb/kind/deploy/step/builder capabilities). Otherwise the plugin was fork/exec'd by charly's COMMAND dispatch (or run by hand) and owns real terminal stdio/TTY: cli runs the command's work with os.Args[1:], its int return becoming the process exit code.

func main() { transport.Main(&provider{}, &meta{}, cliMain) }

func OpenProviderChannel added in v0.2026213.2047

func OpenProviderChannel(ctx context.Context, client pb.ProviderClient, open *pb.ChannelFrame) (pb.Provider_ChannelClient, error)

OpenProviderChannel starts a generated Provider.Channel stream and sends its mandatory open frame. The returned stream is ready for concurrent Send/Recv, as supported by gRPC.

func PluginMap added in v0.2026213.2047

func PluginMap(providerSrv pb.ProviderServer, metaSrv pb.PluginMetaServer) plugin.PluginSet

PluginMap builds the go-plugin PluginSet for the dispense key. Server side passes the two service impls; the client side (charly connecting) passes nil,nil and receives a *Conn from the dispense.

func ReceiveChannelOpen added in v0.2026213.2047

func ReceiveChannelOpen(stream ProviderChannel) (*pb.ChannelFrame, error)

ReceiveChannelOpen reads and validates the mandatory first frame. The request id, provider class/word, and operation are required so every later frame can be correlated without inspecting runtime-specific payloads.

func RelayChannel added in v0.2026213.2047

func RelayChannel(upstream ProviderChannel, downstream interface {
	ProviderChannel
	CloseSend() error
}) error

RelayChannel connects a controller-side ProviderChannel to a downstream gRPC channel with ordered half-close semantics. Provider output is the evidence writer: once that direction ends, no later frame can mutate controller state and the relay may return. If controller input ends first, CloseSend delivers the protocol EOF and the relay drains provider output before returning. This prevents a completed command from racing its successor's durable cursor.

Cancellation ownership: when the provider-output direction finishes first, RelayChannel returns while the input-copy goroutine may still be blocked in upstream.Recv(). The CALLER owns upstream's lifecycle — after return, cancel the context upstream carries (or close upstream) to release that goroutine.

func Serve added in v0.2026213.2047

func Serve(providerSrv pb.ProviderServer, metaSrv pb.PluginMetaServer)

Serve exposes a plugin's Provider + PluginMeta services over go-plugin gRPC and blocks serving. The host reaps it on exit by killing the client connection (providerRegistry.Close → client.Kill, sending the gRPC Shutdown that stops this server); go-plugin's server has no parent-death detection of its own, so watchParentDeath is the backstop that self-terminates this process if the host dies without reaping (crash / SIGKILL / os.Exit) — preventing orphaned `__plugin serve` processes. The serve half of Main (a verb/kind/deploy/step/ builder plugin with no CLI mode may call it directly):

func main() { transport.Serve(&myProvider{}, &myMeta{}) }

func ServeStdio

func ServeStdio(stdin io.ReadCloser, stdout io.WriteCloser, register func(*grpc.Server), opts ...grpc.ServerOption) error

ServeStdio runs a gRPC server on one full-duplex stdio connection. stdout is exclusively HTTP/2 protocol data; diagnostics belong on stderr.

func StdioCommand

func StdioCommand(target spec.TargetSpec, opts DialOptions) ([]string, error)

StdioCommand validates the generic route and returns an argv with no shell interpolation. Hops after the first grpc marker—including tmux, another SSH node, or an exec/container hop—are nested routing data, not special cases.

func StdioFiles

func StdioFiles() (io.ReadCloser, io.WriteCloser)

StdioFiles is the production adapter used by the hidden target command.

Types

type ChannelProvider added in v0.2026213.2047

type ChannelProvider interface {
	OpenChannel(open *pb.ChannelFrame, stream ProviderChannel) error
}

ChannelProvider is the optional streaming extension to Provider. The first frame has already been validated as an open frame and remains available as open; subsequent controller frames arrive through stream. Domain payloads are generated from CUE and carried in open.PayloadJson.

type Conn added in v0.2026213.2047

type Conn struct {
	Provider pb.ProviderClient
	Meta     pb.PluginMetaClient
	// Broker is this connection's go-plugin GRPCBroker — the host's handle to stand up
	// the E3b reverse-channel ExecutorService a deploy/step/builder plugin dials back
	// to. Nil for an in-proc transport (no reverse channel needed).
	Broker *plugin.GRPCBroker
}

Conn is the dispensed client handle — charly's side of a connected plugin.

type DialOptions

type DialOptions struct {
	CharlyBinary       string
	RemoteCharlyBinary string
	SSHBinary          string
	Stderr             io.Writer
	ExtraGRPC          []grpc.DialOption
	Command            func(context.Context, string, ...string) *exec.Cmd
}

DialOptions controls process launch only. It deliberately has no runtime- or tmux-specific fields; those remain in the CUE payload sent over Provider.Channel.

type ProviderChannel added in v0.2026213.2047

type ProviderChannel interface {
	Context() context.Context
	Send(*pb.ChannelFrame) error
	Recv() (*pb.ChannelFrame, error)
}

ProviderChannel is the common subset of the generated client and server streams. It lets in-process and gRPC providers share one channel handler.

type ReplayBuffer added in v0.2026213.2047

type ReplayBuffer struct {
	// contains filtered or unexported fields
}

ReplayBuffer is a bounded, acknowledgement-aware frame history for detach / reconnect. Bounds are enforced by both frame count and protobuf byte size. When an unacknowledged frame would be evicted, Add fails loudly; callers must preserve evidence and enter the incident/RCA workflow rather than hide loss.

func NewReplayBuffer added in v0.2026213.2047

func NewReplayBuffer(maxFrames, maxBytes int) *ReplayBuffer

func (*ReplayBuffer) Acknowledge added in v0.2026213.2047

func (b *ReplayBuffer) Acknowledge(sequence uint64)

func (*ReplayBuffer) Add added in v0.2026213.2047

func (b *ReplayBuffer) Add(frame *pb.ChannelFrame) error

func (*ReplayBuffer) ReplayFrom added in v0.2026213.2047

func (b *ReplayBuffer) ReplayFrom(sequence uint64) ([]*pb.ChannelFrame, error)

type SequenceGate added in v0.2026213.2047

type SequenceGate struct {
	// contains filtered or unexported fields
}

SequenceGate rejects duplicates, regressions, and gaps. A provider can turn a gap into ChannelResync using ReplayBuffer.ReplayFrom; it must never silently reorder process or terminal output.

func NewSequenceGate added in v0.2026213.2047

func NewSequenceGate(first uint64) *SequenceGate

func (*SequenceGate) Accept added in v0.2026213.2047

func (g *SequenceGate) Accept(frame *pb.ChannelFrame) error

func (*SequenceGate) Expected added in v0.2026213.2047

func (g *SequenceGate) Expected() uint64

Expected returns the next sequence without advancing the gate.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL