Documentation
¶
Overview ¶
Package userdirs resolves the per-user directories where the Entire CLIs keep global state. It is the single implementation of that resolution — don't derive ~/.config/entire or ~/.cache/entire paths anywhere else.
- Config: contexts.json, version_check.json, the file-backed token store. $ENTIRE_CONFIG_DIR if set, else ~/.config/entire.
- Cache: discovery caches (nodes.json, cluster_cores.json, api_discovery.json). $XDG_CACHE_HOME/entire if set, else ~/.cache/entire.
Under `go test`, both fall back to a throwaway per-process directory when their env override is unset (see internal/testdirs), so a test that forgets to isolate can never read or pollute the developer's real state.
Index ¶
- Constants
- func Cache() string
- func CacheDirChecked() (string, error)
- func CacheRoot() (*os.Root, error)
- func CacheRootForRead() (*os.Root, error)
- func Config() string
- func ConfigDirChecked() (string, error)
- func ConfigRoot() (*os.Root, error)
- func ConfigRootForRead() (*os.Root, error)
- func EnsurePrivateDir(dir string) error
- func RequireAbsoluteOverride(name, value string) error
Constants ¶
const ( // EnvConfigDir overrides the per-user config directory. EnvConfigDir = "ENTIRE_CONFIG_DIR" // EnvCacheHome overrides the per-user cache directory's parent. EnvCacheHome = "XDG_CACHE_HOME" )
The environment variables that override these directories.
Variables ¶
This section is empty.
Functions ¶
func Cache ¶
func Cache() string
Cache returns the per-user cache directory. See Config on the unreported override.
func CacheDirChecked ¶ added in v0.11.0
CacheDirChecked is Cache for a caller that can report a rejected override.
Needed for the same reason as its config twin: a consumer that creates a directory or takes a lock before handing the path to something that opens a root has to learn about a bad override BEFORE it creates anything, and the string form cannot tell it. plugin_index was the consumer that needed it.
func CacheRoot ¶ added in v0.10.4
CacheRoot returns the shared *os.Root over the per-user cache directory, creating it. CacheRootForRead is the same without creation.
func CacheRootForRead ¶ added in v0.10.4
CacheRootForRead is CacheRoot without creating the directory.
func Config ¶
func Config() string
Config returns the per-user config directory.
The string form cannot report a rejected override, so it returns whatever was set, and every consumer that turns one into I/O is responsible for learning about a bad value BEFORE it creates a directory, takes a lock, or writes.
Deliberately NOT a list of those consumers. Two successive revisions of this comment enumerated them and both were wrong within a commit or two -- the token store slipped past the first (bearer tokens at ./<value>/tokens.json) and plugin_index past the second (an index clone and its lock file in the working directory). A comment cannot fail when someone adds a caller. TestUserDirConsumersAreAudited can, and does: every call site of Config() or Cache() must appear in its ledger with the reason it is safe.
Callers that only display the path are unaffected.
func ConfigDirChecked ¶ added in v0.11.0
ConfigDirChecked is Config for a caller that can report a rejected override. It returns the directory in both cases, so a caller building a path for a message still has one.
func ConfigRoot ¶ added in v0.10.4
ConfigRoot returns the shared *os.Root over the per-user config directory, creating the directory if it does not exist. ConfigRootForRead is the same without creation.
Every read and write of contexts.json, version_check.json, and the file-backed token store goes through this rather than through a path joined onto Config(). The names inside are fixed today, but the point of the root is that they do not have to stay that way: a future context name, cluster slug, or token key that reaches a filename cannot escape the directory, and a symlink swapped in between resolution and open surfaces as an error rather than a redirected write to somewhere in the user's home.
The create/no-create split matters here for the same reason it does for .entire: a command that only looks for a saved login must not leave an ~/.config/entire behind on a machine that has never used one.
func ConfigRootForRead ¶ added in v0.10.4
ConfigRootForRead is ConfigRoot without creating the directory. A missing directory is reported unwrapped, so callers classify it with os.IsNotExist.
func EnsurePrivateDir ¶ added in v0.10.4
EnsurePrivateDir creates dir as a private, user-only directory (0700) and, when it already exists with group or other access, clears those bits.
The tightening step is the point. Config() holds bearer tokens — the login JWTs in contexts.json and the file token store's tokens.json — and those files are written 0600, but a mode-0755 parent leaks their existence and hands anyone on the box a directory they can traverse and enumerate. Because os.MkdirAll is a no-op on an existing path, whichever caller created the directory first fixes its mode permanently: a version check that ran before the first login used to leave it 0755 for good, and the credential stores' own MkdirAll(0700) could never repair it.
Only the group and other bits are ever cleared: the owner bits are carried across untouched, so a directory the user deliberately made stricter than 0700 stays that way whether or not it also needed tightening (0500 survives, 0555 becomes 0500). Masking can leave the owner no access at all, which is the same thing an already-private 0000 directory gets: this function makes a directory private, and does not claim to make it usable.
Windows has no unix permission bits (Go reports synthetic modes and Chmod only toggles the read-only flag), so the tightening step is skipped there.
func RequireAbsoluteOverride ¶ added in v0.11.0
RequireAbsoluteOverride rejects a non-absolute directory override.
A relative value resolves against the process's working directory, so the same environment names a different directory in every process — and, for a tool run from inside a repository, typically names a directory inside it. That is wrong for all three trees this rule covers. The config directory holds bearer tokens (contexts.json and the file token store), the cache directory holds cluster discovery state, and the managed plugin directory holds binaries whose bin subdirectory main.go prepends to $PATH.
One helper rather than one rule per tree. Every override gets it, not just the Entire-specific ones: XDG_DATA_HOME, XDG_CACHE_HOME and LOCALAPPDATA were joined unchecked and left for osroot (which refuses a relative root open) and for main.go (which restores $PATH) to notice. Those backstops hold, but each answers a question of its own, two layers from where this one is decided.
Rejecting is louder than falling through to the platform default, and that is deliberate: a misconfigured override is a user error worth surfacing, and for the config directory the platform default is the developer's REAL ~/.config/entire — quietly substituting it for a test harness's mistyped override is the one outcome worse than an error. name is whatever the message should call the directory. Pass the environment variable where the value plainly came from one, so the reader knows what to change; callers a level down from the variable (contexts, discovery) pass the role instead, because by then the value may equally have come from the platform default.
Types ¶
This section is empty.