config

package
v1.0.52 Latest Latest
Warning

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

Go to latest
Published: May 21, 2026 License: MIT Imports: 11 Imported by: 0

Documentation

Overview

Package config provides centralized configuration management for the application. It has no external dependency on other internal packages to avoid import cycles.

Per the Open CLI Collective Secret-Handling Standard §1.2 / §2.3, no access secret is ever written here. The per-user OAuth token lives only in the OS keyring (via cli-common's credstore). The OAuth client JSON is deployment material (§1.2) and lives in a plain file referenced by oauth_client_path. This file owns config.yml: the authoritative credential_ref (§1.3), the OAuth client JSON path, the optional §1.4 file-backend opt-in, and the pre-existing non-secret tuning (cache TTL, granted scopes).

Index

Constants

View Source
const (
	// DirName is the name of the configuration directory.
	DirName = "google-readonly"
	// CredentialsFile is the legacy OAuth client JSON filename (deployment
	// material). Superseded by OAuthClientFile; retained so the one-time
	// migration can find and relocate it.
	CredentialsFile = "credentials.json"
	// OAuthClientFile is the post-migration OAuth client JSON filename
	// (deployment material per §1.2 — not a secret, not in the keyring).
	OAuthClientFile = "oauth_client.json"
	// TokenFile is the legacy OAuth token fallback filename. Superseded by
	// the keyring; retained so the one-time migration can find it.
	TokenFile = "token.json"
	// ConfigFile is the legacy JSON config filename, read once for
	// transparent upgrade to ConfigFileYAML.
	ConfigFile = "config.json"
	// ConfigFileYAML is the authoritative config filename.
	ConfigFileYAML = "config.yml"

	// DefaultCredentialRef applies when config.yml is absent or omits
	// credential_ref. Callers still resolve it via credstore.ParseRef — the
	// service/profile are never assumed structurally (§1.3).
	DefaultCredentialRef = "google-readonly/default"
)
View Source
const (
	// DirPerm is the permission for config directories (owner rwx only).
	DirPerm = 0700
	// TokenPerm is the permission for the config file (owner rw only). The
	// config holds no secret, but there is no reason for it to be world
	// readable.
	TokenPerm = 0600
	// OutputDirPerm is the permission for output directories.
	OutputDirPerm = 0755
	// OutputFilePerm is the permission for output files, and for the OAuth
	// client JSON (deployment material — non-secret, org-internal).
	OutputFilePerm = 0644
)

File and directory permission constants for consistent security settings.

Variables

View Source
var ErrRelocationConflict = errors.New("config: shared old/new config diverge")

ErrRelocationConflict is returned by LoadConfig (and surfaced through LoadConfigForRuntime) when both the old hand-rolled config dir and the new statedir-resolved config dir contain a config.yml with materially different user settings. Mutation-free: nothing is copied, nothing is overwritten. The user reconciles by running `gro init` (which fails the same way at its pre-write gate) or by manually deleting one side.

Functions

func ApplyConfigRelocation added in v1.0.50

func ApplyConfigRelocation(r SharedRelocation) error

ApplyConfigRelocation copies every file in the old config dir to the new dir EXCEPT token.json (the only access secret; handled by keychain.migrate via §1.8 conflict semantics — see MON-5371 plan). Idempotent: if the new dir already has a same-named file, that one is left untouched. Files are written via temp+rename at 0600 under a 0700 dir. The old dir is not modified — leave-old gives the user a recovery point and matches the MON-5370 family pattern.

func CacheDirPath added in v1.0.49

func CacheDirPath() (string, error)

CacheDirPath resolves the OS-designated cache directory WITHOUT creating it (used by `config clear --all --dry-run` and tests). os.UserCacheDir gives the canonical per-OS root: Linux $XDG_CACHE_HOME or ~/.cache, macOS ~/Library/Caches, Windows %LocalAppData%. We append only DirName — no platform-specific suffix — to keep all three consistent.

func DefaultOAuthClientPath added in v1.0.48

func DefaultOAuthClientPath() (string, error)

DefaultOAuthClientPath is the expanded absolute default for OAuthClientPath: <configdir>/oauth_client.json.

func ExpandPath added in v1.0.48

func ExpandPath(p string) string

ExpandPath resolves a leading ~ and makes the path absolute. Stored config values are always expanded; ~ is for display only (ShortenPath).

func GetCacheDir added in v1.0.49

func GetCacheDir() (string, error)

GetCacheDir returns the cache directory, creating it if needed.

func GetConfigDir

func GetConfigDir() (string, error)

GetConfigDir returns the configuration directory, creating it if needed.

func GetConfigDirNoCreate added in v1.0.52

func GetConfigDirNoCreate() (string, error)

GetConfigDirNoCreate resolves the configuration directory WITHOUT creating it — for side-effect-free callers such as `config clear --all` (incl. `--dry-run`). Symmetric with GetConfigPathNoCreate.

func GetConfigPath added in v1.0.30

func GetConfigPath() (string, error)

GetConfigPath returns the authoritative config file path (config.yml).

func GetConfigPathNoCreate added in v1.0.49

func GetConfigPathNoCreate() (string, error)

GetConfigPathNoCreate is GetConfigPath WITHOUT creating the config dir — for side-effect-free paths such as `config clear --dry-run`.

func GetCredentialsPath

func GetCredentialsPath() (string, error)

GetCredentialsPath returns the path to the LEGACY credentials.json (deployment material). Used by the one-time migration to relocate it to OAuthClientPath; not the runtime client-JSON source post-migration.

func GetTokenPath

func GetTokenPath() (string, error)

GetTokenPath returns the path to the LEGACY token.json fallback. Used only by the one-time migration into the keyring.

func LegacyCacheDir added in v1.0.49

func LegacyCacheDir() (string, error)

LegacyCacheDir resolves the pre-B2b cache directory (a "cache" subdir of the config dir) WITHOUT creating anything — for the one-time relocation only.

func LegacyConfigJSONPath added in v1.0.48

func LegacyConfigJSONPath() (string, error)

LegacyConfigJSONPath returns the pre-migration config.json path.

func OldHandRolledConfigDir added in v1.0.52

func OldHandRolledConfigDir() (string, error)

OldHandRolledConfigDir is the pre-MON-5371 hand-rolled config directory (~/.config/google-readonly on macOS/Windows; identical to the canonical dir on Linux). Exposed so cleanup commands can scrub legacy config files at this location — symmetric with LegacyCacheDir on the cache side. Non-creating. Returns ("", error) if HOME is unresolvable.

func OldHandRolledLegacyCacheDir added in v1.0.50

func OldHandRolledLegacyCacheDir() (string, error)

OldHandRolledLegacyCacheDir is the pre-B2b cache subdir under the pre- MON-5371 hand-rolled config dir (a real artifact on macOS/Windows installs that pre-date both B2b and MON-5371). cli-common does NOT resolve here, so internal/cache's migrator probes both this path and the in-resolver LegacyCacheDir, byte-carrying whichever is present. On Linux this resolves identical to LegacyCacheDir and dedupes inside the migrator.

func OldHandRolledTokenPath added in v1.0.50

func OldHandRolledTokenPath() (string, error)

OldHandRolledTokenPath is the pre-MON-5371 legacy token.json location. Exported so keychain.migrate's token-source enumeration can probe it with full §1.8 conflict semantics (per the MON-5371 plan: token.json is excluded from ApplyConfigRelocation and handled exclusively through the existing migrator).

func SaveConfig added in v1.0.30

func SaveConfig(cfg *Config) error

SaveConfig writes config.yml at 0600 under a 0700 directory using an atomic temp-file-in-same-dir → chmod 0600 → rename (§3 standard). The unique temp name from os.CreateTemp means a same-process concurrent save and a crash-leftover from a prior run can never collide; a hard-crash orphan tmp is harmless (never read as config). OAuthClientPath is persisted expanded + absolute so os.ReadFile never sees a literal ~.

func ShortenPath

func ShortenPath(path string) string

ShortenPath replaces the home directory prefix with ~ for display, so errors and `config show` do not expose usernames.

Types

type Config added in v1.0.30

type Config struct {
	// CredentialRef is the authoritative <service>/<profile> keyring ref
	// (§1.3). Resolved via credstore.ParseRef; never hard-coded.
	CredentialRef string `yaml:"credential_ref" json:"credential_ref,omitempty"`
	// OAuthClientPath is the absolute path to the OAuth client JSON
	// (deployment material). Stored expanded + absolute; `~` is display-only
	// via ShortenPath. An org may override the default location here.
	OAuthClientPath string `yaml:"oauth_client_path" json:"oauth_client_path,omitempty"`
	// GrantedScopes is preserved: detects when a token's scopes drift from
	// what init granted. Not a secret.
	GrantedScopes []string `yaml:"granted_scopes,omitempty" json:"granted_scopes,omitempty"`
	// Keyring carries the optional §1.4 explicit file-backend opt-in.
	Keyring KeyringConfig `yaml:"keyring,omitempty" json:"-"`
}

Config is google-readonly's config.yml. Everything here is safe for an org to ship via MDM (§1.2); none of it is an access secret. JSON tags are retained so a legacy config.json is read transparently for one upgrade.

The pre-MON-5371 `cache_ttl_hours` field is gone — cache TTL is now hard-coded per resource per cli-common/docs/working-with-state.md §4.4. An older config.yml that still contains `cache_ttl_hours: N` continues to load cleanly (yaml.v3 silently ignores unknown fields); the value is just inert post-port.

func LoadConfig added in v1.0.30

func LoadConfig() (*Config, error)

LoadConfig loads config.yml. The strict variant — used by `gro init`'s relocation gate and by tests. Returns ErrRelocationConflict (with a wrapped detail message) when both the old hand-rolled and new statedir-resolved dirs contain materially-different config.yml files; on conflict, the canonical new-dir config is still returned alongside the error so callers can choose to soft-degrade. Runtime call sites should use LoadConfigForRuntime instead — see relocate.go.

If new/config.yml is absent but old-only is present (the MON-5371 macOS/Windows pre-init steady state), the old file is transparently read. If neither YAML is present, a legacy config.json is read once at the new dir (post-init); if that is also absent, defaults are returned.

Defaults are always applied to the returned *Config.

func LoadConfigForRuntime added in v1.0.50

func LoadConfigForRuntime() (*Config, error)

LoadConfigForRuntime is the soft-conflict variant of LoadConfig for non-init callers. On ErrRelocationConflict it prints a one-shot stderr warning, then returns the canonical (new-dir) config so the command can keep working — BUT only when a canonical config was actually read. If LoadConfig couldn't populate cfg (e.g. malformed YAML/JSON on the canonical side), the runtime must hard-fail instead of warning-and-defaulting, otherwise it would silently swap CredentialRef etc. back to defaults and mask the corrupt file. Init uses strict LoadConfig (fail-loud) at its relocation gate.

type KeyringConfig added in v1.0.48

type KeyringConfig struct {
	Backend string `yaml:"backend,omitempty" json:"-"`
}

KeyringConfig is the §1.4 backend selector. Backend == "file" forces the encrypted-file backend; empty means OS default selection (fail-closed on Linux when no Secret Service is available).

type SharedRelocation added in v1.0.50

type SharedRelocation struct {
	Kind       relocKind
	OldPath    string // old hand-rolled config dir; "" on Linux short-circuit
	NewPath    string // statedir-resolved config dir (always set)
	CopyNeeded bool   // relocOldOnly only
}

SharedRelocation is the result of DetectConfigRelocation. Paths are filled even on relocNone so callers can log/diagnose; CopyNeeded is true iff a gated ApplyConfigRelocation would actually do work.

func DetectConfigRelocation added in v1.0.50

func DetectConfigRelocation() (SharedRelocation, error)

DetectConfigRelocation classifies the old/new pair without touching disk beyond stats and reads. Never copies, never writes. On Linux (old==new) it short-circuits to relocNone. On macOS/Windows it returns one of the four kinds and the named paths.

Jump to

Keyboard shortcuts

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