client

package
v0.2.35 Latest Latest
Warning

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

Go to latest
Published: Nov 1, 2026 License: Apache-2.0 Imports: 20 Imported by: 0

Documentation

Overview

Package client reaches one enrolled railgrid runner through the edges provider's published Service proxy, speaking the typed runner/v1 protocol in pkg/runner.

It exists once, next to the protocol types, because every provider that dispatches to a runner needs the same three things and must not get any of them subtly different: a transport that cannot be downgraded, a coordinate that is rendered by the one renderer of the data-plane grammar, and an identity check that refuses a runner which is not the runner the caller was enrolled with.

It owns no scheduler, mints no credentials, and holds no execution identity: the *rest.Config handed to New is the caller's own workspace identity, and every request this package makes is evaluated against it.

Index

Constants

View Source
const (
	EdgeKindLinuxServer = "LinuxServer"
	EdgeKindMacOSServer = "MacOSServer"
)

Host Edge kinds a runner may be enrolled on. A runner is a process on a machine, so a Service backed by a KubernetesCluster edge can never be one: refusing the kind here turns a mis-enrollment into a startup error instead of a dispatch that reaches something that is not a runner at all.

Variables

This section is empty.

Functions

This section is empty.

Types

type Client

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

Client invokes one enrolled runner. It is safe for concurrent use.

func New

func New(cfg *rest.Config, ref ServiceRef, opts ...Option) (*Client, error)

New validates the transport and the enrollment and returns a client. It performs no I/O: discovery happens on the first operation, so constructing a client for a runner that is asleep is not itself an error.

The config must be the caller's own workspace identity addressing the enrolled cluster's front door exactly, and nothing about it may weaken the connection: no plaintext or unverified TLS, no proxy, no exec or auth provider, no basic auth, no impersonation, no client certificate, and no redirects. A config carrying no credential at all is refused too — a bearer, a bearer file, or a WrapTransport that attaches a hub-minted, TTL'd token per request are the three shapes accepted.

Each of those is refused with its own message. They are unrelated faults with unrelated fixes, and one sentence covering all of them tells a reader that something is wrong with their config without saying what.

func (*Client) Artifact

func (c *Client) Artifact(ctx context.Context, attemptID, artifactID string, w io.Writer) (runner.Artifact, error)

Artifact copies one artifact's bytes into w and returns its metadata as the runner served it.

The digest is recomputed while copying and a mismatch is an error: an artifact is immutable by contract, and a body that does not hash to the digest the runner announced is not that artifact. Only ID, Digest, Length, and MediaType are carried on the wire; Name and CreatedAt live on the receipt, so they are left zero here rather than invented.

func (*Client) Cancel

func (c *Client) Cancel(ctx context.Context, req runner.CancelRequest) (runner.Receipt, error)

Cancel asks the runner to stop an attempt.

func (*Client) Capabilities

func (c *Client) Capabilities(ctx context.Context) (runner.Capabilities, error)

Capabilities reports what the enrolled runner can do right now. The coordinate may be cached but the capabilities never are: readiness and capacity are what a caller asks this question to learn.

func (*Client) Events

func (c *Client) Events(ctx context.Context, attemptID string, after uint64) (*EventStream, error)

Events streams an attempt's events from after (exclusive). A caller resumes with the last cursor it saw; passing 0 replays from the beginning of what the runner still holds.

The caller must Close the returned stream.

func (*Client) Inspect

func (c *Client) Inspect(ctx context.Context, attemptID string) (runner.Receipt, error)

Inspect reads the durable receipt of one attempt. It is the reconciliation path: whenever a mutation or a stream fails with an unknown outcome, the receipt is what says what actually happened.

func (*Client) Invalidate

func (c *Client) Invalidate()

Invalidate drops the cached proxy coordinate so the next operation re-discovers and re-checks it. It is called automatically when an operation is answered with 401, 403, or 404 — the statuses a deleted, re-pointed, or un-granted Service produces — and is exported so a caller that learns of a change some other way can force the same re-check.

Nothing is retried on invalidation: a mutation that failed has an unknown outcome, and re-sending it is the coordinator's decision to make with Inspect, not this package's.

func (*Client) Resume

func (c *Client) Resume(ctx context.Context, req runner.ResumeRequest) (runner.Receipt, error)

Resume continues an attempt that is waiting for input.

func (*Client) Start

func (c *Client) Start(ctx context.Context, req runner.StartRequest) (runner.Receipt, error)

Start dispatches an approved attempt. A failure is never retried here: the outcome of a failed mutation is unknown, and Inspect is the authority.

