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:
@@ -0,0 +1,77 @@
|
||||
package vault
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/vault/api"
|
||||
)
|
||||
|
||||
// ErrKind classifies a Vault API error into a small, UI-actionable set.
|
||||
// See internal/ui's error surfacing tiers: Cancelled is dropped silently,
|
||||
// NotFound/Forbidden/CAS become a toast plus an inline hint, Unauthorized
|
||||
// sends the user back to the auth screen, Sealed/Network get a full-body
|
||||
// retry panel.
|
||||
type ErrKind uint8
|
||||
|
||||
const (
|
||||
ErrUnknown ErrKind = iota
|
||||
ErrForbidden
|
||||
ErrNotFound
|
||||
ErrUnauthorized
|
||||
ErrSealed
|
||||
ErrCAS
|
||||
ErrNetwork
|
||||
ErrCancelled
|
||||
)
|
||||
|
||||
// Classify inspects err (typically returned from a Logical() call) and
|
||||
// returns its kind plus a short human-readable message.
|
||||
func Classify(err error) (ErrKind, string) {
|
||||
if err == nil {
|
||||
return ErrUnknown, ""
|
||||
}
|
||||
if errors.Is(err, context.Canceled) {
|
||||
return ErrCancelled, "cancelled"
|
||||
}
|
||||
|
||||
var respErr *api.ResponseError
|
||||
if errors.As(err, &respErr) {
|
||||
msg := strings.Join(respErr.Errors, "; ")
|
||||
if msg == "" {
|
||||
msg = err.Error()
|
||||
}
|
||||
switch respErr.StatusCode {
|
||||
case 403:
|
||||
if isTokenInvalid(msg) {
|
||||
return ErrUnauthorized, "token is invalid or expired"
|
||||
}
|
||||
return ErrForbidden, "permission denied"
|
||||
case 404:
|
||||
return ErrNotFound, "not found"
|
||||
case 400:
|
||||
if strings.Contains(strings.ToLower(msg), "check-and-set") {
|
||||
return ErrCAS, "changed underneath you (check-and-set mismatch)"
|
||||
}
|
||||
return ErrUnknown, msg
|
||||
case 503:
|
||||
return ErrSealed, "vault is sealed or in standby"
|
||||
default:
|
||||
return ErrUnknown, msg
|
||||
}
|
||||
}
|
||||
|
||||
var netErr net.Error
|
||||
if errors.As(err, &netErr) {
|
||||
return ErrNetwork, netErr.Error()
|
||||
}
|
||||
|
||||
return ErrUnknown, err.Error()
|
||||
}
|
||||
|
||||
func isTokenInvalid(msg string) bool {
|
||||
m := strings.ToLower(msg)
|
||||
return strings.Contains(m, "permission denied") && strings.Contains(m, "token")
|
||||
}
|
||||
Reference in New Issue
Block a user