client

package
v0.3.0-20260818173219-... Latest Latest
Warning

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

Go to latest
Published: Aug 18, 2026 License: Apache-2.0 Imports: 19 Imported by: 0

README

client

The SubmitQueue client: dialling a gateway, the calls made against it, and the terminal view of what a queue is doing.

It exists so the tools are thin. A binary under service/ is flag parsing over this package, which is what keeps the gateway CLI and the demo from each growing their own dialling, their own strategy parsing, and their own status table — as they had begun to.

Connecting

Options is a plain struct, not a set of flags: binaries parse their own flags and fill it, so the package stays usable from a test or another program.

Addr is handed to the dialler untouched, so the full gRPC target syntax works — a plain host:port, but also dns:///host:port or unix:///path.sock. Transport security is a separate option rather than something encoded in the address, because gRPC keeps target resolution and transport credentials apart; there is no scheme that means "use TLS", and inventing one would only mislead.

TokenEnv names the variable holding a bearer token rather than carrying the token, so a credential never reaches a command line. An unset variable is not an error — it is how a client against a gateway that wants no credential runs, which today is every gateway in this repository. The token is for one reached through something that does check it: a proxy, a sidecar, an ingress terminating auth ahead of the service.

The view

A Row is one land request and everything shown about it. A Tracker owns a set of rows, polls their histories, and redraws as they move; Draw renders once, for a listing that is not following anything.

Two things move a run forward at once — whatever is producing requests, and the poll reading their statuses — and both draw the same table, so the tracker's mutex is what keeps one from redrawing halfway through the other's update. Reads happen outside that lock: holding it across a round of RPCs would stall the producer behind the network, and letting the producer run ahead is the whole point of watching each request the moment it exists.

The renderer draws in place on a terminal and appends a fresh block when piped, so redirecting to a file gives something readable rather than escape codes. Piped output skips a redraw when nothing in the table moved, which is why the signature it compares leaves out the elapsed clock: that advances every second, and a log reprinting the table for it alone would say nothing while saying it often.

Column widths only ever grow, so a value wider than its header does not make the table jitter as rows fill in.

The changes column

Each row carries []Cell, and a cell is text with an optional URL. The column is supplied by the caller rather than derived, because what identifies a change depends on who is watching: a tool that just opened the pull requests knows their numbers and can link to them, while a client watching a queue it did not create knows only the change URIs the gateway reports. RowsFromSummaries builds the second kind from a listing.

On a terminal a cell with a URL is rendered as an OSC 8 hyperlink; piped, the address itself is printed, since a log has nothing to click and the address is the part worth copying. Padding counts on-screen width rather than string length — a hyperlink is mostly escape bytes that occupy no columns, and padding by length would shove the table sideways.

Settling

Tracker.Seal declares that nothing further will join the set, which is what lets an otherwise-finished run conclude; without it a poll finding nothing outstanding before the first request existed would call the run finished. Conclude draws the verdict and returns an error naming every request that ended anywhere other than landed, so a scripted caller notices.

Documentation

Overview

Package client is the SubmitQueue client: dialling a gateway, the calls a caller makes against it, and the terminal view of what a queue is doing.

It exists so the tools are thin. A binary here is flag parsing over this package, which is what keeps the gateway CLI and the demo from growing their own dialling, their own strategy parsing, and their own status table.

Index

Constants

View Source
const DefaultTokenEnv = "SQ_TOKEN"

DefaultTokenEnv is the environment variable a client reads its bearer token from unless told otherwise.

Variables

This section is empty.

Functions

func Draw

func Draw(rows []*Row, status string)

Draw renders the table once and returns. It is what a one-shot listing wants; a caller following requests as they move uses a Tracker instead, which owns the rows and redraws them.

func ParseStrategy

func ParseStrategy(name string) (mergestrategypb.Strategy, error)

ParseStrategy maps a strategy name to its wire value. An empty name selects the queue's configured default.

func WithTimeout

func WithTimeout(ctx context.Context, timeout time.Duration) (context.Context, context.CancelFunc)

WithTimeout derives a context carrying timeout, or the parent unchanged when timeout is not positive.

A non-positive timeout means "no deadline", which is what a watch needs: it runs until its queue settles or the operator stops it, and a deadline meant for a single call would cut it short.

Types

type Cell

type Cell struct {
	// Text is what the reader sees on a terminal.
	Text string
	// URL is where the text points. Empty when the caller has no address for
	// it, in which case Text is shown as-is in both modes.
	URL string
}

Cell is one entry in a row's changes column: what to show, and optionally where it points.

The column is caller-supplied rather than derived, because what identifies a change depends on who is watching. A tool that just created pull requests knows their numbers and can link to them; a client watching a queue it did not create knows only the change URIs the gateway reports. Both render through the same cell.

type Client

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

Client is a connected gateway client.

func New

func New(opts Options) (*Client, error)

New dials the gateway described by opts.

The caller closes the returned client. Dialling is lazy, as gRPC prefers, so an unreachable address surfaces on the first call rather than here.

func (*Client) Close

func (c *Client) Close() error

Close releases the connection.

func (*Client) Gateway

func (c *Client) Gateway() pb.SubmitQueueGatewayClient

Gateway is the generated client underneath, for a call this package does not wrap yet. Prefer the wrappers; this is the escape hatch, not the interface.

func (*Client) History

func (c *Client) History(ctx context.Context, queue, sqid string) ([]*pb.HistoryEvent, error)

History reads the events recorded for one request, oldest first.

func (*Client) Land

func (c *Client) Land(
	ctx context.Context,
	queue string,
	uris []string,
	strategy mergestrategypb.Strategy,
) (string, error)

Land puts a change on a queue and returns the request id tracking it.

