runner

package
v0.1.1 Latest Latest
Warning

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

Go to latest
Published: Jun 1, 2026 License: MIT Imports: 17 Imported by: 0

Documentation

Overview

Package runner orchestrates a single test dispatch end-to-end: upload the test directory over SFTP, exec the per-OS entrypoint, collect logs and artifacts, and persist the run lifecycle to the runs.Store. The runner is invoked by the `testfleet run` CLI and by future automation surfaces.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Dispatch

func Dispatch(ctx context.Context, deps Deps, req Request) (*runs.Run, error)

Dispatch orchestrates one run end-to-end.

Idempotency (Gap 6): if a run with the same (testDirHash, machine) already exists in pending/running state, that run-id is returned unchanged. Terminal runs (completed/failed/interrupted/timeout) do NOT dedupe — the caller asked again so we run again.

On preflight errors (missing entrypoint, machine busy with different hash) a *DispatchError is returned and NO run is persisted. Once the run is created the lifecycle is recorded into the store and (run, nil) is always returned; the caller branches on run.Status / run.ErrorCode.

func Execute

func Execute(ctx context.Context, deps Deps, run *runs.Run, req Request, entrypoint string) *runs.Run

Execute performs the SSH work for a pending run: dial, upload, exec, download, finalize. It mutates the Run record in the store as it goes and returns the final run. Use this with a fresh pending run produced by Prepare(ready=true). Safe to invoke in a separate process (the _worker subcommand) for true async dispatch.

func HashTestDir

func HashTestDir(dir string) (string, error)

HashTestDir returns a deterministic sha256 hex digest of the file contents rooted at dir. The algorithm: walk dir in sorted order, for each regular file write "<relative posix path>\0<sha256 of contents>\n" into a rolling hash; final hex digest is the testDirHash.

The path separator is normalized to "/" so the same tree hashes identically across OSes.

func Prepare

func Prepare(_ context.Context, deps Deps, req Request) (run *runs.Run, ready bool, hash string, entrypoint string, err error)

Prepare runs the preflight + persistence steps of a dispatch:

  • hash test dir
  • resolve entrypoint (early-fail on missing run.ps1/run.sh)
  • idempotency check (same testDirHash+machine, non-terminal → reuse)
  • machine-busy check
  • create pending Run record + state dirs

It does NOT touch SSH. The returned (run, ready=true) means a fresh pending run was created and the caller is responsible for invoking Execute to do the actual SSH work. (run, ready=false) means an existing non-terminal run was reused (idempotency hit) and no new work should be started.

On preflight errors a *DispatchError is returned and no run is persisted.

func RemoteExecCommand

func RemoteExecCommand(entrypoint, remoteRunDir, osName string) string

RemoteExecCommand builds the invocation string used over SSH given the entrypoint filename, the remote run directory, and the target OS.

The contract (per skills/testfleet/SKILL.md) is that the script writes artifacts to a *relative* `./artifacts/` directory — so we must run the script with its parent directory as the working directory. PowerShell `-File` does NOT change cwd, so we wrap with `Set-Location` first.

remoteRunDir is the POSIX-style directory the entrypoint lives in on the remote host (the SFTP server speaks POSIX even on Windows).

func ResolveEntrypoint

func ResolveEntrypoint(testDir, osName string) (string, error)

ResolveEntrypoint returns the relative filename of the entrypoint script that the runner will invoke on the remote host. windows → run.ps1, anything else → run.sh. If the file does not exist locally the function returns an *EntrypointError carrying output.ErrMissingEntrypoint.

Types

type Deps

type Deps struct {
	Store          *runs.Store
	SSH            SSHDispatcher
	Logger         *slog.Logger
	StateDir       string // local state dir; logs/artifacts land under <StateDir>/runs/<run-id>/
	SSHKey         string
	SSHUser        string
	SSHPort        int    // 0 → 22
	KnownHostsPath string // path to known_hosts file
}

Deps is the dependency bundle passed to Dispatch.

type DispatchError

type DispatchError struct {
	Code    output.ErrorCode
	Message string
	Err     error
}

DispatchError carries the structured error code returned to the CLI on pre-flight failures (missing entrypoint, machine busy, etc.). Once a Run is persisted, failures are recorded into the Run record itself and the (Run, nil) pair is returned; the caller checks Run.Status.

func (*DispatchError) Error

func (e *DispatchError) Error() string

func (*DispatchError) Unwrap

func (e *DispatchError) Unwrap() error

type EntrypointError

type EntrypointError struct {
	Code    output.ErrorCode
	OS      string
	TestDir string
	Want    string
}

EntrypointError indicates that the per-OS entrypoint script is missing from the test directory. Code is always output.ErrMissingEntrypoint so callers can surface it as the public error envelope.

func (*EntrypointError) Error

func (e *EntrypointError) Error() string

type RealDispatcher

type RealDispatcher struct{}

RealDispatcher implements SSHDispatcher against internal/ssh.

func (RealDispatcher) Dial

func (RealDispatcher) Dial(_ context.Context, host string, port int, user, keyPath, knownHostsPath string) (SSHSession, error)

Dial opens an SSH client and wraps it in a realSession.

type Request

type Request struct {
	Machine machines.Machine
	TestDir string        // local path to the test directory
	Timeout time.Duration // exec timeout (0 → no timeout)
	Host    string        // host:port to dial; if empty, uses machine.Name
}

Request is the input to Dispatch.

type SSHDispatcher

type SSHDispatcher interface {
	Dial(ctx context.Context, host string, port int, user, keyPath, knownHostsPath string) (SSHSession, error)
}

SSHDispatcher is the narrow SSH/SFTP surface the runner needs.

Production wires RealDispatcher (over internal/ssh). Tests inject a fake session that returns canned stdout/stderr/exit-code so the runner orchestration can be exercised without an SSH server.

type SSHSession

type SSHSession interface {
	UploadDir(ctx context.Context, localDir, remoteDir string, timeout time.Duration) error
	DownloadDir(ctx context.Context, remoteDir, localDir string, timeout time.Duration) error
	Run(ctx context.Context, cmd string, stdin io.Reader, timeout time.Duration) (stdout, stderr []byte, exitCode int, err error)
	Close() error
}

SSHSession is the per-connection surface used after Dial.

UploadDir / DownloadDir / Run each honour their own timeout (0 = no timeout) and the parent ctx. Run returns (stdout, stderr, exitCode, err); err is non-nil for transport-level failures and ctx cancellation. A non-zero exit code is surfaced via exitCode with nil err — mirroring internal/ssh.Run.

Jump to

Keyboard shortcuts

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