Documentation
¶
Overview ¶
Package config loads hoist's config file: $XDG_CONFIG_HOME/hoist/config.yaml, or ~/.config/hoist/config.yaml when XDG_CONFIG_HOME is unset — the same rule on every platform, so the docs stay one sentence — overridable with --config <path>.
Shape (no convention was stated for configuration before this package; AGENTS.md §8, "building structure where no convention is stated is a decision" — this is the proposal):
- One typed struct per YAML mapping, decoded with yaml.v3 and KnownFields(true): a misspelt key is an error naming the line, never a silently ignored setting.
- Defaults are applied in exactly one step, Normalize, after decoding and before Validate; no consumer reads a zero value and guesses. Required values have no default (AGENTS.md §8, "Configuration"): a missing one is a Validate error.
- Every validation error carries the file and the YAML path of the offending value (config.yaml: repos[0].envs.pairs.app-staging: …), and Validate reports all of them at once rather than the first.
- Load reads one file and nothing else: it never runs a program, reads the network, or touches the repo the file describes. Cross-checks that need the world — whether an env named in envs.production exists, whether a kube context is reachable — belong to the consumer that has already observed the world (AGENTS.md §4.1).
- A missing file is not an error (Found is false and the config is the defaults, so the CLI can run on flags alone); a file that exists and does not parse is always an error. Ignoring a broken file would turn a typo into a silent change of behaviour.
- Values are kept as written: Path holds the user's ~/…; the expanded form is the derived Dir field. `hoist config show` therefore prints what the user wrote plus the defaults, and never a resolved home directory.
Index ¶
Constants ¶
const ( DefaultAppsRoot = "cluster/apps" DefaultCINone = "green" DefaultCIGrace = Duration(3 * time.Minute) DefaultArgoNamespace = "argocd" // DefaultMigrationsPath is pkg/migrate's Rails default, restated here so Normalize fills // the same value the app repo's .hoist.yaml would override. DefaultMigrationsPath = migrate.DefaultMigrationsPath ApprovalComment = "comment" ApprovalAuto = "auto" // OpenPRLaunch, OpenPRDisplay, OpenPRBoth are PreferencesConfig.OpenPR's allowed values — // see its own doc comment for what each means. OpenPRLaunch = "launch" OpenPRDisplay = "display" OpenPRBoth = "both" DefaultOpenPR = OpenPRBoth DefaultBrowserLaunchTimeout = Duration(5 * time.Second) )
Defaults for the optional knobs, applied by Normalize.
Variables ¶
var ErrUnknownRepo = errors.New("unknown repo")
ErrUnknownRepo is wrapped by Repo when a selector names no configured repo, so the CLI can tell "not in the file" (fall back to treating it as a path) from "ambiguous".
Functions ¶
func DefaultPath ¶
DefaultPath is where Load looks when --config is not given: $XDG_CONFIG_HOME/hoist/ config.yaml, else ~/.config/hoist/config.yaml. The XDG rule applies on every platform.
Types ¶
type CIConfig ¶
type CIConfig struct {
None string `yaml:"none"` // green|prompt|block; default green
Grace Duration `yaml:"grace"` // how long to wait for checks to appear; default 3m
}
CIConfig is how a PR with no check-runs is treated (M4).
type ClusterSecret ¶
type ClusterSecret struct {
Namespace string `yaml:"namespace,omitempty"`
Secret string `yaml:"secret,omitempty"`
}
ClusterSecret is the pull secret the cluster auth source reads. Opt-in: both fields or neither.
type Config ¶
type Config struct {
Repos []RepoConfig `yaml:"repos,omitempty"`
Registries []RegistryConfig `yaml:"registries,omitempty"`
Poll PollConfig `yaml:"poll"`
Preferences PreferencesConfig `yaml:"preferences"`
// File is the path Load read, or looked for. Found reports whether it existed.
File string `yaml:"-"`
Found bool `yaml:"-"`
}
Config is the whole file. File and Found are set by Load, never read from YAML.
func Load ¶
Load reads, normalizes and validates the file at path. A missing file yields the defaults with Found false and no error; any other failure — unreadable, malformed, unknown key, invalid value — is an error naming the file.
func Parse ¶
Parse decodes data as the file named file (for messages), then Normalize and Validate. Only one YAML document is accepted: a second `---` document in the file is a mistake (the rest of the file is silently ignored by a decoder that only reads the first), not an alternate config, so it is rejected rather than dropped.
func (Config) Marshal ¶
Marshal renders the effective config as YAML: defaults filled in, paths as written.
func (*Config) Normalize ¶
Normalize fills every documented default and derives Dir, Name and Key. It is the one place defaults live; Validate and every consumer see the filled-in value.
func (*Config) Redacted ¶
Redacted returns a copy with every secret-ish value replaced, for printing. Today that is registries[].op — a reference, not a secret, but the pattern is what future token fields follow — so `hoist config show` output can be pasted into an issue.
func (*Config) Repo ¶
func (c *Config) Repo(sel string) (RepoConfig, error)
Repo selects one repo: by name, by path as written, by expanded path (absolute or relative to the working directory), or the only entry when sel is empty and there is exactly one. No entries, or several with no selector, is an error listing the names. A selector that matches nothing wraps ErrUnknownRepo.
type Duration ¶
Duration is a time.Duration that reads and writes time.ParseDuration syntax ("20s", "4h"). A bare number is refused: yaml.v3 would otherwise read it as nanoseconds.
func (Duration) MarshalYAML ¶
MarshalYAML renders the duration as its string form.
type EnvsConfig ¶
type EnvsConfig struct {
Production []string `yaml:"production,omitempty"`
Pairs map[string]string `yaml:"pairs,omitempty"` // source env -> target env
Approval map[string]string `yaml:"approval,omitempty"` // env -> comment|auto
}
EnvsConfig names the envs that matter to policy. Env names are not checked against the repo here — Load never reads the repo (see doc.go); a name that no Application wrapper declares is reported by the consumer that discovered the repo.
func (EnvsConfig) IsProduction ¶
func (e EnvsConfig) IsProduction(env string) bool
IsProduction reports whether env is one the operator listed as production. The single implementation of that question: envs.production is the one config authority that governs PR-required, approval-required and the direct-mode refusal alike (AGENTS.md §4.5), and a second copy of the scan is how those three drift apart.
type KubeConfig ¶
type KubeConfig struct {
Context string `yaml:"context,omitempty"`
// ArgoNamespace is the namespace Argo CD Application custom resources live in — the
// control-plane namespace (conventionally "argocd"), which is a different thing from
// spec.destination.namespace (the workload's own target env: see gitops.Env's doc
// comment, and every family's Application wrapper in this repo's own fixtures, which sets
// metadata.namespace: argocd distinctly from spec.destination.namespace). pkg/argo's
// invariant 1 requires this be confirmed from config rather than assumed a fixed
// "argocd" — Normalize fills the "argocd" default so it need not be spelled out in every
// config file, but the value driving pkg/argo always came from here, never a hardcoded
// literal in pkg/argo itself.
ArgoNamespace string `yaml:"argo_namespace,omitempty"`
}
KubeConfig names the kubeconfig context hoist reads pods from (M2), and, from M5, where Argo CD's own Application custom resources live on that cluster.
type PollConfig ¶
type PollConfig struct {
CI Duration `yaml:"ci"`
Approval Duration `yaml:"approval"`
Argo Duration `yaml:"argo"`
Rollout Duration `yaml:"rollout"`
Deadline Duration `yaml:"deadline"`
}
PollConfig is how often the engine re-observes each remote (M4/M5).
type PreferencesConfig ¶
type PreferencesConfig struct {
// OpenPR controls what pressing o on the flight screen does with a promotion's PR URL
// (flight.OpenPRMsg, cmd/hoist/wiring.go): "launch" attempts to open it in a browser and
// stays silent on success (M4's original behavior, for a desktop session with one to
// open into); "display" only ever shows the URL as text (for a headless/SSH session with
// no browser to launch into at all — see AGENTS.md's own note on this); "both" attempts
// the launch AND always shows the URL as text regardless of outcome, so a copy/paste
// fallback exists even on a desktop session where launching usually just works. Default
// "both": it never regresses the desktop case (launch is still attempted) and never
// leaves a headless session with nothing to act on.
OpenPR string `yaml:"open_pr"`
// BrowserLaunchTimeout bounds one "launch" attempt (cmd/hoist/wiring.go's runLauncher) —
// see its own doc comment for why this bounds the LAUNCHER's exit, not the browser
// window's own lifetime, and is safe to leave short. Default 5s.
BrowserLaunchTimeout Duration `yaml:"browser_launch_timeout"`
}
PreferencesConfig is operator-facing UX behavior — how hoist itself behaves for this operator, as distinct from Repos/Registries/Poll's own promotion-pipeline policy. Unlike those, every field here has a purely local, no-network-effect default that's safe to change on a whim; nothing here is checked against a repo or forge.
type RegistryConfig ¶
type RegistryConfig struct {
Prefix string `yaml:"prefix"`
Auth []string `yaml:"auth"` // order tried; default env, keychain, cluster, op
Cluster ClusterSecret `yaml:"cluster"`
Op string `yaml:"op,omitempty"` // op://vault/item/field; redacted by Redacted
}
RegistryConfig is the credential chain for one image repo prefix.
type RepoConfig ¶
type RepoConfig struct {
Name string `yaml:"name,omitempty"` // label; defaults to the basename of Path
Path string `yaml:"path,omitempty"` // checkout, as written (~ allowed); see Dir
GitHub string `yaml:"github,omitempty"` // owner/name; M3
AppsRoot string `yaml:"apps_root"` // default cluster/apps
Promotable []string `yaml:"promotable,omitempty"` // image repo prefixes; replaces the CLI placeholder when set
Envs EnvsConfig `yaml:"envs"`
Approvers []string `yaml:"approvers,omitempty"` // M4
Collaborators bool `yaml:"collaborators,omitempty"` // M4: also accept a write-permission collaborator, per Forge.IsAllowedAuthor
CI CIConfig `yaml:"ci"` // M4
Kube KubeConfig `yaml:"kube,omitempty"` // M2
DigestSources []string `yaml:"digest_sources"` // M2; default pods, manifest, registry
Apps map[string]string `yaml:"apps,omitempty"` // image repo -> app git repo; M7
// Migrations maps an image repo (an Apps key) to the path prefix under which its app repo
// keeps database migrations, or "none". Filled to DefaultMigrationsPath by Normalize for
// every Apps key that lacks one; the app repo's own .hoist.yaml overrides either
// (pkg/migrate.MigrationsPath). M10.
Migrations map[string]string `yaml:"migrations,omitempty"`
// Dir is Path with ~ expanded and cleaned; Key is this entry's YAML path (repos[N]).
// Both are derived by Normalize.
Dir string `yaml:"-"`
Key string `yaml:"-"`
}
RepoConfig describes one GitOps repository. Only Path is ever required, and only when the CLI has to choose a checkout without --repo; everything else has a default or is consumed by a later milestone (noted per field).
func (RepoConfig) Approval ¶
func (r RepoConfig) Approval(env string) string
Approval is the approval mode for env: the explicit setting, else comment for a production env, else auto.