The URIs are one change, in caller order: several of them are a stack landing as a single request, not several requests.

func (*Client) List

func (c *Client) List(ctx context.Context, q ListQuery) ([]*pb.RequestSummary, error)

List reads a queue's requests, newest first, following continuation tokens until the limit is reached or the queue is exhausted.

Paging is followed here rather than exposed, because a caller asking what a queue is doing wants the answer, not a cursor. A caller that needs the cursor can reach the generated client through Gateway.

func (*Client) Ping

func (c *Client) Ping(ctx context.Context, message string) (*pb.PingResponse, error)

Ping checks the gateway answers, returning its reply.

func (*Client) Summary

func (c *Client) Summary(ctx context.Context, queue, sqid string) (*pb.RequestSummary, error)

Summary reads one request's current status.

type HistorySource

type HistorySource interface {
	GetRequestHistoryByID(
		ctx context.Context,
		in *pb.GetRequestHistoryByIDRequest,
		opts ...grpc.CallOption,
	) (*pb.GetRequestHistoryByIDResponse, error)
}

HistorySource is the part of the gateway a watch reads from.

It is narrowed to the one call rather than taking the generated client whole, so a caller watching requests can be handed something that only knows how to answer for them. The generated client satisfies it as it stands.

type ListQuery

type ListQuery struct {
	// Queue is the exact queue to read. Required: the gateway has no
	// cross-queue listing, because a request id is only resolvable within its
	// own queue.
	Queue string

	// Since bounds the window to requests received within it, ending at the
	// time of the call. Zero reads from the beginning of retained history.
	Since time.Duration

	// Limit caps how many requests are returned across all pages. Zero means
	// every request in the window, which for a busy queue is a lot of paging.
	Limit int

	// PageSize is what each page requests. Zero takes the server default.
	PageSize int
}

ListQuery selects a page range of a queue's receipt history.

type Options

type Options struct {
	// Addr is the gRPC target. It is passed to the dialler untouched, so the
	// full target syntax works — a plain host:port, but also dns:///host:port
	// or unix:///path/to.sock.
	Addr string

	// TLS dials with transport security instead of plaintext. Off by default,
	// which is what a local stack wants and nothing else should.
	TLS bool

	// TokenEnv names the environment variable holding a bearer token. The
	// variable is named rather than the token passed directly, so a credential
	// never reaches a command line, where it would be visible in the shell
	// history and to anyone running ps. Empty sends no credential.
	TokenEnv string
}

Options is how a caller reaches a gateway.

It is a plain struct rather than a set of flags so the package stays usable from a test or another program; binaries parse their own flags and fill it.

type Row

type Row struct {
	// Cells are what the changes column shows for this request, in caller order.
	Cells []Cell

	// SQID is empty until the gateway accepts the request.
	SQID string
	// Submitted is when the gateway accepted it, and starts the elapsed clock.
	Submitted time.Time
	// Settled is when a terminal status was first observed, and stops it.
	Settled time.Time

	// Trail is the ordered set of statuses the gateway recorded for the request.
	Trail  []string
	Status string
	Note   string
	Done   bool
}

Row is one land request and everything shown about it. A row exists from the first draw, before the request it will carry has been accepted, so the table never changes shape while a run is in progress.

func NewRows

func NewRows(n int) []*Row

NewRows allocates n empty rows, so the table has its final shape before anything has been accepted.

func RowsFromSummaries

func RowsFromSummaries(summaries []*pb.RequestSummary) []*Row

RowsFromSummaries builds watchable rows from a listing, newest first.

The changes column carries each request's change URIs, which is all a client watching a queue it did not create knows about them. A caller that knows more — a tool that just opened the pull requests, say — sets richer cells of its own.

A summary records when a request was received but not when it settled, so a row built here has no settle time and its elapsed column reads as age since receipt. For a request still in flight that is the same number; for one that finished long ago it is how long ago, which is the useful thing to show in a listing anyway.

type Tracker

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

Tracker owns the rows and the table drawn from them.

Two things move a run forward at once — whatever is producing requests, and the poll that reads their statuses — and both draw the same table, so the mutex is what keeps one from redrawing halfway through the other's update.

func NewTracker

func NewTracker(rows []*Row) *Tracker

NewTracker returns a Tracker over the given rows.

func (*Tracker) Conclude

func (t *Tracker) Conclude() error

Conclude draws the verdict and reports whether everything landed. It reads the rows under the lock because a poll may still be applying its last round.

func (*Tracker) Interact

func (t *Tracker) Interact(ctx context.Context) (stop func(), quit <-chan struct{})

Interact puts the terminal into a scrollable full-screen view for as long as the watch runs, and reports when the reader asks to leave.

It is optional and degrades rather than fails: without a terminal on both ends there is nothing to take over and nobody to read a key, so the caller gets a no-op stop and a channel that never fires, and the plain redraw is used exactly as before.

func (*Tracker) Note

func (t *Tracker) Note(format string, args ...any)

Note replaces the line under the table and redraws.

func (*Tracker) Poll

func (t *Tracker) Poll(ctx context.Context, src HistorySource, queue string)

Poll re-reads statuses until the run finishes or the context ends.

func (*Tracker) Rows

func (t *Tracker) Rows() []*Row

Rows are the rows the tracker draws. Mutate them only from inside Update, which holds the lock the poll also takes.

func (*Tracker) Seal

func (t *Tracker) Seal()

Seal declares that nothing further will be watched, which is what lets an otherwise-finished run conclude.

func (*Tracker) Settled

func (t *Tracker) Settled() <-chan struct{}

Settled closes once every request has reached a terminal status.

func (*Tracker) Update

func (t *Tracker) Update(fn func())

Update applies a change to the rows and redraws with it.

Jump to

Keyboard shortcuts

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