type EventStream

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

EventStream is a live, ordered read of one attempt's events.

It is a stream rather than a buffer because that is what the wire is: the runner replays what it holds and then keeps the connection open, so a caller that waited for the whole response would only ever see the replay. Events arrive as Server-Sent Events, framed by pkg/runner/http.go writeSSE as

id: {cursor}\nevent: {type}\ndata: {the JSON Event}\n\n

The id and event fields are redundant with the JSON, which is authoritative here: parsing one representation and trusting another is how two views of the same event start to disagree.

An EventStream is not safe for concurrent use; one consumer calls Next until it returns an error, then Close.

func (*EventStream) Close

func (s *EventStream) Close() error

Close releases the connection. It is safe to call more than once and after Next has returned an error.

func (*EventStream) Cursor

func (s *EventStream) Cursor() uint64

Cursor is the cursor of the last event Next returned, or the `after` the stream was opened with. It is what a caller reconnects from after the runner ends a quiet stream.

func (*EventStream) Next

func (s *EventStream) Next(ctx context.Context) (runner.Event, error)

Next returns the next event, io.EOF when the runner ended the stream, or an error.

A gap in the cursor sequence is returned as *runner.Error with Code ErrorCursorExpired and SnapshotRequired set, the same shape the runner uses when a caller asks for a cursor it no longer holds: events are dropped rather than queued for a consumer that fell behind (pkg/runner/runner.go appends to subscribers with a non-blocking send), so a gap means the caller's view is incomplete and only Inspect can restore it.

Cancelling ctx ends the stream: it closes the underlying body, which is what unblocks a read that is waiting for the next event. The stream is finished afterwards — there is nothing to resume on a connection that has been torn down — so a caller that wants to keep reading passes a ctx it does not cancel.

type HTTPError

type HTTPError struct {
	StatusCode int
	// Service is the enrolled edges Service for a request to its object/proxy path.
	Service string
	Body    []byte
}

HTTPError is a failure the runner protocol does not describe: a proxy, an authenticator, or kcp itself answered instead of the runner. The bounded body is preserved because it is usually the only diagnosis available.

func (*HTTPError) Error

func (e *HTTPError) Error() string

type IdentityError

type IdentityError struct {
	// Reported is the runnerID the runner claims for itself.
	Reported string
	// Enrolled is the runnerID the caller was enrolled with.
	Enrolled string
	// Protocol is the protocol version the runner reported.
	Protocol string
}

IdentityError reports a runner that answers but is not the runner this caller was enrolled with, or one that does not speak this protocol. It is a configuration mismatch rather than an outage, so it carries both identities: the two producers of a runner ID do not disagree by accident, and the difference is the whole diagnosis.

func (*IdentityError) Error

func (e *IdentityError) Error() string

type Option

type Option func(*options)

Option adjusts how New validates the transport. There is deliberately no option that relaxes the credential, the proxy, the impersonation or the certificate rules: those are about WHO the call is made as, and a caller that wants them relaxed wants a different call.

func AllowInsecureTLS

func AllowInsecureTLS() Option

AllowInsecureTLS accepts a config with TLS verification disabled.

It exists because refusing it here can be inconsistent rather than safer. A provider is normally configured with the hub's CA, and then this never applies. But an operator who has deliberately pointed a provider at a hub with verification off — a development stack with a self-signed certificate, which is what RAILGRID_HUB_INSECURE means — has already made that choice for every other call that provider makes to the same hub, including the ones that carry its own identity. Refusing only the runner hop would not protect the harness credential; it would just make the feature unusable on that stack while the same channel carried everything else.

It must be passed EXPLICITLY, by a caller that knows its own config is insecure. Nothing here infers it.

type ServiceRef

type ServiceRef struct {
	// Cluster is the tenant logical cluster the Service lives in.
	Cluster string
	// Service is the name of the edges Service the runner is published as.
	Service string
	// EdgeKind and EdgeName are the edge the Service must still point at.
	EdgeKind string
	EdgeName string
	// RunnerID is the identity the runner must report for itself. Empty means
	// the caller pins the runner by the Service alone — the object it reads and
	// dials by name and UID — and accepts whichever runner the edge published
	// there. The protocol version is checked either way.
	RunnerID string
}

ServiceRef addresses one enrolled runner.

The four coordinates are operator-owned enrollment, never task input: the tenant cluster, the edges Service the runner is published as, the edge that Service must still point at, and the runner identity that must answer at the far end. All four are checked before any operation is sent.

Jump to

Keyboard shortcuts

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