- 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.
78 lines
1.8 KiB
Go
78 lines
1.8 KiB
Go
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")
|
|
}
|