Documentation
¶
Overview ¶
Package attach is the seam behind an attached transport: a live resource inside a workspace bound to one external connection for as long as a lease allows, with the stack having authorized the binding ONCE rather than running on every byte.
Two Kinds exist. "pty" — `workspace://<name>/attach` binds a pseudo-terminal to the WebSocket session whose run made the op, and from then on the personality pumps frames straight to and from the process. "service" — `workspace://<name>/connect` binds a byte stream to one of the workspace's own loopback services (a VNC display, say), named from a chassis-owned table; the same registry, lease, metering and teardown funnel carry it, and the personality cannot tell the two apart.
The processor creates bindings (it has the tenant, the workspace manager and the usage sink); the websocket personality pumps them (it has the socket). Neither imports the other's package for this — both import this one, the way processor.Unit.Workspaces is shared.
Index ¶
Constants ¶
const ( KindPTY = "pty" KindService = "service" )
Kinds of attached resource.
const ( ControlResize = "resize" ControlSignal = "signal" ControlDetach = "detach" // handled by the personality: never reaches Conn )
Control message types a client may send on the text channel; the personality decodes the envelope and calls Conn.Control with the type and the raw JSON.
Variables ¶
var ( ErrAlreadyBound = errors.New("attach: session already has an attachment") ErrNotBound = errors.New("attach: session has no attachment") )
Errors the Registry returns.
Functions ¶
This section is empty.
Types ¶
type Attachment ¶
type Attachment struct {
ID string
Tenant string
AppStack string
Workspace string
Kind string
Service string
SessionID string
NodeID string
RunID string
Computer string
StartedAt time.Time
ExpiresAt time.Time
// Conn is the live resource; set by the caller right after New (the
// session is started on Context, which New must create first).
Conn Conn
// BytesIn counts client → resource bytes, BytesOut resource → client;
// the pump adds, the heartbeat reads.
BytesIn atomic.Int64
BytesOut atomic.Int64
// contains filtered or unexported fields
}
Attachment is one granted binding: who holds it, what it is bound to, how long it may live, and the counters the lease's metering reads.
func New ¶
func New(parent context.Context, p Params) *Attachment
New builds an attachment whose lifetime is its own context, derived from parent WITHOUT its cancellation: the op that grants a binding returns in milliseconds, and the binding must outlive that run. It ends on Close — detach, expiry, session teardown, shutdown.
func (*Attachment) Close ¶
func (a *Attachment) Close(reason string)
Close ends the attachment once: cancels the context (killing the process), closes the Conn, and records why. Safe from any goroutine.
func (*Attachment) Closed ¶
func (a *Attachment) Closed() bool
Closed reports whether Close has run.
func (*Attachment) Context ¶
func (a *Attachment) Context() context.Context
Context ends when the attachment does. Start the session's process on it so that ending the attachment kills the process.
func (*Attachment) Expired ¶
func (a *Attachment) Expired(now time.Time) bool
Expired reports whether the lease's max_duration has passed at now.
func (*Attachment) Reason ¶
func (a *Attachment) Reason() string
Reason is why the attachment ended ("" while it lives).
type Conn ¶
type Conn interface {
io.ReadWriteCloser
Control(ctx context.Context, typ string, raw []byte) error
Wait() (exit int, err error)
}
Conn is one live resource bound to one external transport. Read is resource → client, Write is client → resource; Control carries the typed out-of-band messages (resize, signal); Wait reports how the resource ended. Close ends it, whatever it is doing, and is idempotent.
func NewPTY ¶
func NewPTY(sess workspace.ExecSession) Conn
NewPTY binds a running TTY session as a Conn: Read drains the terminal, Write feeds it, resize and signal are the two controls.
type Params ¶
type Params struct {
ID string // the lease id; the handle in logs and admin views
Tenant string
AppStack string // the workspace's owning stack (workspace.AppStack)
Workspace string
Kind string // KindPTY | KindService
Service string // KindService: the service name the binding reached
SessionID string // the WebSocket session holding the socket
NodeID string // the chassis node holding that socket
RunID string // the workspace run the resource lives in
Computer string // the provider's reference for the workspace
StartedAt time.Time
ExpiresAt time.Time
}
Params are the plain fields New needs to build an Attachment. Kept separate from Attachment so the atomics never travel by value (go vet).
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry maps a session to its one attachment. A session may hold at most one — a second attach on a bound session is refused, and the terminal's own multiplexing (tmux) is where several processes go.
func (*Registry) Bind ¶
func (r *Registry) Bind(a *Attachment) error
Bind registers a for its session; ErrAlreadyBound if one is live there.
func (*Registry) List ¶
func (r *Registry) List(tenant string) []*Attachment
List snapshots the live attachments, every tenant when tenant is "".