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
+78
View File
@@ -0,0 +1,78 @@
package config
import (
"bytes"
"fmt"
"os"
"gopkg.in/yaml.v3"
)
// Load reads and decodes the file at path. A missing file is not an error:
// it returns a zero-valued *File so the tool remains usable purely from
// flags/env (see the package doc). Unknown YAML keys are a hard error so
// typos in a hand-edited config surface immediately instead of being
// silently ignored.
func Load(path string) (*File, error) {
data, err := os.ReadFile(path)
if err != nil {
if os.IsNotExist(err) {
return &File{Version: SchemaVersion}, nil
}
return nil, fmt.Errorf("reading config %q: %w", path, err)
}
var f File
dec := yaml.NewDecoder(bytes.NewReader(data))
dec.KnownFields(true)
if err := dec.Decode(&f); err != nil {
return nil, fmt.Errorf("parsing config %q: %w", path, err)
}
if f.Version == 0 {
f.Version = SchemaVersion
}
if f.Version != SchemaVersion {
return nil, fmt.Errorf("config %q has version %d, vault-tui supports version %d", path, f.Version, SchemaVersion)
}
for name, p := range f.Profiles {
if p != nil && p.Token.Value != "" {
fmt.Fprintf(os.Stderr, "warning: profile %q sets token.value directly in the config file; prefer VAULT_TOKEN or token.file\n", name)
}
}
return &f, nil
}
// Save writes f to path as YAML, creating parent directories as needed.
// The file is written with 0600 permissions since profiles may carry
// TLS key paths and (discouraged but supported) inline token values.
func Save(path string, f *File) error {
if err := os.MkdirAll(dirOf(path), 0o700); err != nil {
return fmt.Errorf("creating config directory: %w", err)
}
var buf bytes.Buffer
enc := yaml.NewEncoder(&buf)
enc.SetIndent(2)
if err := enc.Encode(f); err != nil {
return fmt.Errorf("encoding config: %w", err)
}
if err := enc.Close(); err != nil {
return err
}
return os.WriteFile(path, buf.Bytes(), 0o600)
}
func dirOf(path string) string {
for i := len(path) - 1; i >= 0; i-- {
if path[i] == '/' {
return path[:i]
}
}
return "."
}
// Exists reports whether a config file is present at path.
func Exists(path string) bool {
_, err := os.Stat(path)
return err == nil
}