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
+76
View File
@@ -0,0 +1,76 @@
// Package vault wraps github.com/hashicorp/vault/api behind a small
// Service that the UI and CLI layers talk to. Nothing in this package
// imports a TUI toolkit, so it is fully unit-testable with httptest and
// reusable by the headless commands in internal/cli.
package vault
import (
"fmt"
"github.com/hashicorp/vault/api"
"git.morlana.online/f.weber/vault-tui/internal/config"
)
// NewClient builds an *api.Client from a fully resolved config.Settings.
//
// api.DefaultConfig() already calls ReadEnvironment(), and api.NewClient
// additionally picks up VAULT_TOKEN/VAULT_NAMESPACE/VAULT_HEADERS as soon as
// it sees a *api.Config — all of which would bypass the flag>env>profile>
// defaults precedence that config.Resolve already computed into s. So this
// function overwrites every field env may have set, then explicitly clears
// the token and re-sets the namespace from s: ClearToken()+SetNamespace()
// right after NewClient is the load-bearing pair of calls here.
func NewClient(s *config.Settings) (*api.Client, error) {
cfg := api.DefaultConfig()
if cfg.Error != nil {
return nil, fmt.Errorf("building base client config: %w", cfg.Error)
}
cfg.Address = s.Address
cfg.Timeout = s.Timeout
cfg.MaxRetries = s.MaxRetries
if s.MinRetryWait > 0 {
cfg.MinRetryWait = s.MinRetryWait
}
if s.MaxRetryWait > 0 {
cfg.MaxRetryWait = s.MaxRetryWait
}
cfg.SRVLookup = s.SRVLookup
cfg.DisableRedirects = s.DisableRedirects
cfg.CloneHeaders = true
tls := &api.TLSConfig{
CACert: s.CACert,
CACertBytes: s.CACertPEM,
CAPath: s.CAPath,
ClientCert: s.ClientCert,
ClientKey: s.ClientKey,
TLSServerName: s.ServerName,
Insecure: s.SkipVerify,
}
if err := cfg.ConfigureTLS(tls); err != nil {
return nil, fmt.Errorf("configuring TLS: %w", err)
}
c, err := api.NewClient(cfg)
if err != nil {
return nil, fmt.Errorf("creating vault client: %w", err)
}
c.ClearToken()
c.SetNamespace(s.Namespace)
c.SetCloneHeaders(true)
if len(s.Headers) > 0 {
h := c.Headers()
if h == nil {
h = make(map[string][]string)
}
for k, v := range s.Headers {
h.Set(k, v)
}
c.SetHeaders(h)
}
return c, nil
}