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
- type Client
- func (c *Client) Artifact(ctx context.Context, attemptID, artifactID string, w io.Writer) (runner.Artifact, error)
- func (c *Client) Cancel(ctx context.Context, req runner.CancelRequest) (runner.Receipt, error)
- func (c *Client) Capabilities(ctx context.Context) (runner.Capabilities, error)
- func (c *Client) Events(ctx context.Context, attemptID string, after uint64) (*EventStream, error)
- func (c *Client) Inspect(ctx context.Context, attemptID string) (runner.Receipt, error)
- func (c *Client) Invalidate()
- func (c *Client) Resume(ctx context.Context, req runner.ResumeRequest) (runner.Receipt, error)
- func (c *Client) Start(ctx context.Context, req runner.StartRequest) (runner.Receipt, error)
- type EventStream
- type HTTPError
- type IdentityError
- type Option
- type ServiceRef
Constants ¶
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 ¶
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) Capabilities ¶
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 ¶
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 ¶
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.
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 ¶
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 ¶
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.
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.
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.