agentcli

package
v8.97.2 Latest Latest
Warning

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

Go to latest
Published: Sep 24, 2026 License: Apache-2.0 Imports: 16 Imported by: 0

Documentation

Overview

Package agentcli is what devctl's agent-facing commands share: the JSON envelope every one of them prints as its only stdout output, the exit-code table, the clock that DEVCTL_TIME_SCALE speeds up for tests, the endpoint configuration read from the environment, the --progress writer and the retrying transport under the API clients.

An agent-facing command blocks, prints one JSON document on stdout when it finishes and nothing else, and exits with a code from the table below. Progress, when asked for with --progress, goes to stderr.

Index

Constants

View Source
const (
	// EnvGitHubAPIURL is the GitHub REST API (https://api.github.com).
	EnvGitHubAPIURL = "DEVCTL_GITHUB_API_URL"
	// EnvGitHubOAuthURL is the host of GitHub's device-flow endpoints
	// (https://github.com).
	EnvGitHubOAuthURL = "DEVCTL_GITHUB_OAUTH_URL"
	// EnvCircleCIAPIURL is the CircleCI API v2 (https://circleci.com/api/v2).
	EnvCircleCIAPIURL = "DEVCTL_CIRCLECI_API_URL"
	// EnvCircleCIOAuthURL is CircleCI's OAuth issuer (https://app.circleci.com).
	EnvCircleCIOAuthURL = "DEVCTL_CIRCLECI_OAUTH_URL"
	// EnvRegistryPublic is the public registry, probed anonymously.
	EnvRegistryPublic = "DEVCTL_REGISTRY_PUBLIC"
	// EnvRegistryPrivate is the private registry, read with the docker keychain.
	EnvRegistryPrivate = "DEVCTL_REGISTRY_PRIVATE"
	// EnvRegistryInsecure set to 1 talks plain HTTP to the registries (tests only).
	EnvRegistryInsecure = "DEVCTL_REGISTRY_INSECURE"
	// EnvMusterURL is the muster MCP endpoint `devctl auth login --muster-only`
	// logs in to, and the one the repo commands reach giantswarm-repo-manager
	// through (https://muster.gazelle.awsprod.gigantic.io/mcp).
	EnvMusterURL = "DEVCTL_MUSTER_URL"
	// EnvKeyringFile names a 0600 JSON file that replaces the OS keychain
	// (tests only).
	EnvKeyringFile = "DEVCTL_KEYRING_FILE"
)

The environment variables that point an agent-facing command at another site or at a test double. Every default is the production endpoint.

View Source
const (
	// ExitOK: the wait ended green, the merge happened, the release is
	// available or none follows the merge.
	ExitOK = 0
	// ExitRed: a check is red or the tag's CI failed.
	ExitRed = 1
	// ExitTimeout: the deadline passed; the document names what was unfinished.
	ExitTimeout = 2
	// ExitNotApplicable: draft, closed, conflicting, behind a strict base, a
	// version that does not resolve.
	ExitNotApplicable = 3
	// ExitRequiredMissing: a required context never reported.
	ExitRequiredMissing = 4
	// ExitRefused: the command declines (another author, an opt-out).
	ExitRefused = 5
	// ExitReleaseFailed: merged, and the release the merge triggered failed:
	// its auto-release run or its tag's CI.
	ExitReleaseFailed = 6
	// ExitUsage: wrong usage or a tooling failure.
	ExitUsage = 7
	// ExitAuthRequired: no usable token; the reason names `devctl auth login`.
	ExitAuthRequired = 8
	// ExitReleaseUnconfirmed: merged, and the release was not confirmed
	// pullable: the release timeout passed first, or the release wait could
	// not judge it.
	ExitReleaseUnconfirmed = 9
)

The exit codes of every agent-facing command. 6 and 9 say that devctl pr merge merged: they are never a reason to merge again.

View Source
const (
	RetryAttempts       = 8
	RetryBackoff        = 2 * time.Second
	RetryBackoffCeiling = 60 * time.Second
	// RetryAttemptTimeout bounds one try, the answer's body included; it is
	// a network timeout and not scaled.
	RetryAttemptTimeout = 60 * time.Second
)

The retry budget of one read at scale 1: RetryAttempts tries, the pause before the first retry RetryBackoff, doubling up to RetryBackoffCeiling, about three minutes of pauses in all.

View Source
const EnvTimeScale = "DEVCTL_TIME_SCALE"

EnvTimeScale multiplies every sleep and timeout of an agent-facing command. Production runs at 1; the e2e suite runs at 0.001 so a five-minute wait takes 300 milliseconds. Timestamps are never scaled.

View Source
const RateLimitSecondaryPause = time.Minute

RateLimitSecondaryPause is how long a read refused for a secondary rate limit waits when the answer names no time: GitHub's advice is at least a minute.

View Source
const SchemaVersion = 1

SchemaVersion is the version of the envelope; a breaking change to any command's document bumps it.

Variables

This section is empty.

