gomoteclient

package
v0.0.0-...-8c1f882 Latest Latest
Warning

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

Go to latest
Published: Jul 10, 2026 License: BSD-3-Clause Imports: 18 Imported by: 0

Documentation

Overview

Package gomoteclient is an ergonomic, concurrency-safe wrapper around the gomote command-line tool (golang.org/x/build/cmd/gomote). It is intended to replace ad-hoc shelling out to the gomote binary with strongly-typed, context-aware, retry-capable command construction.

Transport

The client wraps the gomote CLI; it does not speak the coordinator gRPC protocol directly. The CLI handles authentication, so callers only need a working, authenticated gomote on PATH (or allow the client to bootstrap one). Command execution goes through the unexported runner interface, which both isolates the os/exec dependency for testing and leaves room for an alternative backend in the future.

Security posture

The client never invokes a shell: commands are executed by absolute path with an explicit argument vector, eliminating the shell-injection surface that arises from string concatenation. Inputs that become argv elements (builder types, instance IDs, environment keys) are validated to prevent flag smuggling. The subprocess receives an explicit, curated environment rather than inheriting the host's, and Client.Info redacts secret-looking values.

Bootstrapping (building gomote from source when it is missing) is opt-in via Config.AllowBootstrap. Builds run in an isolated sandbox with module checksum verification left enabled. Pinning Config.GomoteVersion yields a reproducible build; leaving it empty resolves "@latest", which is convenient but not reproducible and is logged as a warning.

Example

c, err := gomoteclient.New(ctx, gomoteclient.Config{})
if err != nil {
	log.Fatal(err)
}
inst, err := c.Create(ctx, gomoteclient.CreateOptions{BuilderType: "gotip-linux-amd64"})
if err != nil {
	log.Fatal(err)
}
defer inst.Destroy(ctx)
res, err := inst.Run(ctx, gomoteclient.RunOptions{
	Dir:     "tmp/sharded",
	Command: "go",
	Args:    []string{"test", "-bench", "."},
})
_ = res

Coverage

The wrapper exposes create, list, run, push, destroy, ping, ls, rm, the put/puttar/putbootstrap upload family, and gettar download. Builder types are the named BuilderType; discover the authoritative set for the connected coordinator with Client.BuilderTypes rather than hard-coding names. The coordinator address is selectable via Config.Server. gomote's interactive (ssh, rdp) and group subcommands are intentionally out of scope: they do not fit a captured-output, single-instance model.

Index

Constants

This section is empty.

Variables

View Source
var ErrInstanceNotFound = errors.New("gomoteclient: instance not found")

ErrInstanceNotFound indicates that the coordinator does not know the target instance: it was never created, already destroyed, or its lease expired. Callers can test for it with errors.Is. It is joined with the underlying *CommandError, so errors.As can still recover the command details.

Functions

This section is empty.

Types

type BuilderType

type BuilderType string

BuilderType is a gomote builder (host) type such as "gotip-linux-amd64". The authoritative set is dynamic and version-dependent; discover it at runtime with Client.BuilderTypes (which runs "gomote create -list") rather than hard-coding names. The named type exists to keep builder identifiers from being confused with arbitrary strings at call sites and to centralize validation.

func (BuilderType) String

func (b BuilderType) String() string

String returns the builder type as a string.

func (BuilderType) Validate

func (b BuilderType) Validate() error

Validate reports whether b is syntactically a valid builder type. It checks shape only; it does not confirm the type exists on the coordinator.

type Client

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

Client is a concurrency-safe handle to a resolved gomote binary together with the policy used to invoke it. A Client is safe for use by multiple goroutines.

func New

func New(ctx context.Context, cfg Config) (*Client, error)

New resolves the gomote binary and returns a ready Client. Resolution order is Config.BinaryPath, then PATH, then bootstrap (only if Config.AllowBootstrap is set). New does not contact the coordinator. The provided context bounds the bootstrap build, if one occurs.

func NewClient

func NewClient(ctx context.Context, opts ...Option) (*Client, error)

NewClient is the functional-options constructor: it assembles a Config from opts and delegates to New. It is equivalent to New with a Config literal and is provided for call sites that prefer a fluent style.

func (*Client) BuilderTypes

func (c *Client) BuilderTypes(ctx context.Context) ([]BuilderType, error)

BuilderTypes returns the builder types the coordinator currently offers, by running "gomote create -list". The result is authoritative for the connected server and should be preferred over hard-coded names.

func (*Client) Create

func (c *Client) Create(ctx context.Context, opt CreateOptions) (*Instance, error)

