Documentation
¶
Index ¶
- Constants
- Variables
- func AddUnknownCommandSuggestions(root *cobra.Command)
- func CheckProfile(rt *module.Runtime) error
- func CheckProfileName(cfg *config.Config, name string) error
- func Confirm(rt *module.Runtime, prompt string, force bool) error
- func ExitCode(err error) int
- func GuardProfile(cmd *cobra.Command, rt *module.Runtime) error
- func IsMutating(cmd *cobra.Command) bool
- func LimitFlagHelp(pd PageDefaults) string
- func MarkMutating(cmd *cobra.Command)
- func NewCompletionCmd() *cobra.Command
- func NewDoctorCmd(rt *module.Runtime, deps *DoctorDeps) *cobra.Command
- func NewEnvCmd(rt *module.Runtime) *cobra.Command
- func NewHelpCmd() *cobra.Command
- func NewInspectCmd(rt *module.Runtime, deps *InspectDeps) *cobra.Command
- func NewLLMSCmd() *cobra.Command
- func NewProfileCmd(rt *module.Runtime) *cobra.Command
- func NewRootCmd(rt *module.Runtime) *cobra.Command
- func NewSelfCmd(rt *module.Runtime, modules []module.ModuleInfo, deps *SelfDeps) *cobra.Command
- func NewVersionCmd(rt *module.Runtime, modules []module.ModuleInfo) *cobra.Command
- func OptionalIntFlag(fs *pflag.FlagSet, flag string, lowest int) (value int, send bool, err error)
- func OptionalIntFlagInRange(fs *pflag.FlagSet, flag string, lowest, highest int) (value int, send bool, err error)
- func OptionalStringFlag(fs *pflag.FlagSet, flag, remedy string) (value string, send bool, err error)
- func ParseTimeFlag(flag, value string) (time.Time, error)
- func ParseUUIDArg(arg, kind string) (uuid.UUID, error)
- func PrintTruncationHint(rt *module.Runtime, resultCount, limit int, pd PageDefaults, noun string)
- func RenderDryRun(rt *module.Runtime) error
- func RenderDryRunPartial(rt *module.Runtime)
- func RequiredStringFlag(fs *pflag.FlagSet, flag, remedy string) (string, error)
- func RunInspect(ctx context.Context, rt *module.Runtime, deps *InspectDeps, a inspect.Analysis, ...) error
- func StrayArgsOnHelp(cmd *cobra.Command) error
- type DoctorDeps
- type GlobalFlags
- type InspectDeps
- type PageDefaults
- type SelfDeps
- type SetupError
- type UsageError
Constants ¶
const ( ExitOK = 0 // success ExitError = 1 // general runtime failure ExitUsage = 2 // bad flags, arguments, or command ExitTimeout = 3 // a deadline expired before the work finished )
Exit codes per the CLI contract. internal/controlplane/cmd declares its own; the two do not share them but must agree.
const ( LimitLowest = 1 OffsetLowest = 0 )
LimitLowest and OffsetLowest are the floors for the two paging flags every list verb in every module spells the same way.
A limit of zero asks for a page of nothing, which no caller means and which the API answers with a full default page instead of an error. An offset of zero is the first page, an ordinary value, so it is accepted and sent.
Unlike the per-endpoint maxima these are not spec-derived, so they live here rather than in a module: managed declares exactly these two minima and its paging spec test asserts the agreement, while byoc and controlplane declare no paging bounds and would otherwise each invent the same two numbers.
const ( // AnnotationProfileExempt turns the guard off for a command and its // subtree. AnnotationProfileExempt = "pgedge:profile-exempt" // AnnotationProfileRepair waives only the current_profile path, for // the commands an operator reaches for when current_profile names a // profile that does not exist. The full exemption would also stop // `--profile <typo>` being caught there: a name typed on this // invocation is a typo to catch, not a broken file to repair. AnnotationProfileRepair = "pgedge:profile-repair" )
Both annotations take a short reason as their value. Nothing prints it; the pinning tests in internal/clitest read it, so neither set can grow silently.
const DryRunFlag = "dry-run"
DryRunFlag is the flag name, in one place, because setupRuntime and internal/clitest's dry-run gates have to agree on it.
const MutatesAnnotation = "pgedge:mutates"
MutatesAnnotation marks a command as an API-mutating leaf. The structural gates in internal/clitest read it to prove that every verb which writes is dry-runnable and that no read-only verb pretends to be.
const NoUpperBound = 0
NoUpperBound is the highest to pass OptionalIntFlagInRange when the spec declares no maximum for a parameter.
The server may still clamp; the contract just publishes no bound, and a bound the CLI invented would refuse a value the API accepts the day the API raises its cap.
A clamp is visible only where PrintTruncationHint reports it, as `managed task list --limit 500` returning 100 rows does. That is per verb, not a property of this constant:
- byoc's seven --limit verbs, and managed's `task list`, `backup list` and `database branch list`, print the hint whenever a page comes back full.
- `managed database list` prints one only when the caller set --limit. The endpoint applies no default page, so an unbounded read is the whole result and cannot have been truncated.
- `controlplane task list` never prints one, so a clamp there is invisible to the caller as well as absent from the contract.
- The unpaginated catalogue verbs take no paging flags.
Variables ¶
var ( Version = "dev" Commit = "none" BuildDate = "unknown" )
Version, Commit, and BuildDate are injected at build time via ldflags (see the Makefile and .goreleaser.yaml). The defaults are what a plain `go build`/`go run` with no ldflags reports.
Functions ¶
func AddUnknownCommandSuggestions ¶
AddUnknownCommandSuggestions makes an unknown subcommand offer the same "Did you mean this?" near-miss at every depth that the root already offers at depth 1.
Two cobra paths produce the same sentence and only one suggests: the root goes through legacyArgs, which ends `unknown command %q for %q%s` with findSuggestions, while every command below it declares cobra.NoArgs, whose message has the same first two verbs and no third. findSuggestions is unexported, so this rebuilds its output byte for byte from the exported SuggestionsFor, and the depths agree.
It wraps only a validator that is exactly cobra.NoArgs, by function identity, and that is the safety argument. NoArgs fails one way, a positional given to a command that takes none, so a near-miss cannot mislabel a different kind of mistake, as it did when this wrapped an arity check. `controlplane database restore <database_id>` has subcommands and a required positional, and SuggestionsFor matches by prefix as well as by edit distance, so on the shipped binary `controlplane database restore t b` answered
Error: accepts 1 arg(s), received 2
Did you mean this?
template
though the caller mistyped nothing and `template` does not fix an arity error.
That failure is usually a subcommand that did not resolve, but not always: `pgedge starfleet -- tenant` reaches it with a positional the caller said was not a command. cobra's Find strips everything after `--` before legacyArgs, so the root never sees it, while below the root Flags().Args() keeps it; suggestionHint drops the one suggestion that case produces.
The identity test costs no coverage: measured before the walk runs, the 46 group commands are the root with a nil Args, 44 declaring cobra.NoArgs, and that one hybrid.
It is also what makes the walk idempotent. After wrapping, Args is this closure, not cobra.NoArgs, so a second walk skips it; without the test, walking twice appended the hint twice (measured 1 hint before, 2 after), and this has two call sites.
StrayArgsOnHelp asks the command's own validator, now the wrapped one, so an unknown subcommand at depth 2 or more with --help appended gets the same suggestion as the ordinary form.
The root is left alone. Its Args is nil, so cobra's ValidateArgs falls through to legacyArgs and depth 1 keeps its own hint; wrapping it would append a second copy. The nil check is redundant with the identity test and kept because it says why.
Call it after every module has registered, because a command added later is not walked. cmd/pgedge/main.go and clitest.FullTree both do, and the gate walks the population the tree declares rather than a list, so a new group command is covered the day it lands.
func CheckProfile ¶
CheckProfile validates whatever profile a command resolved to, regardless of what chose it. Rejection is the same error and exit code as `pgedge profile use <unknown>`; main.go's hook ordering protects that parity. There is no nil guard because root's PersistentPreRunE sets rt.Config before any run hook fires.
It is not gated on rt.ProfileExplicit because a hand edit never goes through `profile use`: a current_profile naming nothing would otherwise send every command to the default production URL.
func CheckProfileName ¶
CheckProfileName is the one definition of a name usable to read under: the built-in "default", or a key of the config file. The guard, `profile show` and `profile list` all call it, so the flag, config and argument paths cannot drift apart. "default" passes with no section of that name because ResolveProfile falls back to it on a fresh install.
`profile use` is deliberately not a caller: it goes through Config.SetCurrentProfile to Config.ValidateProfile without this exception, so `profile use default` fails where `--profile default` succeeds. `use` selects a configured profile, and writing `current_profile: default` would mean what deleting the line means. llms.txt documents the asymmetry; do not add a fourth caller.
func Confirm ¶
Confirm gates a destructive operation behind user confirmation.
- force bypasses the prompt and proceeds.
- In a non-interactive context (stdin is not a terminal) without force, it returns a *UsageError so scripts and AI agents fail loudly rather than hang on a prompt or silently no-op.
- On a terminal it prompts; a "y"/"Y" answer proceeds, anything else is treated as a rejection.
It returns nil when the caller should proceed.
func ExitCode ¶
ExitCode maps an error to the CLI exit code contract: nil = 0, UsageError = 2, an error implementing coder returns its own code, a deadline that expired = 3, anything else = 1.
func GuardProfile ¶
GuardProfile runs CheckProfile unless cmd or an ancestor is annotated out of it. main.go wraps every command's run hook with this; see wrapProfileGuard for why a wrap and not a PersistentPreRunE.
The full exemption skips the guard; the repair carve-out skips only the current_profile path. Without it, `pgedge profile use <good>`, which rewrites a broken current_profile, would be refused for the very value it exists to replace.
func IsMutating ¶
IsMutating reports whether cmd carries the annotation.
func LimitFlagHelp ¶
func LimitFlagHelp(pd PageDefaults) string
LimitFlagHelp is the --limit help text for a list verb, with the default page size taken from pd rather than typed as a literal. `make docs` copies the help into the generated reference, and building it from pd keeps it from disagreeing with the table the truncation hint reads. Def 0 means no default page, so it gets no parenthetical.
func MarkMutating ¶
MarkMutating declares cmd an API-mutating leaf: it sets the annotation and registers --dry-run, so the two can never disagree. It is one call, not an annotation plus a tree walker in main, because clitest.FullTree() builds the tree without cmd/pgedge/main.go, and the gates would inspect a tree with no --dry-run on it.
The flag is a string with NoOptDefVal, never a bool. A bool accepts --dry-run=true, the spelling kubectl had to break: it shipped a bool, found it ambiguous once client- and server-side dry runs both existed, and removed the boolean form in 1.23. Helm 3.13 instead added --dry-run=server as a new value while bare --dry-run kept working; a string flag leaves that path open.
func NewCompletionCmd ¶
NewCompletionCmd builds the `pgedge completion` command group. It replaces Cobra's auto-generated completion command (disabled in NewRootCmd) so the CLI owns the command surface: a documentable, gate-tested tree plus the `install` convenience subcommand.
func NewDoctorCmd ¶
func NewDoctorCmd(rt *module.Runtime, deps *DoctorDeps) *cobra.Command
NewDoctorCmd builds the `pgedge doctor` command.
func NewEnvCmd ¶
NewEnvCmd builds `pgedge env`, whose one verb runs the linked module's own `env pull`.
func NewHelpCmd ¶
NewHelpCmd builds the `pgedge help` command, replacing cobra's built-in one through the SetHelpCommand extension point.
The built-in cannot report a failure, for two structural reasons:
- It is declared with Run, not RunE (command.go's InitDefaultHelpCmd), so `pgedge help zzz` prints "Unknown help topic" and exits 0, a diagnostic with a success code.
- It discards Find's second return, the args Find could not consume, so `pgedge help starfleet zzz` drops `zzz` and prints starfleet's help with no diagnostic at all.
Both are usage errors (exit 2) here, as a malformed command is everywhere. A path that resolves renders the help cobra would have, via the built-in's three calls; that branch is the only part copied from cobra and the only part that can drift against it.
func NewInspectCmd ¶
func NewInspectCmd(rt *module.Runtime, deps *InspectDeps) *cobra.Command
NewInspectCmd builds `pgedge inspect`, which runs one read-only diagnostic against any Postgres reachable by connection string. The module trees carry the same analyses for a database the API knows, resolving the string for you; this is the form for a Control Plane database or any other Postgres.
func NewLLMSCmd ¶
NewLLMSCmd builds the `pgedge llms` command.
Bare, it prints the index: what is true across the whole CLI (global flags, profiles, environment variables, output formats and exit codes) plus routing tables naming every registered module and every top-level command group's page (`pgedge llms inspect`). `pgedge llms <module>` prints one module's reference, and `pgedge llms <module> <sub>` one sub-scope of it: `pgedge llms starfleet byoc` is byoc's document without the rest of starfleet.
It prints no single document covering every module, so an agent needing one flag does not read every module's reference: the index names the one command to run next, and the common path is one small read plus one targeted one. Sizes are not stated here, because they go stale; pgedgecli.IndexSizeBudget bounds the index and TestIndexStaysUnderBudget enforces it. There is deliberately no --all, which would only rebuild that monolith.
func NewProfileCmd ¶
NewProfileCmd builds the `pgedge profile` command group: list, show and use switch between the named profiles in ~/.pgedge/cli/config.yaml without hand-editing YAML. Every module borrows the active profile, so this lives on the root command rather than inside any one module.
func NewRootCmd ¶
NewRootCmd builds the root pgedge command. rt is the module runtime; it is nil in unit tests that only exercise flag registration.
func NewSelfCmd ¶
NewSelfCmd builds the `pgedge self` command group.
func NewVersionCmd ¶
NewVersionCmd reports the launcher version plus each module's version. rt supplies the output renderer; modules is the set to list (nil renders the launcher alone). rt is nil only in unit tests that exercise flag registration without running the command.
func OptionalIntFlag ¶
func OptionalIntFlag(fs *pflag.FlagSet, flag string, lowest int) ( value int, send bool, err error, )
OptionalIntFlag is the same contract for a numeric flag, refusing anything below lowest.
An explicit 0 is refused along with a negative. No caller means either, and the API's answer is worse than a rejection: byoc created a cluster for --volume-size -5 with its 100 GB default and reported success. Omitting the flag is how you ask for the default.
func OptionalIntFlagInRange ¶
func OptionalIntFlagInRange( fs *pflag.FlagSet, flag string, lowest, highest int, ) (value int, send bool, err error)
OptionalIntFlagInRange is OptionalIntFlag with a declared maximum.
highest must come from the endpoint's OWN parameter in the vendored spec, never from a constant shared across endpoints: `limit` is {min 1, max 1000} on /managed/v1/databases and {min 1, max 100} on /managed/v1/backups. Pass NoUpperBound where the spec declares none.
The refusal is spec-driven, and its message says only that. What the server does with an over-large value on these two endpoints is unmeasured. The measured clamp (`--limit 500` returning 100 rows on a live managed tenant) is on /managed/v1/tasks, which declares no maximum and never reaches this branch.
func OptionalStringFlag ¶
func OptionalStringFlag(fs *pflag.FlagSet, flag, remedy string) ( value string, send bool, err error, )
OptionalStringFlag reports whether an optional string flag should be sent, refusing an explicitly empty value.
remedy completes "--flag given an empty value: " and must say both what a real value looks like and what omitting the flag does, as --config's and --pg-version's do. Omitting the flag stays a valid way to take the API's default, so send is false with no error.
func ParseTimeFlag ¶
ParseTimeFlag parses an RFC3339 flag value, naming the flag and the shape expected rather than echoing Go's layout string.
flag carries its dashes, because callers pass a literal.
func ParseUUIDArg ¶
ParseUUIDArg parses a UUID the caller typed, returning a UsageError on failure so it exits 2, which the exit-code contract reserves for a bad invocation; one function keeps each call site off the general code.
It lives here, not in a module, and returns UsageError, not either tree's ExitError, because TestPlatformImportsNoModule forbids a platform package importing a module, and controlplane's ExitError has no constructor. ExitCode maps UsageError before consulting coder.
kind names the resource so the message says which ID was wrong ("cluster ID", "invite ID") at all ten call sites. The exit-code tests assert codes, not text, so no gate enforces that.
Not for a UUID from an API response: a malformed ID the server sent is a server fault and stays a general error, since the caller did not type it. A failed parse refuses; nothing falls through to a prefix match.
func PrintTruncationHint ¶
func PrintTruncationHint( rt *module.Runtime, resultCount, limit int, pd PageDefaults, noun string, )
PrintTruncationHint writes an advisory line to rt.Stderr, in text and table mode only, when a paginated result may have been capped by the server rather than complete.
limit is the command's --limit value, or 0 if unset, in which case pd.Def is the effective limit. The hint fires when resultCount >= min(effective, pd.Cap), or effective alone when pd.Cap is 0. That cannot tell a capped page from a result landing exactly on the threshold, so the hint says "may be more" and never states a total; none of these endpoints reports one.
noun names the collection in the message ("results", "backups", …).
Callers reach this only from the text/table branch, after the JSON/YAML early return, and after their own "no results" early return, so resultCount is always > 0 here.
func RenderDryRun ¶
RenderDryRun writes the dry-run report for rt. The report is the command's result, not a diagnostic, so it goes to the renderer's output stream, where the real run's result would have gone.
func RenderDryRunPartial ¶
RenderDryRunPartial reports a dry run that stopped before any write was intercepted, because a check failed or the command sent nothing.
It writes text to stderr whatever -o asks for. A failed check exits non-zero, so a partial report on stdout would look like a result, and half a JSON envelope would look whole to a parser. An intercepted write is the result, which RenderDryRun sends to stdout in the chosen format.
It prints nothing when no check ran, since "checks passed: none" adds nothing to the error being read. Write errors are swallowed so that a failed stderr write cannot change the command's exit code.
func RequiredStringFlag ¶
RequiredStringFlag returns a required string flag's value, refusing a blank one.
Cobra's MarkFlagRequired tests only Changed, so `--size ""` satisfies it and the blank reaches the request body. There is no "omit it" branch to offer, so the remedy says only what a real value looks like. A blank stays exit 2 even when the flag's vocabulary can only be checked against a list the CLI has to fetch.
func RunInspect ¶
func RunInspect( ctx context.Context, rt *module.Runtime, deps *InspectDeps, a inspect.Analysis, dbURL string, ) error
RunInspect opens dbURL, runs a, and prints the rows through rt's renderer. It is shared with the module verbs, which resolve dbURL from the API before calling it.
func StrayArgsOnHelp ¶
StrayArgsOnHelp asks a command's own Args validator the question cobra skipped: with --help present, does this command carry a positional it cannot accept, a word meant to name a subcommand that does not?
cobra's execute() returns flag.ErrHelp on the help flag (command.go:935 in the pinned v1.10.2) before reaching ValidateArgs at 968. Every group command pairs Args: cobra.NoArgs with RunE so `pgedge controlplane backup` is a usage error, and appending --help printed help and exited 0 instead, at every depth below the root. The root is safe because cobra's Find rejects an unresolvable first word before execute() runs, and legacyArgs applies that rule only to a command with no parent.
Asking the command's own validator, rather than re-deriving whether the word names a subcommand, keeps one definition of a stray argument for the help path and the ordinary path. It also accepts the one hybrid, `controlplane database restore <database_id>`, a group command with a required positional; hardcoding cobra.NoArgs would reject it.
The two gates below narrow this to an unknown subcommand, and each decides a different shape, so do not collapse them:
len(positional) == 0 keeps `<leaf> --help` working when the leaf declares a required argument. `pgedge starfleet tenant get --help` is how an operator learns get needs an ID, and asking ExactArgs(1) about zero positionals answers "accepts 1 arg(s), received 0". Nothing else prevents that.
!HasSubCommands keeps a childless command's surplus positionals out of scope: `starfleet tenant get id1 id2 --help`, `profile use a b --help` and `completion bash extra --help` exit 0, and 2 without this gate. A positional given to a command with subcommands is a path element that failed to resolve; one given to a childless command is an argument, not part of the path a help request names. The "childless command, help flag, extra argument" test row pins it, and needs two positionals to do so.
cmd.Flags().Args() is exactly what execute() would have validated, since ParseFlags ran before ErrHelp was returned. Deriving the positionals again would mean copying cobra's unexported stripFlags, and re-parsing would double-append every slice flag, such as controlplane's stringArray --base-url.
The help-flag check matters because two routes through execute() reach HelpFunc with a nil error, the help flag (935) and a non-runnable command (956), and only the first is ours; internal/clitest's pure-router gate already forbids a non-runnable group. Where ValidateArgs did run, the validator has already agreed, so re-asking could only disagree by accident, and would for a DisableFlagParsing command, whose raw args Flags().Args() never holds. Nothing sets that field today.
A nil command returns nil: main.run passes whatever ExecuteC returned, which is non-nil on the paths that matter but not by its signature.
Types ¶
type DoctorDeps ¶
type DoctorDeps struct {
// Source fetches the release list checkLatestVersion resolves
// against. nil builds the same production ladder `pgedge self
// update` uses: the unauthenticated HTTP rung, falling back to gh.
Source selfupdate.Source
// NoVersionCheck skips the "Latest version" row's release lookup,
// the one call doctor makes off the machine. A preset value is the
// --no-version-check flag's default.
NoVersionCheck bool
}
DoctorDeps injects doctor's externally-facing dependency for tests; a nil field takes the production default. It is SelfDeps's Source seam alone, since doctor downloads and swaps nothing.
type GlobalFlags ¶
type GlobalFlags struct {
Config string
Profile string
Debug bool
Verbose bool
Output string
NoColor bool
}
GlobalFlags holds the values of the root persistent flags.
type InspectDeps ¶
InspectDeps is the seam a test uses to run an analysis without a Postgres. nil Open connects with the real driver.
type PageDefaults ¶
PageDefaults records what a list endpoint measurably does: the page size it applies when --limit is omitted (Def), and the size it clamps a larger --limit to (Cap).
Neither value may gate a refusal. Refusals come from the vendored spec, through OptionalIntFlagInRange; refusing from this table would invent a client-side ceiling that outlives any change to the server's cap. These values feed the truncation hint and nothing else.
Def of 0 means the endpoint applies no default page, so an unbounded list cannot have been truncated and PrintTruncationHint stays silent. Cap of 0 means no clamp is known, so only --limit bounds the page.
Each module keeps its own table with its own provenance, because what an endpoint such as /managed/v1/tasks does is a module fact.
type SelfDeps ¶
type SelfDeps struct {
// Source fetches releases and downloads assets. nil builds the
// production ladder: the unauthenticated HTTP rung, falling back
// to gh.
Source selfupdate.Source
// Runner execs the newly-installed binary's version probe. nil
// uses exec.CommandContext.
Runner selfupdate.Runner
// Resolve locates and refusal-checks the swap target. nil uses
// selfupdate.ResolveTarget.
Resolve func() (string, error)
// Swap renames the staged binary onto the target. nil uses
// selfupdate.Swap. Injectable so a test can observe WHERE the
// rename reads from: staging anywhere but target's own directory
// is unrenameable across a filesystem boundary, and no hermetic
// test can conjure one.
Swap func(target, staged string) error
// Trusted supplies the Sigstore trust material VerifySignature
// checks the release's checksums.txt against. nil calls
// selfupdate.ProductionTrustedMaterial, which reaches public
// Sigstore infrastructure — tests inject a hermetic one built
// from selfupdate.NewTestTrustedMaterial instead.
Trusted *selfupdate.TrustedMaterial
}
SelfDeps injects self update's externally-facing dependencies for tests; a nil field takes the production default.
type SetupError ¶
type SetupError struct{ Err error }
SetupError marks a failure that happened while building the Runtime, before any command ran. It is a runtime failure (exit 1), not a cobra parse error, so main must not promote it to exit 2.
func (*SetupError) Error ¶
func (e *SetupError) Error() string
func (*SetupError) Unwrap ¶
func (e *SetupError) Unwrap() error
type UsageError ¶
type UsageError struct{ Msg string }
UsageError marks an error caused by bad flags or arguments; it maps to exit code 2 per the UX standards.
func (*UsageError) Error ¶
func (e *UsageError) Error() string