Functions

func AgentFacing added in v8.91.5

func AgentFacing() map[string]string

AgentFacing is the annotation of an agent-facing command: the checks that precede other commands (the version gate) run inside it and end in its document, never as an error printed for a person.

func Emit

func Emit(w io.Writer, document any) error

Emit writes document as one indented JSON document followed by a newline.

func Exit

func Exit(err error) int

Exit maps err to the process exit code: 0 for nil, the code of an ExitCoder anywhere in the chain, ExitUsage for any other error.

func FlagError added in v8.91.4

func FlagError(command string, err error) error

FlagError is the error of a flag cobra could not parse, as a command's reason: the parser's message and where the flags are listed. The command reports it with its document, exit 7, like any other wrong call.

func IsAgentFacing added in v8.91.5

func IsAgentFacing(annotations map[string]string) bool

IsAgentFacing reports whether a command's annotations carry AgentFacing.

func ProgressFlag

func ProgressFlag(cmd *cobra.Command, v *bool)

ProgressFlag registers --progress on cmd, bound to v.

func Report added in v8.91.4

func Report(w io.Writer, doc Document, ok Verdict, err error) error

Report finishes doc from the command's error, the ok verdict when there is none, writes it on w and returns what the command returns to cobra: nil on exit 0, otherwise the *ExitError the process exit code follows.

Types

type Clock

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

Clock is the time source of a command: the current time, and sleeps and timeouts scaled by EnvTimeScale.

func NewClock

func NewClock(scale float64, now func() time.Time) Clock

NewClock is a clock with an explicit scale and time source; now nil means the wall clock.

func SystemClock

func SystemClock() (Clock, error)

SystemClock is the wall clock at the scale of EnvTimeScale (1 when unset).

func (Clock) Now

func (c Clock) Now() time.Time

Now is the current time, unscaled.

func (Clock) Scale

func (c Clock) Scale() float64

Scale is the factor applied to durations.

func (Clock) Scaled

func (c Clock) Scaled(d time.Duration) time.Duration

Scaled is d at the clock's scale, never below one millisecond for a positive d.

func (Clock) Sleep

func (c Clock) Sleep(ctx context.Context, d time.Duration) error

Sleep waits for the scaled d or until ctx ends, whichever comes first, and returns ctx's error in the second case.

func (Clock) Timeout

Timeout derives a context that ends after the scaled d.

type Document added in v8.91.4

type Document interface {
	Finish(now time.Time, ok Verdict, err error)
	Err() error
}

Document is a command's JSON document: a pointer to a struct that embeds Envelope and adds the command's own fields.

type Endpoints

type Endpoints struct {
	GitHubAPIURL     string
	GitHubOAuthURL   string
	CircleCIAPIURL   string
	CircleCIOAuthURL string
	RegistryPublic   string
	RegistryPrivate  string
	RegistryInsecure bool
	// MusterURL is the muster MCP endpoint of the installation that runs
	// giantswarm-repo-manager.
	MusterURL string
	// KeyringFile is empty for the OS keychain.
	KeyringFile string
}

Endpoints is where the agent-facing commands talk to.

func DefaultEndpoints

func DefaultEndpoints() Endpoints

DefaultEndpoints are the production endpoints.

func EndpointsFromEnv

func EndpointsFromEnv() Endpoints

EndpointsFromEnv are the defaults with every set variable applied. URLs lose their trailing slash.

type Envelope

type Envelope struct {
	Command       string    `json:"command"`
	SchemaVersion int       `json:"schemaVersion"`
	ExitCode      int       `json:"exitCode"`
	Verdict       Verdict   `json:"verdict"`
	Reason        string    `json:"reason"`
	Warnings      []string  `json:"warnings"`
	StartedAt     time.Time `json:"startedAt"`
	FinishedAt    time.Time `json:"finishedAt"`
}

Envelope is the head of every command's JSON document. A command's document embeds it and adds its own fields.

func NewEnvelope

func NewEnvelope(command string, now time.Time) Envelope

NewEnvelope starts the envelope of command at now.

func (Envelope) Err

func (e Envelope) Err() error

Err is the error a command returns to cobra after emitting its document: nil on exit 0, otherwise an *ExitError carrying the envelope's code. The process exit code follows it; nothing is printed for it, the document was.

func (*Envelope) Finish

func (e *Envelope) Finish(now time.Time, ok Verdict, err error)

Finish completes the envelope at now from the command's error: nil is exit 0 with the ok verdict, an ExitCoder carries its own code and verdict, and anything else is a tooling failure (ExitUsage).

func (*Envelope) Warn

func (e *Envelope) Warn(message string)

Warn appends a warning; an empty message is ignored.

type ExitCoder

type ExitCoder interface {
	error
	ExitCode() int
	ExitVerdict() Verdict
}

ExitCoder is an error that knows its place in the exit-code table.

type ExitError

type ExitError struct {
	Code    int
	Verdict Verdict
	Reason  string
}

