From b59a62f0380aab50abc492a88ce3cd25e5c1425c Mon Sep 17 00:00:00 2001 From: unkin-agent Date: Fri, 23 Oct 2026 21:55:13 +1100 Subject: [PATCH] Rename vctl to vcm and move config/cache to vcm dirs --- .gitignore | 6 +-- .woodpecker/release.yaml | 4 +- AGENTS.md | 16 ++++---- Makefile | 4 +- README.md | 31 +++++++------- doc.go | 2 +- docs/{vctl.md => vcm.md} | 52 +++++++++++------------- docs/vctx.md | 11 +++-- examples/{vctl.yaml => config.yaml} | 6 +-- packaging/nfpm.yaml | 18 ++++----- scripts/build-rpm.sh | 6 +-- shared/cache.go | 4 +- shared/config.go | 63 +++++++++++------------------ shared/config_test.go | 25 ++++-------- {vctl => vcm}/main.go | 20 ++++----- {vctl => vcm}/main_test.go | 34 ++++++++-------- vctx/main.go | 6 +-- vctx/main_test.go | 4 +- 18 files changed, 140 insertions(+), 172 deletions(-) rename docs/{vctl.md => vcm.md} (73%) rename examples/{vctl.yaml => config.yaml} (79%) rename {vctl => vcm}/main.go (93%) rename {vctl => vcm}/main_test.go (94%) diff --git a/.gitignore b/.gitignore index 73d38fb..8baeb1a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ -# cross-compiled release artifacts at repo root (e.g. vctl-linux-amd64). -# NB: do NOT ignore /vctl or /vctx bare — those are the tool source directories. -/vctl-* +# cross-compiled release artifacts at repo root (e.g. vcm-linux-amd64). +# NB: do NOT ignore /vcm or /vctx bare — those are the tool source directories. +/vcm-* /vctx-* sha256sums.txt # build output: binaries, completions, RPM diff --git a/.woodpecker/release.yaml b/.woodpecker/release.yaml index 7cc0f7d..e8f28f4 100644 --- a/.woodpecker/release.yaml +++ b/.woodpecker/release.yaml @@ -28,7 +28,7 @@ steps: # for the shell instead of substituting them (as pipeline vars) at parse # time. ${CI_COMMIT_TAG} is a real Woodpecker var and stays single-$. - | - for entry in "vctl:./vctl" "vctx:./vctx"; do + for entry in "vcm:./vcm" "vctx:./vctx"; do name="$${entry%%:*}"; pkg="$${entry##*:}" for osarch in linux/amd64 linux/arm64 darwin/amd64 darwin/arm64; do os="$${osarch%/*}"; arch="$${osarch#*/}" @@ -135,7 +135,7 @@ steps: # root; the package step writes the RPM to dist/. Generate a checksums # manifest over everything we attach so downloads can be verified. RPM=$$(ls dist/*.rpm 2>/dev/null | head -1) - ASSETS="vctl-linux-amd64 vctl-linux-arm64 vctl-darwin-amd64 vctl-darwin-arm64 vctx-linux-amd64 vctx-linux-arm64 vctx-darwin-amd64 vctx-darwin-arm64" + ASSETS="vcm-linux-amd64 vcm-linux-arm64 vcm-darwin-amd64 vcm-darwin-arm64 vctx-linux-amd64 vctx-linux-arm64 vctx-darwin-amd64 vctx-darwin-arm64" [ -n "$$RPM" ] && ASSETS="$$ASSETS $$RPM" sha256sum $$ASSETS > sha256sums.txt tea releases assets create "${CI_COMMIT_TAG}" $$ASSETS sha256sums.txt \ diff --git a/AGENTS.md b/AGENTS.md index 66cba8b..c2492a3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -5,24 +5,24 @@ `vault-tools` is a Go monorepo of small CLIs for managing Vault tokens across multiple vault instances ("contexts"): -- **`vctl`** — logs in to / renews Vault tokens per context (`login`, `renew`, +- **`vcm`** — logs in to / renews Vault tokens per context (`login`, `renew`, `list`), with `--all`, `--method` and `--user` flags. Caches each token under - `~/.cache/vault/` as JSON (token + accessor + policies + TTL/expiry). + `~/.cache/vcm/` as JSON (token + accessor + policies + TTL/expiry). - **`vctx`** — thin wrapper around the real `vault` CLI: `vctx --context ` resolves the context, sets `VAULT_ADDR`/`VAULT_TOKEN`/`VAULT_NAMESPACE`, and execs `vault`. Both tools share the `shared/` package (config parsing, token cache, Vault HTTP -client) and read the SAME config file (`~/.config/vault/vctl.yaml` or -`config.yaml`) and token cache so one `~/.config/vault/` configures everything. +client) and read the SAME config file (`~/.config/vcm/config.yaml`) +and token cache so one `~/.config/vcm/` configures everything. ## Structure ``` shared/ # config.go, cache.go, vault.go (+ *_test.go) -vctl/main.go # vctl CLI (module main package) +vcm/main.go # vcm CLI (module main package) vctx/main.go # vctx CLI (module main package) -docs/vctl.md # per-command docs +docs/vcm.md # per-command docs docs/vctx.md go.mod # module git.unkin.net/unkin/vault-tools Makefile # build / test / lint / completions / rpm / version-bump @@ -39,9 +39,9 @@ mains to one file). ## Build / test ```bash -make build # -> dist/vctl, dist/vctx +make build # -> dist/vcm, dist/vctx make test # go test -race ./... -make completions # -> dist/completions/{vctl,vctx}.{bash,fish}, _{vctl,vctx} +make completions # -> dist/completions/{vcm,vctx}.{bash,fish}, _{vcm,vctx} make rpm # build + nfpm RPM (bundles completions) go build ./... && go test ./... ``` diff --git a/Makefile b/Makefile index afa68a4..5865f09 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ -# vault-tools: a Go monorepo of Vault token CLIs (vctl + vctx) sharing shared/. +# vault-tools: a Go monorepo of Vault token CLIs (vcm + vctx) sharing shared/. # Each tool is a separate main package under its own subfolder. -BINARIES := vctl vctx +BINARIES := vcm vctx DIST := dist VERSION := $(shell git describe --tags --always --dirty 2>/dev/null || echo dev) GOFLAGS := -ldflags="-s -w -X main.version=$(VERSION)" diff --git a/README.md b/README.md index 84dc431..3f5ebb9 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@ # vault-tools A Go monorepo of small CLI tools for working with multiple Vault instances -("contexts"). Both tools share a single config file (`~/.config/vault/`) and -token cache (`~/.cache/vault/`), so once a context is configured every tool +("contexts"). Both tools share a single config file (`~/.config/vcm/`) and +token cache (`~/.cache/vcm/`), so once a context is configured every tool knows about it. | Tool | Purpose | | -------------------- | ----------------------------------------------------------------------- | -| [`vctl`](docs/vctl.md) | Log in to / renew Vault tokens per context and cache them on disk. | +| [`vcm`](docs/vcm.md) | Log in to / renew Vault tokens per context and cache them on disk. | | [`vctx`](docs/vctx.md) | Run the real `vault` CLI against a named context (sets `VAULT_ADDR`/`VAULT_TOKEN`/`VAULT_NAMESPACE`, then execs `vault`). | See the per-command docs in [`docs/`](docs/) for full details. @@ -16,8 +16,8 @@ See the per-command docs in [`docs/`](docs/) for full details. ```bash # 1. Configure your vaults -mkdir -p ~/.config/vault -cat > ~/.config/vault/vctl.yaml <<'YAML' +mkdir -p ~/.config/vcm +cat > ~/.config/vcm/config.yaml <<'YAML' defaults: method: ldap user: ben @@ -29,32 +29,31 @@ contexts: namespace: staging YAML -# 2. Log in (prompts for password), caches a token under ~/.cache/vault/ -vctl login sydney -vctl login --all # or log in to every context at once +# 2. Log in (prompts for password), caches a token under ~/.cache/vcm/ +vcm login sydney +vcm login --all # or log in to every context at once # 3. Use the vault CLI against a context vctx --context sydney kv put kv/foo/bar secret=baz vctx --context staging/sydney kv list kv/ # 4. Keep tokens fresh -vctl renew --all +vcm renew --all # See what's configured and which tokens are still valid -vctl list +vcm list ``` ## Configuration -Both tools read the first of `~/.config/vault/vctl.yaml` or -`~/.config/vault/config.yaml` that exists (`$XDG_CONFIG_HOME` honoured). The +Both tools read `~/.config/vcm/config.yaml` (`$XDG_CONFIG_HOME` honoured). The file maps context names to a vault address plus optional per-context overrides (`method`, `user`, `namespace`, `path`) with file-level `defaults`. Context names may contain slashes, which nest on disk in the token cache. See -[docs/vctl.md](docs/vctl.md#configuration) for the full schema and resolution +[docs/vcm.md](docs/vcm.md#configuration) for the full schema and resolution rules. -Tokens are cached under `~/.cache/vault/` (`$XDG_CACHE_HOME` honoured) +Tokens are cached under `~/.cache/vcm/` (`$XDG_CACHE_HOME` honoured) as JSON with restrictive permissions (files `0600`, dirs `0700`), storing the token plus its accessor, policies, TTL/expiry and renewable flag. @@ -62,7 +61,7 @@ token plus its accessor, policies, TTL/expiry and renewable flag. ``` shared/ # config parsing, token cache, Vault HTTP client (shared lib + tests) -vctl/ # vctl CLI (main package) +vcm/ # vcm CLI (main package) vctx/ # vctx CLI (main package) docs/ # per-command documentation packaging/nfpm.yaml# nfpm spec (envsubst-templated) for the RPM (both binaries + completions) @@ -74,7 +73,7 @@ Makefile # build / test / completions / rpm / version-bump targets ## Building ```bash -make build # build vctl + vctx into dist/ +make build # build vcm + vctx into dist/ make test # go test -race ./... make completions # generate bash/zsh/fish completions into dist/completions/ make rpm # build + package an RPM (needs nfpm) diff --git a/doc.go b/doc.go index 7a98ba9..ad28516 100644 --- a/doc.go +++ b/doc.go @@ -1,5 +1,5 @@ // Package vaulttools is the module root for the vault-tools monorepo. The -// shipped commands live in their own subfolders (vctl/, vctx/) and share the +// shipped commands live in their own subfolders (vcm/, vctx/) and share the // shared/ library; this file exists so the module root is itself a buildable Go // package (some tooling, e.g. the pre-commit go-vet hook, runs `go list` here). package vaulttools diff --git a/docs/vctl.md b/docs/vcm.md similarity index 73% rename from docs/vctl.md rename to docs/vcm.md index 2e70dd1..ddc4bbf 100644 --- a/docs/vctl.md +++ b/docs/vcm.md @@ -1,19 +1,19 @@ -# vctl +# vcm -`vctl` manages Vault tokens for multiple vault instances ("contexts"). It logs +`vcm` manages Vault tokens for multiple vault instances ("contexts"). It logs in to (or renews tokens for) one or all configured contexts and caches each -resulting token under `~/.cache/vault/` for use by +resulting token under `~/.cache/vcm/` for use by [`vctx`](vctx.md) and other tooling. ## Synopsis ``` -vctl login # log in to one context -vctl login --all # log in to every configured context -vctl renew # renew one context's cached token -vctl renew --all # renew every context that has a cached token -vctl list # show contexts + cached-token status -vctl version +vcm login # log in to one context +vcm login --all # log in to every configured context +vcm renew # renew one context's cached token +vcm renew --all # renew every context that has a cached token +vcm list # show contexts + cached-token status +vcm version ``` ## Flags @@ -30,15 +30,11 @@ A context name or `--all` is required (but not both). ## Configuration -`vctl` reads the first of these files that exists: - -1. `~/.config/vault/vctl.yaml` -2. `~/.config/vault/config.yaml` - -(`$XDG_CONFIG_HOME` is honoured in place of `~/.config`.) +`vcm` reads `~/.config/vcm/config.yaml` (`$XDG_CONFIG_HOME` is honoured in +place of `~/.config`). ```yaml -# ~/.config/vault/vctl.yaml +# ~/.config/vcm/config.yaml defaults: method: ldap # applied to any context that doesn't set its own user: ben @@ -75,16 +71,16 @@ the auth path follows the new method. ## Auth methods -- **Password methods** (`ldap`, `userpass`, `okta`, `radius`): `vctl` prompts +- **Password methods** (`ldap`, `userpass`, `okta`, `radius`): `vcm` prompts for a password (no echo) and POSTs to `auth//login/`. -- **`token`**: `vctl` prompts for a raw Vault token (no echo), verifies it with +- **`token`**: `vcm` prompts for a raw Vault token (no echo), verifies it with `auth/token/lookup-self`, and caches its details. ## Token cache -Tokens are written to `~/.cache/vault/` (honouring `$XDG_CACHE_HOME`) +Tokens are written to `~/.cache/vcm/` (honouring `$XDG_CACHE_HOME`) as JSON. Slash contexts nest: `staging/sydney` → -`~/.cache/vault/staging/sydney`. Parent directories are created `0700` and token +`~/.cache/vcm/staging/sydney`. Parent directories are created `0700` and token files are written `0600`. Each file stores enough to inspect and later revoke the token: @@ -111,29 +107,29 @@ The `accessor` lets you revoke the token later without exposing the secret ```bash # Log in to one context (prompts for password) -vctl login sydney +vcm login sydney # Log in everywhere at once -vctl login --all +vcm login --all # Log in to a context overriding the method + user -vctl login sydney --method okta --user someone.else +vcm login sydney --method okta --user someone.else # Renew a slash-named context -vctl renew staging/sydney +vcm renew staging/sydney # Renew everything that currently has a cached token -vctl renew --all +vcm renew --all # See what is configured and which tokens are still valid -vctl list +vcm list ``` ## Shell completion ```bash -vctl completion bash > /etc/bash_completion.d/vctl -vctl completion zsh > ~/.zsh/completions/_vctl +vcm completion bash > /etc/bash_completion.d/vcm +vcm completion zsh > ~/.zsh/completions/_vcm ``` Context names complete dynamically from the config file (the RPM installs these diff --git a/docs/vctx.md b/docs/vctx.md index f7bc723..d9ec725 100644 --- a/docs/vctx.md +++ b/docs/vctx.md @@ -1,7 +1,7 @@ # vctx `vctx` is a thin, context-aware wrapper around the real `vault` CLI. It resolves -a context (using the same config file and token cache as [`vctl`](vctl.md)), +a context (using the same config file and token cache as [`vcm`](vcm.md)), sets `VAULT_ADDR`, `VAULT_TOKEN` and `VAULT_NAMESPACE` for that single invocation only, and execs `vault` with the remaining arguments. @@ -18,10 +18,9 @@ vctx version vctx --context sydney kv put kv/foo/bar secret=baz ``` -1. Resolves the `sydney` context from `~/.config/vault/vctl.yaml` (or - `config.yaml`) — the exact file `vctl` uses. -2. Loads the cached token from `~/.cache/vault/sydney` (errors with a hint to - run `vctl login sydney` if none exists). +1. Resolves the `sydney` context from `~/.config/vcm/config.yaml` — the exact file `vcm` uses. +2. Loads the cached token from `~/.cache/vcm/sydney` (errors with a hint to + run `vcm login sydney` if none exists). 3. Sets, for this process only: - `VAULT_ADDR` = the context's address - `VAULT_TOKEN` = the cached token @@ -68,7 +67,7 @@ vctx --context sydney token lookup ## Requirements - The `vault` CLI must be installed and on `PATH`. -- A token must already be cached for the context (`vctl login `). +- A token must already be cached for the context (`vcm login `). ## Shell completion diff --git a/examples/vctl.yaml b/examples/config.yaml similarity index 79% rename from examples/vctl.yaml rename to examples/config.yaml index ed066a0..a851b34 100644 --- a/examples/vctl.yaml +++ b/examples/config.yaml @@ -1,5 +1,5 @@ -# Example vault-tools config. Copy to ~/.config/vault/vctl.yaml (or config.yaml) -# and edit. Read by both vctl and vctx. +# Example vault-tools config. Copy to ~/.config/vcm/config.yaml +# and edit. Read by both vcm and vctx. # File-level defaults applied to any context that doesn't set its own value. defaults: @@ -12,7 +12,7 @@ contexts: sydney: address: https://vault.syd1.au.unkin.net - # Slash-named context nests on disk as ~/.cache/vault/staging/sydney. + # Slash-named context nests on disk as ~/.cache/vcm/staging/sydney. staging/sydney: address: https://vault-staging.syd1.au.unkin.net namespace: staging diff --git a/packaging/nfpm.yaml b/packaging/nfpm.yaml index 277d4b7..c77cfa7 100644 --- a/packaging/nfpm.yaml +++ b/packaging/nfpm.yaml @@ -1,5 +1,5 @@ --- -# nfpm config for building the vault-tools RPM (vctl + vctx). +# nfpm config for building the vault-tools RPM (vcm + vctx). # Rendered through envsubst (see scripts/build-rpm.sh) then fed to `nfpm pkg`. name: ${PACKAGE_NAME} @@ -24,8 +24,8 @@ provides: contents: # The CLI binaries. - - src: dist/vctl - dst: /usr/bin/vctl + - src: dist/vcm + dst: /usr/bin/vcm file_info: mode: 0755 owner: root @@ -38,16 +38,16 @@ contents: group: root # Shell completions (generated by scripts/build-rpm.sh before packaging). - - src: dist/completions/vctl.bash - dst: /usr/share/bash-completion/completions/vctl + - src: dist/completions/vcm.bash + dst: /usr/share/bash-completion/completions/vcm file_info: mode: 0644 - - src: dist/completions/_vctl - dst: /usr/share/zsh/site-functions/_vctl + - src: dist/completions/_vcm + dst: /usr/share/zsh/site-functions/_vcm file_info: mode: 0644 - - src: dist/completions/vctl.fish - dst: /usr/share/fish/vendor_completions.d/vctl.fish + - src: dist/completions/vcm.fish + dst: /usr/share/fish/vendor_completions.d/vcm.fish file_info: mode: 0644 - src: dist/completions/vctx.bash diff --git a/scripts/build-rpm.sh b/scripts/build-rpm.sh index da6ca97..5be3a27 100755 --- a/scripts/build-rpm.sh +++ b/scripts/build-rpm.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # -# Package the (already built) vctl and vctx binaries into an RPM with nfpm, +# Package the (already built) vcm and vctx binaries into an RPM with nfpm, # bundling generated bash/zsh/fish shell completions. # Usage: scripts/build-rpm.sh [version] (version defaults to $CI_COMMIT_TAG) # @@ -12,7 +12,7 @@ cd "${ROOT_DIR}" VERSION="${1:-${CI_COMMIT_TAG:-0.0.0-dev}}" VERSION="${VERSION#v}" # strip a leading v PACKAGE="vault-tools" -BINARIES=(vctl vctx) +BINARIES=(vcm vctx) DIST="dist" for b in "${BINARIES[@]}"; do @@ -37,7 +37,7 @@ export PACKAGE_VERSION="${VERSION}" export PACKAGE_RELEASE="1" export PACKAGE_ARCH="amd64" export PACKAGE_PLATFORM="linux" -export PACKAGE_DESCRIPTION="CLI tools for managing Vault tokens across multiple vault instances: vctl (per-context login/renew) and vctx (context-aware vault CLI wrapper)" +export PACKAGE_DESCRIPTION="CLI tools for managing Vault tokens across multiple vault instances: vcm (per-context login/renew) and vctx (context-aware vault CLI wrapper)" export PACKAGE_MAINTAINER="Ben Vincent " export PACKAGE_HOMEPAGE="https://git.unkin.net/unkin/vault-tools" export PACKAGE_LICENSE="MIT" diff --git a/shared/cache.go b/shared/cache.go index 397e3ae..948b766 100644 --- a/shared/cache.go +++ b/shared/cache.go @@ -39,7 +39,7 @@ type Token struct { ExpiresAt time.Time `json:"expires_at,omitempty"` } -// CacheDir returns the XDG_CACHE_HOME/vault directory that holds token files. +// CacheDir returns the XDG_CACHE_HOME/vcm directory that holds token files. func CacheDir() string { base := os.Getenv("XDG_CACHE_HOME") if base == "" { @@ -122,7 +122,7 @@ func LoadToken(context string) (*Token, error) { data, err := os.ReadFile(path) if err != nil { if os.IsNotExist(err) { - return nil, fmt.Errorf("no cached token for context %q (run 'vctl login %s'): %w", context, context, os.ErrNotExist) + return nil, fmt.Errorf("no cached token for context %q (run 'vcm login %s'): %w", context, context, os.ErrNotExist) } return nil, fmt.Errorf("reading token %s: %w", path, err) } diff --git a/shared/config.go b/shared/config.go index bc8e86d..62e2ac4 100644 --- a/shared/config.go +++ b/shared/config.go @@ -1,7 +1,7 @@ -// Package shared holds the plumbing common to the vault-tools CLIs (vctl and +// Package shared holds the plumbing common to the vault-tools CLIs (vcm and // vctx): config-file parsing, the on-disk token cache, and a small Vault HTTP // API client. Both tools read the SAME config file and token cache so a single -// ~/.config/vault/ configures every tool in the family. +// ~/.config/vcm/ configures every tool in the family. package shared import ( @@ -19,12 +19,10 @@ const ( DefaultMethod = "ldap" // appDir is the per-user config/cache subdirectory both tools live under. - appDir = "vault" -) + appDir = "vcm" -// configFileNames are the accepted config file basenames, tried in order. The -// first one that exists wins. -var configFileNames = []string{"vctl.yaml", "config.yaml"} + configFileName = "config.yaml" +) // Context is a single vault instance the tools can target. Every field except // Address is optional and, when empty, falls back to the file-level Defaults @@ -72,7 +70,7 @@ type ResolvedContext struct { Path string } -// ConfigDir returns the XDG_CONFIG_HOME/vault directory. +// ConfigDir returns the XDG_CONFIG_HOME/vcm directory. func ConfigDir() string { base := os.Getenv("XDG_CONFIG_HOME") if base == "" { @@ -82,45 +80,32 @@ func ConfigDir() string { return filepath.Join(base, appDir) } -// ConfigPath returns the path of the first existing config file, or the path -// the file would take (the first candidate) when none exists yet. +// ConfigPath returns the path of the config file, whether or not it exists. func ConfigPath() string { - dir := ConfigDir() - for _, name := range configFileNames { - p := filepath.Join(dir, name) - if _, err := os.Stat(p); err == nil { - return p - } - } - return filepath.Join(dir, configFileNames[0]) + return filepath.Join(ConfigDir(), configFileName) } -// Load reads and parses the first config file found in ConfigDir. A missing -// config file is not an error: an empty Config is returned so callers can give -// a helpful "no contexts configured" message. +// Load reads and parses the config file. A missing config file is not an +// error: an empty Config is returned so callers can give a helpful "no +// contexts configured" message. func Load() (*Config, error) { cfg := &Config{Contexts: map[string]Context{}} - dir := ConfigDir() - for _, name := range configFileNames { - p := filepath.Join(dir, name) - data, err := os.ReadFile(p) - if err != nil { - if os.IsNotExist(err) { - continue - } - return cfg, fmt.Errorf("reading config %s: %w", p, err) + p := ConfigPath() + data, err := os.ReadFile(p) + if err != nil { + if os.IsNotExist(err) { + return cfg, nil } - if err := yaml.Unmarshal(data, cfg); err != nil { - return cfg, fmt.Errorf("parsing config %s: %w", p, err) - } - if cfg.Contexts == nil { - cfg.Contexts = map[string]Context{} - } - cfg.path = p - return cfg, nil + return cfg, fmt.Errorf("reading config %s: %w", p, err) } - + if err := yaml.Unmarshal(data, cfg); err != nil { + return cfg, fmt.Errorf("parsing config %s: %w", p, err) + } + if cfg.Contexts == nil { + cfg.Contexts = map[string]Context{} + } + cfg.path = p return cfg, nil } diff --git a/shared/config_test.go b/shared/config_test.go index fc6615e..f7d5f39 100644 --- a/shared/config_test.go +++ b/shared/config_test.go @@ -7,7 +7,7 @@ import ( ) // withConfigDir points XDG_CONFIG_HOME at a temp dir and writes the given -// config file into /vault/, returning the temp base. +// config file into /vcm/, returning the temp base. func withConfigDir(t *testing.T, name, content string) string { t.Helper() base := t.TempDir() @@ -42,7 +42,7 @@ contexts: ` func TestLoadAndContextNames(t *testing.T) { - withConfigDir(t, "vctl.yaml", sampleConfig) + withConfigDir(t, "config.yaml", sampleConfig) cfg, err := Load() if err != nil { @@ -63,18 +63,7 @@ func TestLoadAndContextNames(t *testing.T) { } } -func TestConfigPathPrefersVctlYaml(t *testing.T) { - base := withConfigDir(t, "vctl.yaml", sampleConfig) - // also write a config.yaml; vctl.yaml should win - if err := os.WriteFile(filepath.Join(base, appDir, "config.yaml"), []byte(sampleConfig), 0o600); err != nil { - t.Fatal(err) - } - if got, want := ConfigPath(), filepath.Join(base, appDir, "vctl.yaml"); got != want { - t.Errorf("ConfigPath = %q, want %q", got, want) - } -} - -func TestConfigPathFallsBackToConfigYaml(t *testing.T) { +func TestConfigPath(t *testing.T) { base := withConfigDir(t, "config.yaml", sampleConfig) if got, want := ConfigPath(), filepath.Join(base, appDir, "config.yaml"); got != want { t.Errorf("ConfigPath = %q, want %q", got, want) @@ -93,7 +82,7 @@ func TestLoadMissingConfigIsNotError(t *testing.T) { } func TestResolveAppliesDefaults(t *testing.T) { - withConfigDir(t, "vctl.yaml", sampleConfig) + withConfigDir(t, "config.yaml", sampleConfig) cfg, err := Load() if err != nil { t.Fatal(err) @@ -128,7 +117,7 @@ func TestResolveAppliesDefaults(t *testing.T) { } func TestResolveUnknownContext(t *testing.T) { - withConfigDir(t, "vctl.yaml", sampleConfig) + withConfigDir(t, "config.yaml", sampleConfig) cfg, _ := Load() if _, err := cfg.Resolve("nope"); err == nil { t.Error("expected error for unknown context") @@ -136,7 +125,7 @@ func TestResolveUnknownContext(t *testing.T) { } func TestResolveMethodDefaultWhenUnset(t *testing.T) { - withConfigDir(t, "vctl.yaml", ` + withConfigDir(t, "config.yaml", ` contexts: bare: address: https://vault.example.net @@ -156,7 +145,7 @@ contexts: } func TestResolveWithOverrides(t *testing.T) { - withConfigDir(t, "vctl.yaml", sampleConfig) + withConfigDir(t, "config.yaml", sampleConfig) cfg, _ := Load() // Overriding the method also moves the auth path (context did not pin one). diff --git a/vctl/main.go b/vcm/main.go similarity index 93% rename from vctl/main.go rename to vcm/main.go index bd106ea..9982f76 100644 --- a/vctl/main.go +++ b/vcm/main.go @@ -1,13 +1,13 @@ -// Command vctl manages Vault tokens for multiple vault instances ("contexts"). +// Command vcm manages Vault tokens for multiple vault instances ("contexts"). // // It logs in to (or renews tokens for) one or all configured contexts and -// caches the resulting tokens under ~/.cache/vault/ for use by vctx +// caches the resulting tokens under ~/.cache/vcm/ for use by vctx // and other tooling. // -// vctl login sydney -// vctl login --all -// vctl renew staging/sydney -// vctl renew --all +// vcm login sydney +// vcm login --all +// vcm renew staging/sydney +// vcm renew --all package main import ( @@ -39,11 +39,11 @@ func newRootCmd() *cobra.Command { ) root := &cobra.Command{ - Use: "vctl", + Use: "vcm", Short: "Manage Vault tokens for multiple vault instances (contexts).", - Long: "vctl logs in to and renews Vault tokens for the contexts defined in\n" + - "~/.config/vault/vctl.yaml (or config.yaml), caching each token under\n" + - "~/.cache/vault/ for use by vctx and other tooling.", + Long: "vcm logs in to and renews Vault tokens for the contexts defined in\n" + + "~/.config/vcm/config.yaml, caching each token under\n" + + "~/.cache/vcm/ for use by vctx and other tooling.", SilenceUsage: true, } diff --git a/vctl/main_test.go b/vcm/main_test.go similarity index 94% rename from vctl/main_test.go rename to vcm/main_test.go index 1919255..418130a 100644 --- a/vctl/main_test.go +++ b/vcm/main_test.go @@ -16,23 +16,23 @@ import ( "git.unkin.net/unkin/vault-tools/shared" ) -// setupVctl points XDG dirs at temp locations and writes a config file. It +// setupVcm points XDG dirs at temp locations and writes a config file. It // returns the config dir base so tests can inspect cache writes. -func setupVctl(t *testing.T, cfg string) { +func setupVcm(t *testing.T, cfg string) { t.Helper() cfgHome := t.TempDir() t.Setenv("XDG_CONFIG_HOME", cfgHome) t.Setenv("XDG_CACHE_HOME", t.TempDir()) - cfgDir := filepath.Join(cfgHome, "vault") + cfgDir := filepath.Join(cfgHome, "vcm") if err := os.MkdirAll(cfgDir, 0o700); err != nil { t.Fatal(err) } - if err := os.WriteFile(filepath.Join(cfgDir, "vctl.yaml"), []byte(cfg), 0o600); err != nil { + if err := os.WriteFile(filepath.Join(cfgDir, "config.yaml"), []byte(cfg), 0o600); err != nil { t.Fatal(err) } } -const vctlConfig = ` +const vcmConfig = ` defaults: method: ldap user: ben @@ -47,7 +47,7 @@ contexts: // --- runOverContexts: selection + iteration + aggregation ------------------ func TestRunOverContextsSingle(t *testing.T) { - setupVctl(t, vctlConfig) + setupVcm(t, vcmConfig) var seen []string fn := func(rc shared.ResolvedContext) (*shared.Token, error) { seen = append(seen, rc.Name) @@ -66,7 +66,7 @@ func TestRunOverContextsSingle(t *testing.T) { } func TestRunOverContextsAllIteratesEveryContext(t *testing.T) { - setupVctl(t, vctlConfig) + setupVcm(t, vcmConfig) var seen []string fn := func(rc shared.ResolvedContext) (*shared.Token, error) { seen = append(seen, rc.Name) @@ -88,7 +88,7 @@ func TestRunOverContextsAllIteratesEveryContext(t *testing.T) { } func TestRunOverContextsContextAndAllConflict(t *testing.T) { - setupVctl(t, vctlConfig) + setupVcm(t, vcmConfig) fn := func(rc shared.ResolvedContext) (*shared.Token, error) { return nil, nil } err := runOverContexts([]string{"sydney"}, true, "", "", "login", fn) if err == nil || !strings.Contains(err.Error(), "not both") { @@ -97,7 +97,7 @@ func TestRunOverContextsContextAndAllConflict(t *testing.T) { } func TestRunOverContextsNoTarget(t *testing.T) { - setupVctl(t, vctlConfig) + setupVcm(t, vcmConfig) fn := func(rc shared.ResolvedContext) (*shared.Token, error) { return nil, nil } if err := runOverContexts(nil, false, "", "", "login", fn); err == nil { t.Error("expected error when neither context nor --all given") @@ -105,7 +105,7 @@ func TestRunOverContextsNoTarget(t *testing.T) { } func TestRunOverContextsAllEmptyConfig(t *testing.T) { - setupVctl(t, "contexts: {}\n") + setupVcm(t, "contexts: {}\n") fn := func(rc shared.ResolvedContext) (*shared.Token, error) { return nil, nil } err := runOverContexts(nil, true, "", "", "login", fn) if err == nil || !strings.Contains(err.Error(), "no contexts configured") { @@ -114,7 +114,7 @@ func TestRunOverContextsAllEmptyConfig(t *testing.T) { } func TestRunOverContextsAggregatesFailures(t *testing.T) { - setupVctl(t, vctlConfig) + setupVcm(t, vcmConfig) fn := func(rc shared.ResolvedContext) (*shared.Token, error) { if rc.Name == "sydney" { return nil, io.ErrUnexpectedEOF // simulate a login failure @@ -137,7 +137,7 @@ func TestRunOverContextsAggregatesFailures(t *testing.T) { func TestRunOverContextsResolveErrorCounts(t *testing.T) { // A context missing an address fails resolution and is counted as a failure. - setupVctl(t, ` + setupVcm(t, ` contexts: good: address: https://vault.example.net @@ -153,7 +153,7 @@ contexts: } func TestRunOverContextsAppliesOverrides(t *testing.T) { - setupVctl(t, vctlConfig) + setupVcm(t, vcmConfig) var got shared.ResolvedContext fn := func(rc shared.ResolvedContext) (*shared.Token, error) { got = rc @@ -237,7 +237,7 @@ func TestDoLoginTokenMethod(t *testing.T) { // --- doRenew --------------------------------------------------------------- func TestDoRenewNoCachedToken(t *testing.T) { - setupVctl(t, vctlConfig) + setupVcm(t, vcmConfig) rc := shared.ResolvedContext{Name: "sydney", Address: "https://vault.example.net"} if _, err := doRenew(rc); err == nil { t.Error("expected error renewing a context with no cached token") @@ -245,7 +245,7 @@ func TestDoRenewNoCachedToken(t *testing.T) { } func TestDoRenewUsesCachedToken(t *testing.T) { - setupVctl(t, vctlConfig) + setupVcm(t, vcmConfig) srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/v1/auth/token/renew-self" { t.Errorf("path = %q", r.URL.Path) @@ -276,7 +276,7 @@ func TestDoRenewUsesCachedToken(t *testing.T) { // --- runList --------------------------------------------------------------- func TestRunListShowsTokenStatus(t *testing.T) { - setupVctl(t, vctlConfig) + setupVcm(t, vcmConfig) // sydney has a valid token; staging/sydney has none. if err := shared.SaveToken(&shared.Token{ Context: "sydney", @@ -300,7 +300,7 @@ func TestRunListShowsTokenStatus(t *testing.T) { } func TestRunListEmptyConfig(t *testing.T) { - setupVctl(t, "contexts: {}\n") + setupVcm(t, "contexts: {}\n") var buf bytes.Buffer if err := runList(&buf); err != nil { t.Fatal(err) diff --git a/vctx/main.go b/vctx/main.go index a1725e9..0709e3d 100644 --- a/vctx/main.go +++ b/vctx/main.go @@ -1,6 +1,6 @@ // Command vctx is a thin, context-aware wrapper around the real `vault` CLI. // -// It resolves a context (via the same config + token cache as vctl), sets +// It resolves a context (via the same config + token cache as vcm), sets // VAULT_ADDR / VAULT_TOKEN / VAULT_NAMESPACE for that single invocation, and // execs `vault` with the remaining arguments: // @@ -37,8 +37,8 @@ func newRootCmd() *cobra.Command { root := &cobra.Command{ Use: "vctx --context ", Short: "Run the vault CLI against a named context.", - Long: "vctx resolves a context from ~/.config/vault (shared with vctl), loads its\n" + - "cached token from ~/.cache/vault/, sets VAULT_ADDR, VAULT_TOKEN and\n" + + Long: "vctx resolves a context from ~/.config/vcm (shared with vcm), loads its\n" + + "cached token from ~/.cache/vcm/, sets VAULT_ADDR, VAULT_TOKEN and\n" + "VAULT_NAMESPACE for this invocation only, and execs `vault` with the\n" + "remaining arguments.\n\n" + "Example: vctx --context sydney kv put kv/foo/bar secret=baz", diff --git a/vctx/main_test.go b/vctx/main_test.go index 2ba810e..4451777 100644 --- a/vctx/main_test.go +++ b/vctx/main_test.go @@ -20,11 +20,11 @@ func setupVctx(t *testing.T, cfg string, tokens map[string]shared.Token) string t.Setenv("XDG_CONFIG_HOME", cfgHome) t.Setenv("XDG_CACHE_HOME", t.TempDir()) - cfgDir := filepath.Join(cfgHome, "vault") + cfgDir := filepath.Join(cfgHome, "vcm") if err := os.MkdirAll(cfgDir, 0o700); err != nil { t.Fatal(err) } - if err := os.WriteFile(filepath.Join(cfgDir, "vctl.yaml"), []byte(cfg), 0o600); err != nil { + if err := os.WriteFile(filepath.Join(cfgDir, "config.yaml"), []byte(cfg), 0o600); err != nil { t.Fatal(err) } for name, tok := range tokens { -- 2.47.3