Create provisions a new buildlet and returns a handle to it. Create is not retried by default (see CreateOptions.RetrySafe).

func (*Client) Info

func (c *Client) Info() Info

Info returns a snapshot of the client's binary, sandbox, tracked instances, and (redacted) subprocess environment. It performs no command execution.

func (*Client) Instance

func (c *Client) Instance(id string) (*Instance, error)

Instance returns a handle to an existing buildlet by ID without contacting the coordinator. The ID is validated for format only; operations on it fail if the buildlet does not actually exist.

func (*Client) List

func (c *Client) List(ctx context.Context) ([]InstanceInfo, error)

List returns the buildlets currently associated with the gomote credentials.

type CommandError

type CommandError struct {
	// Args is the (secret-redacted) argument vector that was executed.
	Args []string
	// ExitCode is the process exit code, or -1 if the process did not start.
	ExitCode int
	// Stderr is the captured standard error of the command.
	Stderr string
	// Err is the underlying execution error.
	Err error
}

CommandError reports a failed gomote invocation. It preserves the captured stderr from the CLI and unwraps to the underlying os/exec error, so callers can use errors.As to inspect *exec.ExitError.

func (*CommandError) Error

func (e *CommandError) Error() string

Error implements error, including the redacted command and stderr tail.

func (*CommandError) Unwrap

func (e *CommandError) Unwrap() error

Unwrap returns the underlying execution error.

type Config

type Config struct {
	// BinaryPath, if set, is an explicit path to a gomote binary. It is
	// validated and resolved to an absolute path. When empty, the client
	// resolves the binary from PATH and then, if AllowBootstrap is true,
	// bootstraps one into SandboxDir.
	BinaryPath string

	// SandboxDir is the directory used to build and cache a bootstrapped
	// gomote. When empty it defaults to <os.UserCacheDir>/gomoteclient.
	SandboxDir string

	// GomoteVersion is the module version installed during bootstrap, for
	// example a tag or pseudo-version of golang.org/x/build. When empty,
	// bootstrap resolves "@latest" (not reproducible; logged as a warning).
	GomoteVersion string

	// AllowBootstrap permits the client to download and build gomote when it
	// is not otherwise available. It defaults to false: a missing binary is
	// then a hard error rather than an implicit network build.
	AllowBootstrap bool

	// Server overrides the coordinator address (gomote's global -server flag,
	// for example "gomote.golang.org:443"). When empty, gomote's built-in
	// default is used. The value is validated to reject argument injection.
	Server string

	// Retry governs transient-failure handling. The zero value is replaced by
	// DefaultRetryPolicy; individual zero fields are filled with defaults.
	Retry RetryPolicy

	// Env is the explicit environment passed to the gomote subprocess. When
	// nil, a curated allowlist is derived from the host environment (see the
	// package documentation). Provide a value to fully control isolation.
	Env []string

	// DefaultTimeout bounds a single command attempt when neither the caller's
	// context nor RetryPolicy.PerAttempt sets a deadline. Zero means no
	// implicit deadline.
	DefaultTimeout time.Duration

	// Logger receives structured, secret-redacted diagnostics. When nil, logs
	// are discarded.
	Logger *slog.Logger
}

Config configures a Client. The zero value is usable: it resolves gomote from PATH (or Config.BinaryPath), applies DefaultRetryPolicy, derives a curated subprocess environment, and discards log output. Bootstrapping is disabled unless AllowBootstrap is set.

type CreateOptions

type CreateOptions struct {
	// BuilderType is the gomote builder type, e.g. "gotip-linux-amd64". Required.
	BuilderType BuilderType
	// SetUpEnv are environment variables applied to the new instance via -e.
	SetUpEnv []EnvVar
	// Setup requests that gomote push GOROOT and build the Go toolchain on the
	// new instance (the -setup flag). It is off by default.
	Setup bool
	// RetrySafe permits transparent retries of Create. It is off by default
	// because a retried create can leak buildlets; enable it only when the
	// caller cleans up orphans.
	RetrySafe bool
}

CreateOptions configures Client.Create.

type EnvVar

type EnvVar struct {
	Key   string
	Value string
}

EnvVar is a typed environment assignment passed via gomote's -e flag.

func (EnvVar) String

func (e EnvVar) String() string

String renders the variable as KEY=VALUE.

type GetOptions

type GetOptions struct {
	// Dir is the remote directory to archive (-dir). Optional.
	Dir string
	// Output is the local path to write the downloaded .tar.gz to. Required.
	Output string
}