ExitError is an outcome with its exit code: what a command returns after its document is written, and what any error of the table can be expressed as.

func NewExitError

func NewExitError(code int, verdict Verdict, format string, args ...any) *ExitError

NewExitError returns an ExitError with a formatted reason.

func (*ExitError) Error

func (e *ExitError) Error() string

func (*ExitError) ExitCode

func (e *ExitError) ExitCode() int

ExitCode implements ExitCoder.

func (*ExitError) ExitVerdict

func (e *ExitError) ExitVerdict() Verdict

ExitVerdict implements ExitCoder.

type Progress

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

Progress writes one line per step to stderr when --progress is set and nothing otherwise. Stdout stays the document's.

func NewProgress

func NewProgress(w io.Writer, enabled bool) *Progress

NewProgress writes to w; a nil w or enabled false discards.

func (*Progress) Printf

func (p *Progress) Printf(format string, args ...any)

Printf writes one line.

type RateLimitedError added in v8.92.1

type RateLimitedError struct {
	// Limit is the answer, the limit it named and when it resets.
	Limit string
	// Deadline is the caller's, on the unscaled clock.
	Deadline time.Time
}

RateLimitedError is a read refused for a rate limit that resets only after the caller's deadline: sleeping to the deadline would not reach the reset, so the wait ends at once, a timeout (exit 2) whose reason names the reset. The HTTP client wraps it with the method and URL.

func (*RateLimitedError) Error added in v8.92.1

func (e *RateLimitedError) Error() string

func (*RateLimitedError) ExitCode added in v8.92.1

func (e *RateLimitedError) ExitCode() int

ExitCode implements ExitCoder.

func (*RateLimitedError) ExitVerdict added in v8.92.1

func (e *RateLimitedError) ExitVerdict() Verdict

ExitVerdict implements ExitCoder.

type RetriesExhaustedError added in v8.91.3

type RetriesExhaustedError struct {
	Attempts int
	// Last is the last failure: the transport error, the 5xx status or the
	// rate limit.
	Last string
}

RetriesExhaustedError is a read that failed on every try. The HTTP client wraps it with the method and URL.

func (*RetriesExhaustedError) Error added in v8.91.3

func (e *RetriesExhaustedError) Error() string

type Retrying added in v8.91.3

type Retrying struct {
	// Base sends the requests; nil means http.DefaultTransport.
	Base http.RoundTripper
	// Clock scales the pauses; zero means the wall clock at scale 1.
	Clock Clock
	// Progress receives one line per retry; nil is silent.
	Progress *Progress
	// Warn receives one line per retried failure, with its time, for the
	// document's warnings; nil drops them.
	Warn func(message string)
	// Attempts, Backoff, BackoffCeiling and AttemptTimeout default to the
	// Retry constants.
	Attempts       int
	Backoff        time.Duration
	BackoffCeiling time.Duration
	AttemptTimeout time.Duration
	// contains filtered or unexported fields
}

Retrying is the http.RoundTripper under the API clients of a wait: a read that GitHub or CircleCI did not answer -- a reset connection, an EOF, a timeout, a 5xx -- is sent again after a backoff instead of ending the wait, and a read refused for a rate limit is sent again once the limit resets (see [rateLimited]). Every poll reads the same state again, so one such failure is not an outcome; one that persists through RetryAttempts tries in a row is, and the error then names the request, the count and the last failure. The caller's context bounds the retries: the wait's own deadline ends them, and a rate limit that resets only after it ends the wait at once with a *RateLimitedError.

Only GET and HEAD are retried, and a GET's body is read within its try, so a connection that breaks in the middle of an answer is retried too. Other methods pass through untouched: a write is not repeated on a guess.

func (*Retrying) RoundTrip added in v8.91.3

func (r *Retrying) RoundTrip(req *http.Request) (*http.Response, error)

RoundTrip implements http.RoundTripper.

type Verdict

type Verdict string

Verdict is the one-word outcome of a command.

const (
	VerdictGreen              Verdict = "green"
	VerdictRed                Verdict = "red"
	VerdictTimeout            Verdict = "timeout"
	VerdictNotApplicable      Verdict = "not_applicable"
	VerdictRequiredMissing    Verdict = "required_missing"
	VerdictRefused            Verdict = "refused"
	VerdictAvailable          Verdict = "available"
	VerdictCIFailed           Verdict = "ci_failed"
	VerdictNoRelease          Verdict = "no_release"
	VerdictReleaseFailed      Verdict = "release_failed"
	VerdictReleaseUnconfirmed Verdict = "release_unconfirmed"
	VerdictAuthRequired       Verdict = "auth_required"
	VerdictUsage              Verdict = "usage"
)

The verdicts a command reports.

func Outcome added in v8.91.0

func Outcome(err error) (int, Verdict)

Outcome is err's place in the exit-code table: the code and verdict of an ExitCoder anywhere in the chain, ExitUsage for any other error. err must not be nil.

Jump to

Keyboard shortcuts

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