Refactor vault functionality to dedicated package, fix import cycles with interface pattern, fix tests
This commit is contained in:
260
internal/vault/management.go
Normal file
260
internal/vault/management.go
Normal file
@@ -0,0 +1,260 @@
|
||||
package vault
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"git.eeqj.de/sneak/secret/internal/secret"
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
// Register the GetCurrentVault function with the secret package
|
||||
func init() {
|
||||
secret.RegisterGetCurrentVaultFunc(func(fs afero.Fs, stateDir string) (secret.VaultInterface, error) {
|
||||
return GetCurrentVault(fs, stateDir)
|
||||
})
|
||||
}
|
||||
|
||||
// resolveVaultSymlink resolves the currentvault symlink by changing into it and getting the absolute path
|
||||
func resolveVaultSymlink(fs afero.Fs, symlinkPath string) (string, error) {
|
||||
secret.Debug("resolveVaultSymlink starting", "symlink_path", symlinkPath)
|
||||
|
||||
// For real filesystems, we can use os.Chdir and os.Getwd
|
||||
if _, ok := fs.(*afero.OsFs); ok {
|
||||
secret.Debug("Using real filesystem symlink resolution")
|
||||
|
||||
// Check what the symlink points to first
|
||||
secret.Debug("Checking symlink target", "symlink_path", symlinkPath)
|
||||
linkTarget, err := os.Readlink(symlinkPath)
|
||||
if err != nil {
|
||||
secret.Debug("Failed to read symlink target", "error", err, "symlink_path", symlinkPath)
|
||||
// Maybe it's not a symlink, try reading as file
|
||||
secret.Debug("Trying to read as file instead of symlink")
|
||||
targetBytes, err := os.ReadFile(symlinkPath)
|
||||
if err != nil {
|
||||
secret.Debug("Failed to read as file", "error", err)
|
||||
return "", fmt.Errorf("failed to read vault symlink: %w", err)
|
||||
}
|
||||
targetPath := string(targetBytes)
|
||||
secret.Debug("Read target path from file", "target_path", targetPath)
|
||||
return targetPath, nil
|
||||
}
|
||||
|
||||
secret.Debug("Symlink points to", "target", linkTarget)
|
||||
|
||||
// Save current directory so we can restore it
|
||||
secret.Debug("Getting current directory")
|
||||
originalDir, err := os.Getwd()
|
||||
if err != nil {
|
||||
secret.Debug("Failed to get current directory", "error", err)
|
||||
return "", fmt.Errorf("failed to get current directory: %w", err)
|
||||
}
|
||||
secret.Debug("Got current directory", "original_dir", originalDir)
|
||||
|
||||
// Change to the symlink directory
|
||||
secret.Debug("Changing to symlink directory", "symlink_path", symlinkPath)
|
||||
secret.Debug("About to call os.Chdir - this might hang if symlink is broken")
|
||||
if err := os.Chdir(symlinkPath); err != nil {
|
||||
secret.Debug("Failed to change to symlink directory", "error", err)
|
||||
return "", fmt.Errorf("failed to change to symlink directory: %w", err)
|
||||
}
|
||||
secret.Debug("Changed to symlink directory successfully - os.Chdir completed")
|
||||
|
||||
// Get absolute path of current directory (which is the resolved symlink)
|
||||
secret.Debug("Getting absolute path of current directory")
|
||||
absolutePath, err := os.Getwd()
|
||||
if err != nil {
|
||||
secret.Debug("Failed to get absolute path", "error", err)
|
||||
// Try to restore original directory before returning error
|
||||
if restoreErr := os.Chdir(originalDir); restoreErr != nil {
|
||||
secret.Debug("Failed to restore original directory after error", "error", restoreErr)
|
||||
}
|
||||
return "", fmt.Errorf("failed to get absolute path: %w", err)
|
||||
}
|
||||
secret.Debug("Got absolute path", "absolute_path", absolutePath)
|
||||
|
||||
// Restore original directory
|
||||
secret.Debug("Restoring original directory", "original_dir", originalDir)
|
||||
if err := os.Chdir(originalDir); err != nil {
|
||||
secret.Debug("Failed to restore original directory", "error", err)
|
||||
// Don't return error here since we got what we needed
|
||||
} else {
|
||||
secret.Debug("Restored original directory successfully")
|
||||
}
|
||||
|
||||
secret.Debug("resolveVaultSymlink completed successfully", "result", absolutePath)
|
||||
return absolutePath, nil
|
||||
}
|
||||
|
||||
// For in-memory filesystems, read the symlink content directly
|
||||
secret.Debug("Using in-memory filesystem symlink resolution")
|
||||
content, err := afero.ReadFile(fs, symlinkPath)
|
||||
if err != nil {
|
||||
secret.Debug("Failed to read symlink content", "error", err)
|
||||
return "", fmt.Errorf("failed to read vault symlink: %w", err)
|
||||
}
|
||||
|
||||
targetPath := string(content)
|
||||
secret.Debug("Read symlink target from in-memory filesystem", "target_path", targetPath)
|
||||
return targetPath, nil
|
||||
}
|
||||
|
||||
// GetCurrentVault gets the currently selected vault
|
||||
func GetCurrentVault(fs afero.Fs, stateDir string) (*Vault, error) {
|
||||
secret.Debug("Getting current vault", "state_dir", stateDir)
|
||||
|
||||
// Check if current vault symlink exists
|
||||
currentVaultPath := filepath.Join(stateDir, "currentvault")
|
||||
secret.Debug("Checking current vault symlink", "path", currentVaultPath)
|
||||
|
||||
_, err := fs.Stat(currentVaultPath)
|
||||
if err != nil {
|
||||
secret.Debug("Failed to stat current vault symlink", "error", err, "path", currentVaultPath)
|
||||
return nil, fmt.Errorf("failed to read current vault symlink: %w", err)
|
||||
}
|
||||
secret.Debug("Current vault symlink exists")
|
||||
|
||||
// Resolve symlink to get target path
|
||||
secret.Debug("Resolving vault symlink")
|
||||
targetPath, err := resolveVaultSymlink(fs, currentVaultPath)
|
||||
if err != nil {
|
||||
secret.Debug("Failed to resolve vault symlink", "error", err)
|
||||
return nil, fmt.Errorf("failed to resolve vault symlink: %w", err)
|
||||
}
|
||||
secret.Debug("Resolved vault symlink", "target_path", targetPath)
|
||||
|
||||
// Extract vault name from target path
|
||||
vaultName := filepath.Base(targetPath)
|
||||
secret.Debug("Extracted vault name", "vault_name", vaultName)
|
||||
|
||||
secret.Debug("Current vault resolved", "vault_name", vaultName, "target_path", targetPath)
|
||||
|
||||
// Create and return Vault instance
|
||||
secret.Debug("Creating NewVault instance")
|
||||
vault := NewVault(fs, vaultName, stateDir)
|
||||
secret.Debug("Created NewVault instance successfully")
|
||||
|
||||
return vault, nil
|
||||
}
|
||||
|
||||
// ListVaults returns a list of available vault names
|
||||
func ListVaults(fs afero.Fs, stateDir string) ([]string, error) {
|
||||
vaultsDir := filepath.Join(stateDir, "vaults.d")
|
||||
|
||||
// Check if vaults directory exists
|
||||
exists, err := afero.DirExists(fs, vaultsDir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to check vaults directory: %w", err)
|
||||
}
|
||||
if !exists {
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
// Read directory contents
|
||||
files, err := afero.ReadDir(fs, vaultsDir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read vaults directory: %w", err)
|
||||
}
|
||||
|
||||
var vaults []string
|
||||
for _, file := range files {
|
||||
if file.IsDir() {
|
||||
vaults = append(vaults, file.Name())
|
||||
}
|
||||
}
|
||||
|
||||
return vaults, nil
|
||||
}
|
||||
|
||||
// CreateVault creates a new vault with the given name
|
||||
func CreateVault(fs afero.Fs, stateDir string, name string) (*Vault, error) {
|
||||
secret.Debug("Creating new vault", "name", name, "state_dir", stateDir)
|
||||
|
||||
// Create vault directory structure
|
||||
vaultDir := filepath.Join(stateDir, "vaults.d", name)
|
||||
secret.Debug("Creating vault directory structure", "vault_dir", vaultDir)
|
||||
|
||||
// Check if vault already exists
|
||||
exists, err := afero.DirExists(fs, vaultDir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to check if vault exists: %w", err)
|
||||
}
|
||||
if exists {
|
||||
return nil, fmt.Errorf("vault %s already exists", name)
|
||||
}
|
||||
|
||||
// Create vault directory
|
||||
if err := fs.MkdirAll(vaultDir, 0700); err != nil {
|
||||
return nil, fmt.Errorf("failed to create vault directory: %w", err)
|
||||
}
|
||||
|
||||
// Create subdirectories
|
||||
secretsDir := filepath.Join(vaultDir, "secrets.d")
|
||||
if err := fs.MkdirAll(secretsDir, 0700); err != nil {
|
||||
return nil, fmt.Errorf("failed to create secrets directory: %w", err)
|
||||
}
|
||||
|
||||
unlockKeysDir := filepath.Join(vaultDir, "unlock.d")
|
||||
if err := fs.MkdirAll(unlockKeysDir, 0700); err != nil {
|
||||
return nil, fmt.Errorf("failed to create unlock keys directory: %w", err)
|
||||
}
|
||||
|
||||
// Select the new vault as current
|
||||
secret.Debug("Selecting newly created vault as current", "name", name)
|
||||
if err := SelectVault(fs, stateDir, name); err != nil {
|
||||
return nil, fmt.Errorf("failed to select new vault: %w", err)
|
||||
}
|
||||
|
||||
secret.Debug("Successfully created vault", "name", name)
|
||||
return NewVault(fs, name, stateDir), nil
|
||||
}
|
||||
|
||||
// SelectVault selects the given vault as the current vault
|
||||
func SelectVault(fs afero.Fs, stateDir string, name string) error {
|
||||
secret.Debug("Selecting vault", "vault_name", name, "state_dir", stateDir)
|
||||
|
||||
// Check if vault exists
|
||||
vaultDir := filepath.Join(stateDir, "vaults.d", name)
|
||||
exists, err := afero.DirExists(fs, vaultDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to check if vault exists: %w", err)
|
||||
}
|
||||
if !exists {
|
||||
return fmt.Errorf("vault %s does not exist", name)
|
||||
}
|
||||
|
||||
// Create/update current vault symlink
|
||||
currentVaultPath := filepath.Join(stateDir, "currentvault")
|
||||
|
||||
// Remove existing symlink if it exists
|
||||
if exists, _ := afero.Exists(fs, currentVaultPath); exists {
|
||||
secret.Debug("Removing existing current vault symlink", "path", currentVaultPath)
|
||||
if err := fs.Remove(currentVaultPath); err != nil {
|
||||
secret.Debug("Failed to remove existing symlink", "error", err, "path", currentVaultPath)
|
||||
}
|
||||
}
|
||||
|
||||
// Create new symlink pointing to the vault
|
||||
targetPath := vaultDir
|
||||
secret.Debug("Creating vault symlink", "target", targetPath, "link", currentVaultPath)
|
||||
|
||||
// For real filesystems, try to create a real symlink first
|
||||
if _, ok := fs.(*afero.OsFs); ok {
|
||||
if err := os.Symlink(targetPath, currentVaultPath); err != nil {
|
||||
// If symlink creation fails, fall back to writing target path to file
|
||||
secret.Debug("Failed to create real symlink, falling back to file", "error", err)
|
||||
if err := afero.WriteFile(fs, currentVaultPath, []byte(targetPath), 0600); err != nil {
|
||||
return fmt.Errorf("failed to create vault symlink: %w", err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// For in-memory filesystems, write target path to file
|
||||
if err := afero.WriteFile(fs, currentVaultPath, []byte(targetPath), 0600); err != nil {
|
||||
return fmt.Errorf("failed to create vault symlink: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
secret.Debug("Successfully selected vault", "vault_name", name)
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user