GetOptions configures Instance.Get.

type Info

type Info struct {
	// BinaryPath is the absolute path to the resolved gomote binary.
	BinaryPath string
	// BinarySource indicates how the binary was located.
	BinarySource Source
	// SandboxDir is the configured sandbox directory (may be empty).
	SandboxDir string
	// SandboxReady reports whether the binary was built into the sandbox.
	SandboxReady bool
	// GomoteVersion is the module version when known (bootstrap builds), else "".
	GomoteVersion string
	// ActiveInstances are the IDs of instances created via this Client that
	// have not been destroyed, sorted lexically.
	ActiveInstances []string
	// Env is the redacted environment passed to the gomote subprocess.
	Env []string
}

Info is a point-in-time snapshot of a Client's state. The Env field is redacted: values of secret-looking keys are masked.

type Instance

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

Instance is a handle to a single created gomote buildlet. It is safe for concurrent use; its methods are stateless apart from the shared Client.

func (*Instance) BuilderType

func (in *Instance) BuilderType() BuilderType

BuilderType returns the builder type the instance was created from. It is the zero value for instances obtained via Client.Instance.

func (*Instance) Destroy

func (in *Instance) Destroy(ctx context.Context) error

Destroy tears down the instance. It is idempotent: an already-gone instance is treated as success.

func (*Instance) Get

func (in *Instance) Get(ctx context.Context, opt GetOptions) error

Get downloads files from the instance as a tar archive (gomote gettar) and writes them to GetOptions.Output.

gomote gettar writes "<instance>.tar.gz" into its working directory rather than to stdout, so Get runs it inside a private temporary directory and then moves the result to Output. This keeps the download off the caller's working directory and out of any retry-duplicated state.

func (*Instance) ID

func (in *Instance) ID() string

ID returns the gomote instance name.

func (*Instance) Ls

func (in *Instance) Ls(ctx context.Context, opt LsOptions) ([]string, error)

Ls lists the contents of a directory on the instance (gomote ls) and returns the non-empty output lines.

func (*Instance) Ping

func (in *Instance) Ping(ctx context.Context) error

Ping reports whether the instance is alive and reachable (gomote ping). A nil return means healthy; a missing instance reports errors.Is(err, ErrInstanceNotFound).

func (*Instance) Push

func (in *Instance) Push(ctx context.Context) error

Push synchronizes the instance's Go checkout (gomote push).

func (*Instance) Put

func (in *Instance) Put(ctx context.Context, opt PutOptions) error

Put uploads a single file to the instance.

func (*Instance) PutBootstrap

func (in *Instance) PutBootstrap(ctx context.Context) error

PutBootstrap places the bootstrap toolchain on the instance (gomote putbootstrap), a prerequisite for building Go from source on some hosts.

func (*Instance) PutTar

func (in *Instance) PutTar(ctx context.Context, opt PutTarOptions) error

PutTar uploads and extracts a tar archive onto the instance.

func (*Instance) Remove

func (in *Instance) Remove(ctx context.Context, paths ...string) error

Remove deletes files or directories on the instance (gomote rm). Pass "." to delete everything in the work directory. At least one path is required.

func (*Instance) Run

func (in *Instance) Run(ctx context.Context, opt RunOptions) (RunResult, error)

Run executes a command on the instance and returns its captured output. Run is retried only on transient infrastructure errors, never on a non-zero exit from the remote command itself.

type InstanceInfo

type InstanceInfo struct {
	ID  string
	Raw string // the full, unparsed line as printed by gomote list
}

InstanceInfo describes an instance reported by Client.List.

type JitterMode

type JitterMode int

JitterMode selects how backoff delays are randomized.

const (
	// FullJitter (the default) draws each delay uniformly from
	// [0, cappedExponentialDelay], which is the most effective scheme for
	// avoiding synchronized retries across concurrently fanned-out callers.
	FullJitter JitterMode = iota
	// NoJitter uses the capped exponential delay directly.
	NoJitter
)

type LsOptions

type LsOptions struct {
	// Dir is the remote directory to list, relative to the work dir. Optional.
	Dir string
	// Recursive lists directories recursively (-R).
	Recursive bool
	// Digest requests file digests (-d).
	Digest bool
	// Skip is a list of relative directories to skip (-skip), joined with ",".
	Skip []string
}

LsOptions configures Instance.Ls.

type Option

type Option func(*Config)

Option configures a Client built by NewClient. Options are a thin, fluent alternative to constructing a Config literal; they compose left to right.

func WithBinaryPath

func WithBinaryPath(path string) Option

