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
- Variables
- func ApplyConfigRelocation(r SharedRelocation) error
- func CacheDirPath() (string, error)
- func DefaultOAuthClientPath() (string, error)
- func ExpandPath(p string) string
- func GetCacheDir() (string, error)
- func GetConfigDir() (string, error)
- func GetConfigDirNoCreate() (string, error)
- func GetConfigPath() (string, error)
- func GetConfigPathNoCreate() (string, error)
- func GetCredentialsPath() (string, error)
- func GetTokenPath() (string, error)
- func LegacyCacheDir() (string, error)
- func LegacyConfigJSONPath() (string, error)
- func OldHandRolledConfigDir() (string, error)
- func OldHandRolledLegacyCacheDir() (string, error)
- func OldHandRolledTokenPath() (string, error)
- func SaveConfig(cfg *Config) error
- func ShortenPath(path string) string
- type Config
- type KeyringConfig
- type SharedRelocation
Constants ¶
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" )
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 ¶
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
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
DefaultOAuthClientPath is the expanded absolute default for OAuthClientPath: <configdir>/oauth_client.json.
func ExpandPath ¶ added in v1.0.48
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
GetCacheDir returns the cache directory, creating it if needed.
func GetConfigDir ¶
GetConfigDir returns the configuration directory, creating it if needed.
func GetConfigDirNoCreate ¶ added in v1.0.52
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
GetConfigPath returns the authoritative config file path (config.yml).
func GetConfigPathNoCreate ¶ added in v1.0.49
GetConfigPathNoCreate is GetConfigPath WITHOUT creating the config dir — for side-effect-free paths such as `config clear --dry-run`.
func GetCredentialsPath ¶
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 ¶
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
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
LegacyConfigJSONPath returns the pre-migration config.json path.
func OldHandRolledConfigDir ¶ added in v1.0.52
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
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
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
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 ¶
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
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
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 {
}
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.