feat(vault-tui): implement KV client and service for managing secrets

- Added internal/vault/client.go for creating a Vault client with configuration settings.
- Introduced internal/vault/errors.go to classify Vault API errors for better UI handling.
- Created internal/vault/kv.go to manage KV secrets, including listing, reading, writing, and deleting operations.
- Implemented internal/vault/mounts.go to list and describe secret engine mounts.
- Developed internal/vault/service.go to provide a unified entry point for Vault operations.
- Added internal/vault/kv_test.go for comprehensive testing of KV operations.
- Introduced internal/ui/toast.go for transient notifications in the UI.
- Added renovate.json for dependency management and updates.
This commit is contained in:
2026-08-14 11:09:03 +02:00
commit ae30ba1240
85 changed files with 9413 additions and 0 deletions
+68
View File
@@ -0,0 +1,68 @@
package token
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/natefinch/atomic"
)
// profileStore keeps one token per profile under the XDG state directory.
// It exists because vault-cli storage is single-valued (~/.vault-token):
// logging into "prod" would silently clobber "dev". Uses the same
// write-then-atomic-rename pattern as Vault's own InternalTokenHelper.
type profileStore struct {
path string
}
// NewProfileStore returns a Store scoped to one profile name, rooted at
// stateDir (see config.StateDir), e.g. stateDir/tokens/<profile>.token.
func NewProfileStore(stateDir, profile string) Store {
return profileStore{path: filepath.Join(stateDir, "tokens", sanitize(profile)+".token")}
}
// NewFileStore is a profileStore pinned to an explicit path, used for
// token.file in the config.
func NewFileStore(path string) Store {
return profileStore{path: path}
}
func sanitize(name string) string {
return strings.NewReplacer("/", "_", "\\", "_", "..", "_").Replace(name)
}
func (s profileStore) Kind() string { return "profile" }
func (s profileStore) Location() string { return s.path }
func (s profileStore) Get(context.Context) (string, error) {
b, err := os.ReadFile(s.path)
if os.IsNotExist(err) {
return "", nil
}
if err != nil {
return "", err
}
return strings.TrimSpace(string(b)), nil
}
func (s profileStore) Store(_ context.Context, tok string) error {
dir := filepath.Dir(s.path)
if err := os.MkdirAll(dir, 0o700); err != nil {
return fmt.Errorf("creating token directory %q: %w", dir, err)
}
tmp := s.path + ".tmp"
if err := os.WriteFile(tmp, []byte(tok), 0o600); err != nil {
return err
}
return atomic.ReplaceFile(tmp, s.path)
}
func (s profileStore) Erase(context.Context) error {
if err := os.Remove(s.path); err != nil && !os.IsNotExist(err) {
return err
}
return nil
}