WithBinaryPath pins an explicit gomote binary instead of resolving from PATH.

func WithBootstrap

func WithBootstrap(version string) Option

WithBootstrap enables building gomote from source when it is missing, pinning the given module version (empty resolves "@latest").

func WithDefaultTimeout

func WithDefaultTimeout(d time.Duration) Option

WithDefaultTimeout bounds each command attempt when the caller sets no deadline.

func WithEnv

func WithEnv(env []string) Option

WithEnv sets the explicit subprocess environment, bypassing the curated allowlist.

func WithLogger

func WithLogger(l *slog.Logger) Option

WithLogger directs structured, secret-redacted diagnostics to l.

func WithRetryPolicy

func WithRetryPolicy(p RetryPolicy) Option

WithRetryPolicy installs a custom retry policy.

func WithSandboxDir

func WithSandboxDir(dir string) Option

WithSandboxDir sets the directory used to cache a bootstrapped gomote.

func WithServer

func WithServer(addr string) Option

WithServer overrides the coordinator address (the global -server flag).

type PutOptions

type PutOptions struct {
	// Source is the local file to upload. Required.
	Source string
	// Dest is the destination path on the instance. Optional.
	Dest string
	// Mode optionally sets the destination Unix file mode (-mode). When zero,
	// gomote defaults to the source file's mode.
	Mode os.FileMode
}

PutOptions configures Instance.Put.

type PutTarOptions

type PutTarOptions struct {
	// Dir is the destination directory on the instance (-dir).
	Dir string
	// File is the local .tar.gz archive to upload. Required.
	File string
}

PutTarOptions configures Instance.PutTar.

type RetryPolicy

type RetryPolicy struct {
	// MaxAttempts is the total number of attempts (not counting only retries).
	MaxAttempts int
	// BaseDelay is the first backoff interval.
	BaseDelay time.Duration
	// MaxDelay caps the exponential backoff interval.
	MaxDelay time.Duration
	// Multiplier is the exponential growth factor between attempts.
	Multiplier float64
	// Jitter selects the randomization strategy.
	Jitter JitterMode
	// PerAttempt optionally bounds each individual attempt.
	PerAttempt time.Duration
	// Retryable classifies a failed result as transient (retryable) or not.
	// When nil, defaultRetryable is used.
	Retryable func(res RunResult, err error) bool
}

RetryPolicy controls transient-failure handling for network-dependent gomote commands. The zero value is filled in with the values from DefaultRetryPolicy.

func DefaultRetryPolicy

func DefaultRetryPolicy() RetryPolicy

DefaultRetryPolicy returns the policy applied when Config.Retry is the zero value: four attempts of full-jitter exponential backoff from 500ms to 30s.

type RunOptions

type RunOptions struct {
	// Dir is the working directory on the instance (-dir).
	Dir string
	// Path sets the remote PATH (-path); elements are joined with ",". The
	// gomote tokens $PATH and $WORKDIR are passed through verbatim, and the
	// special element "EMPTY" runs with no $PATH at all.
	Path []string
	// Env are environment variables for the command (-e).
	Env []EnvVar
	// System runs the command in the system context rather than the work
	// directory (-system). gomote also implies this when Command is absolute.
	System bool
	// Debug requests debug information about the command before it runs (-debug).
	Debug bool
	// Firewall enables the outbound firewall for the instance lifetime (-firewall).
	Firewall bool
	// BuilderEnv selects an alternate builder to emulate; it must share the same
	// underlying host type (-builderenv).
	BuilderEnv string
	// Until reruns the command until its output matches this regexp (-until).
	// Bound it with the context to avoid running forever.
	Until string
	// Command is the remote program to execute. Required.
	Command string
	// Args are the remote program's arguments. They may begin with "-".
	Args []string
}

RunOptions configures Instance.Run.

type RunResult

type RunResult struct {
	Stdout   []byte
	Stderr   []byte
	ExitCode int
}

RunResult holds the captured output of a single gomote command invocation.

type Source

type Source int

Source describes how a Client located its gomote binary.

const (
	// SourceUnknown is the zero value and indicates the binary was not resolved.
	SourceUnknown Source = iota
	// SourceExplicit indicates Config.BinaryPath was used.
	SourceExplicit
	// SourcePATH indicates the binary was found via the PATH environment.
	SourcePATH
	// SourceBootstrap indicates the binary was built into the sandbox.
	SourceBootstrap
)

func (Source) String

func (s Source) String() string

String returns a human-readable name for the source.

Jump to

Keyboard shortcuts

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