diff --git a/internal/io/vault/output.go b/internal/io/vault/output.go index 3499f612..1d7eac18 100644 --- a/internal/io/vault/output.go +++ b/internal/io/vault/output.go @@ -38,7 +38,8 @@ func PrintVaultList(format string, vaultNames []string) { for _, name := range vaultNames { vault, err := vaultFromName(name) if err != nil { - logger.Log().Fatalf("Vault error %s - %v", name, err) + logger.Log().Warnf("vault %s could not be opened - %v", name, err) + vault = unopenableVault(name, err) } vaults.Vaults = append(vaults.Vaults, vault) } diff --git a/internal/io/vault/view.go b/internal/io/vault/view.go index 3aef9fef..a31a274d 100644 --- a/internal/io/vault/view.go +++ b/internal/io/vault/view.go @@ -21,10 +21,20 @@ type vaultEntity struct { Name string `json:"name" yaml:"name"` Path string `json:"path" yaml:"path"` Type string `json:"type" yaml:"type"` + // Error is set when the vault could not be opened. It is listed anyway, so + // one unusable vault does not hide every other one. + Error string `json:"error,omitempty" yaml:"error,omitempty"` Data map[string]interface{} `json:"data" yaml:"data"` } +// unopenableVault is the placeholder for a vault whose config loads but whose +// provider will not open -- a missing key, a revoked identity, a config written +// by an older version. +func unopenableVault(name string, err error) *vaultEntity { + return &vaultEntity{Name: name, Type: "unknown", Error: err.Error(), Data: map[string]interface{}{}} +} + func (v *vaultEntity) YAML() (string, error) { data, err := yaml.Marshal(v) if err != nil { @@ -115,10 +125,7 @@ func NewVaultListView( for _, name := range vaultNames { v, err := vaultFromName(name) if err != nil || v == nil { - return views.NewErrorView( - fmt.Errorf("vault '%s' error: %w", name, err), - container.RenderState().Theme, - ) + v = unopenableVault(name, err) } vaults = append(vaults, v) } diff --git a/internal/vault/vault.go b/internal/vault/vault.go index 900da498..196960ee 100644 --- a/internal/vault/vault.go +++ b/internal/vault/vault.go @@ -210,13 +210,7 @@ func NewExternalVault(providerConfigFile string) (*CreateResult, error) { return nil, fmt.Errorf("invalid vault name %q in config: %w", cfg.ID, err) } - // An external vault keeps a registry of the links it holds. A config authored - // elsewhere -- rendered from a preset, or written by hand -- has no business - // knowing where flow keeps vault state, so it arrives without a storage path - // and flow supplies the same location every other provider uses. - if cfg.External != nil && cfg.External.StoragePath == "" { - cfg.External.StoragePath = CacheDirectory(cfg.ID) - } + applyExternalDefaults(&cfg) v, _, err := vault.New(cfg.ID, vault.WithExternalConfig(cfg.External)) if err != nil { @@ -250,6 +244,8 @@ func VaultFromName(name string) (*VaultConfig, Vault, error) { return nil, nil, fmt.Errorf("failed to load vault config: %w", err) } + applyExternalDefaults(&cfg) + switch cfg.Type { case vault.ProviderTypeAge: provider, err := vault.NewAgeVault(&cfg) @@ -360,3 +356,12 @@ func writeKeyToFile(key, filePath string) error { return nil } + +// applyExternalDefaults fills in settings a config authored elsewhere cannot +// know. External vaults created before the link registry existed have no +// storage path, so this has to run when opening a vault as well as creating one. +func applyExternalDefaults(cfg *VaultConfig) { + if cfg.External != nil && cfg.External.StoragePath == "" { + cfg.External.StoragePath = CacheDirectory(cfg.ID) + } +}