mcp-framework/secretstore/runtime.go

215 lines
5.8 KiB
Go
Raw Normal View History

package secretstore
import (
"errors"
"fmt"
"strings"
)
const DefaultManifestSource = "default:auto (manifest not found)"
type DescribeRuntimeOptions struct {
2026-05-02 12:59:04 +00:00
ServiceName string
LookupEnv func(string) (string, bool)
KWalletAppID string
KWalletFolder string
BitwardenCommand string
BitwardenDebug bool
DisableBitwardenCache bool
CheckReady bool
2026-05-02 12:59:04 +00:00
Shell string
ManifestLoader ManifestLoader
ExecutableResolver ExecutableResolver
}
type RuntimeDescription struct {
ManifestSource string
DeclaredPolicy BackendPolicy
EffectivePolicy BackendPolicy
DisplayName string
Ready bool
ReadyError error
}
type PreflightStatus string
const (
PreflightStatusReady PreflightStatus = "ready"
PreflightStatusFail PreflightStatus = "fail"
)
type PreflightOptions = DescribeRuntimeOptions
type PreflightReport struct {
Status PreflightStatus
Summary string
Remediation string
Runtime RuntimeDescription
}
func DescribeRuntime(options DescribeRuntimeOptions) (RuntimeDescription, error) {
resolution, err := resolveManifestPolicy(OpenFromManifestOptions{
2026-05-02 12:59:04 +00:00
ServiceName: options.ServiceName,
LookupEnv: options.LookupEnv,
KWalletAppID: options.KWalletAppID,
KWalletFolder: options.KWalletFolder,
BitwardenCommand: options.BitwardenCommand,
DisableBitwardenCache: options.DisableBitwardenCache,
Shell: options.Shell,
ManifestLoader: options.ManifestLoader,
ExecutableResolver: options.ExecutableResolver,
})
if err != nil {
return RuntimeDescription{}, err
}
desc := RuntimeDescription{
ManifestSource: manifestSourceLabel(resolution.Source),
DeclaredPolicy: resolution.Policy,
EffectivePolicy: resolution.Policy,
DisplayName: BackendDisplayName(resolution.Policy),
}
store, openErr := Open(Options{
2026-05-02 12:59:04 +00:00
ServiceName: options.ServiceName,
BackendPolicy: resolution.Policy,
LookupEnv: options.LookupEnv,
KWalletAppID: options.KWalletAppID,
KWalletFolder: options.KWalletFolder,
BitwardenCommand: options.BitwardenCommand,
BitwardenDebug: options.BitwardenDebug,
DisableBitwardenCache: disableBitwardenCacheOption(options.DisableBitwardenCache, resolution.BitwardenCache),
Shell: options.Shell,
})
if openErr != nil {
desc.Ready = false
desc.ReadyError = openErr
return desc, nil
}
desc.Ready = true
if effective := EffectiveBackendPolicy(store); strings.TrimSpace(string(effective)) != "" {
desc.EffectivePolicy = effective
desc.DisplayName = BackendDisplayName(effective)
}
if options.CheckReady && desc.EffectivePolicy == BackendBitwardenCLI {
2026-05-02 13:30:18 +00:00
if err := verifyBitwardenCLIReady(Options{
BitwardenCommand: options.BitwardenCommand,
BitwardenDebug: options.BitwardenDebug,
LookupEnv: options.LookupEnv,
Shell: options.Shell,
}); err != nil {
desc.Ready = false
desc.ReadyError = err
}
}
return desc, nil
}
func PreflightFromManifest(options PreflightOptions) (PreflightReport, error) {
options.CheckReady = true
desc, err := DescribeRuntime(options)
if err != nil {
return PreflightReport{}, err
}
if desc.Ready {
return PreflightReport{
Status: PreflightStatusReady,
Summary: "secret backend is ready",
Runtime: desc,
}, nil
}
summary, remediation := summarizePreflightFailure(desc.ReadyError)
return PreflightReport{
Status: PreflightStatusFail,
Summary: summary,
Remediation: remediation,
Runtime: desc,
}, nil
}
func BackendDisplayName(policy BackendPolicy) string {
switch policy {
case BackendBitwardenCLI:
return "Bitwarden CLI"
case BackendEnvOnly:
return "Environment variables"
case BackendKWalletOnly:
return "KWallet"
case BackendAuto:
return "automatic backend selection"
case BackendKeyringAny:
return BackendName()
default:
trimmed := strings.TrimSpace(string(policy))
if trimmed == "" {
return "unknown backend"
}
return trimmed
}
}
func FormatBackendStatus(desc RuntimeDescription) string {
source := manifestSourceLabel(desc.ManifestSource)
display := strings.TrimSpace(desc.DisplayName)
if display == "" {
display = BackendDisplayName(desc.EffectivePolicy)
}
effective := desc.EffectivePolicy
if strings.TrimSpace(string(effective)) == "" {
effective = desc.DeclaredPolicy
}
parts := []string{
fmt.Sprintf("declared=%s", normalizeStatusPolicy(desc.DeclaredPolicy)),
fmt.Sprintf("effective=%s", normalizeStatusPolicy(effective)),
fmt.Sprintf("display=%s", display),
fmt.Sprintf("ready=%t", desc.Ready),
fmt.Sprintf("source=%s", source),
}
if desc.ReadyError != nil {
parts = append(parts, fmt.Sprintf("error=%s", strings.TrimSpace(desc.ReadyError.Error())))
}
return strings.Join(parts, " ")
}
func summarizePreflightFailure(err error) (string, string) {
if err == nil {
return "secret backend is unavailable", ""
}
switch {
case errors.Is(err, ErrBWNotLoggedIn):
return "bitwarden login is required", strings.TrimSpace(err.Error())
case errors.Is(err, ErrBWLocked):
return "bitwarden vault is locked or BW_SESSION is missing", strings.TrimSpace(err.Error())
case errors.Is(err, ErrBWUnavailable):
return "bitwarden CLI is unavailable", strings.TrimSpace(err.Error())
case errors.Is(err, ErrBackendUnavailable):
return "secret backend is unavailable", strings.TrimSpace(err.Error())
default:
return "secret backend preflight failed", strings.TrimSpace(err.Error())
}
}
func manifestSourceLabel(source string) string {
trimmed := strings.TrimSpace(source)
if trimmed == "" {
return DefaultManifestSource
}
return trimmed
}
func normalizeStatusPolicy(policy BackendPolicy) string {
trimmed := strings.TrimSpace(string(policy))
if trimmed == "" {
return string(BackendAuto)
}
return trimmed
}