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
- func AgentFacing() map[string]string
- func Emit(w io.Writer, document any) error
- func Exit(err error) int
- func FlagError(command string, err error) error
- func IsAgentFacing(annotations map[string]string) bool
- func ParsePullRequest(command string, args []string) (owner, repo string, number int, err error)
- func ParseRepository(arg string) (owner, repo string, err error)
- func ParseRepositoryArgument(command, what string, args []string) (owner, repo, value string, err error)
- func ProgressFlag(cmd *cobra.Command, v *bool)
- func Report(w io.Writer, doc Document, ok Verdict, err error) error
- type Clock
- type Document
- type Endpoints
- type Envelope
- type ExitCoder
- type ExitError
- type Progress
- type RateLimitedError
- type RetriesExhaustedError
- type Retrying
- type Verdict
Constants ¶
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.
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, or the head // waits only for Actions runs a member has to approve. 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 // ExitRunning: devctl pr merge status found the detached merge still // running. ExitRunning = 10 )
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.
const ( RetryAttempts = 15 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 ten minutes of pauses in all, so an API outage of that length ends no wait.
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.
const HeartbeatInterval = 2 * time.Minute
HeartbeatInterval is how often a wait without --progress still says on stderr what it is waiting for, so a wait that outlives its caller's patience names its cause instead of staying silent.
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.
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
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 Exit ¶
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
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
IsAgentFacing reports whether a command's annotations carry AgentFacing.
func ParsePullRequest ¶ added in v8.124.0
ParsePullRequest reads the arguments "<owner/repo> <number>" of command.
func ParseRepository ¶ added in v8.124.0
ParseRepository reads "<owner/repo>".
func ParseRepositoryArgument ¶ added in v8.126.0
func ParseRepositoryArgument(command, what string, args []string) (owner, repo, value string, err error)
ParseRepositoryArgument reads the arguments "<owner/repo> <what>" of command: the repository and one more argument, named what in the usage.
func ProgressFlag ¶
ProgressFlag registers --progress on cmd, bound to v.
func Report ¶ added in v8.91.4
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 ¶
NewClock is a clock with an explicit scale and time source; now nil means the wall clock.
func NewVirtualClock ¶ added in v8.109.1
NewVirtualClock is a clock for tests whose time passes only in Sleep, starting at start: a sleep returns at once and moves Now on by d, and a Timeout ends when the sleeps reach it. A deadline never interrupts a call in flight, so the outcome of a wait does not depend on how fast the machine runs it.
func SystemClock ¶
SystemClock is the wall clock at the scale of EnvTimeScale (1 when unset).
func (Clock) Scaled ¶
Scaled is d at the clock's scale, never below one millisecond for a positive d.
type Document ¶ added in v8.91.4
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 ¶
NewEnvelope starts the envelope of command at now.
func (Envelope) Err ¶
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.
type ExitError ¶
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 ¶
NewExitError returns an ExitError with a formatted reason.
func (*ExitError) ExitVerdict ¶
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, without it, only the heartbeat of Progress.Waiting. Stdout stays the document's.
func NewProgress ¶
NewProgress writes every line to w with enabled; without it only the heartbeat goes to w. A nil w discards everything.
func (*Progress) Waiting ¶ added in v8.125.0
Waiting is the heartbeat of a wait without --progress: what the wait still waits for at now, written as "waiting for <what>" once per HeartbeatInterval, and at once when it changed, so the stream names every cause a wait had and no more than one line every two minutes for the same one. With --progress the per-poll lines say it already and Waiting writes nothing.
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.
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" VerdictApprovalRequired Verdict = "approval_required" 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" // VerdictDetached: devctl pr merge --detach started the merge in a // process of its own; the handle reads its outcome. VerdictDetached Verdict = "detached" // VerdictRunning: a detached merge has no outcome yet. VerdictRunning Verdict = "running" // VerdictListed: a read-only view (devctl ci jobs) was read in full; the // document, not the verdict, says where the pipeline stands. VerdictListed Verdict = "listed" )
The verdicts a command reports.