Switch from relative paths to bare names in pointer files

- currentvault now contains just the vault name (e.g., "default")
- current-unlocker now contains just the unlocker name (e.g., "passphrase")
- current version file now contains just the version (e.g., "20231215.001")
- Resolution functions prepend the appropriate directory prefix
This commit is contained in:
Jeffrey Paul 2025-12-23 13:43:10 +07:00
parent 949a5aee61
commit 20690ba652
7 changed files with 50 additions and 63 deletions

View File

@ -332,12 +332,12 @@ func test01Initialize(t *testing.T, tempDir, testMnemonic, testPassphrase string
defaultVaultDir := filepath.Join(vaultsDir, "default") defaultVaultDir := filepath.Join(vaultsDir, "default")
verifyFileExists(t, defaultVaultDir) verifyFileExists(t, defaultVaultDir)
// Check currentvault file contains the relative path // Check currentvault file contains the vault name
currentVaultFile := filepath.Join(tempDir, "currentvault") currentVaultFile := filepath.Join(tempDir, "currentvault")
targetBytes, err := os.ReadFile(currentVaultFile) targetBytes, err := os.ReadFile(currentVaultFile)
require.NoError(t, err, "should be able to read currentvault file") require.NoError(t, err, "should be able to read currentvault file")
target := string(targetBytes) target := string(targetBytes)
assert.Equal(t, "vaults.d/default", target, "currentvault should contain relative path") assert.Equal(t, "default", target, "currentvault should contain vault name")
// Verify vault structure // Verify vault structure
pubKeyFile := filepath.Join(defaultVaultDir, "pub.age") pubKeyFile := filepath.Join(defaultVaultDir, "pub.age")
@ -463,7 +463,7 @@ func test03CreateVault(t *testing.T, tempDir string, runSecret func(...string) (
targetBytes, err := os.ReadFile(currentVaultFile) targetBytes, err := os.ReadFile(currentVaultFile)
require.NoError(t, err, "should be able to read currentvault file") require.NoError(t, err, "should be able to read currentvault file")
target := string(targetBytes) target := string(targetBytes)
assert.Equal(t, "vaults.d/work", target, "currentvault should contain relative path to work") assert.Equal(t, "work", target, "currentvault should contain vault name")
// Verify work vault has basic structure // Verify work vault has basic structure
unlockersDir := filepath.Join(workVaultDir, "unlockers.d") unlockersDir := filepath.Join(workVaultDir, "unlockers.d")
@ -593,12 +593,11 @@ func test05AddSecret(t *testing.T, tempDir, testMnemonic string, runSecret func(
currentLink := filepath.Join(secretDir, "current") currentLink := filepath.Join(secretDir, "current")
verifyFileExists(t, currentLink) verifyFileExists(t, currentLink)
// Verify current file contains the version path // Verify current file contains the version name
targetBytes, err := os.ReadFile(currentLink) targetBytes, err := os.ReadFile(currentLink)
require.NoError(t, err, "should read current file") require.NoError(t, err, "should read current file")
target := string(targetBytes) target := string(targetBytes)
expectedTarget := filepath.Join("versions", versionName) assert.Equal(t, versionName, target, "current file should contain version name")
assert.Equal(t, expectedTarget, target, "current file should point to version")
// Verify we can retrieve the secret // Verify we can retrieve the secret
getOutput, err := runSecretWithEnv(map[string]string{ getOutput, err := runSecretWithEnv(map[string]string{
@ -685,8 +684,7 @@ func test07AddSecretVersion(t *testing.T, tempDir, testMnemonic string, runSecre
targetBytes, err := os.ReadFile(currentLink) targetBytes, err := os.ReadFile(currentLink)
require.NoError(t, err, "should read current file") require.NoError(t, err, "should read current file")
target := string(targetBytes) target := string(targetBytes)
expectedTarget := filepath.Join("versions", newVersion) assert.Equal(t, newVersion, target, "current file should contain version name")
assert.Equal(t, expectedTarget, target, "current file should point to new version")
// Verify we get the new value when retrieving the secret // Verify we get the new value when retrieving the secret
getOutput, err := runSecretWithEnv(map[string]string{ getOutput, err := runSecretWithEnv(map[string]string{
@ -801,7 +799,7 @@ func test10PromoteVersion(t *testing.T, tempDir, testMnemonic string, runSecret
targetBytes, err := os.ReadFile(currentLink) targetBytes, err := os.ReadFile(currentLink)
require.NoError(t, err, "should read current file") require.NoError(t, err, "should read current file")
target := string(targetBytes) target := string(targetBytes)
assert.Equal(t, filepath.Join("versions", version002), target, "current should initially point to .002") assert.Equal(t, version002, target, "current should initially point to .002")
// Promote the old version // Promote the old version
output, err := runSecretWithEnv(map[string]string{ output, err := runSecretWithEnv(map[string]string{
@ -816,8 +814,7 @@ func test10PromoteVersion(t *testing.T, tempDir, testMnemonic string, runSecret
newTargetBytes, err := os.ReadFile(currentLink) newTargetBytes, err := os.ReadFile(currentLink)
require.NoError(t, err, "should read current file after promotion") require.NoError(t, err, "should read current file after promotion")
newTarget := string(newTargetBytes) newTarget := string(newTargetBytes)
expectedTarget := filepath.Join("versions", version001) assert.Equal(t, version001, newTarget, "current file should now point to .001")
assert.Equal(t, expectedTarget, newTarget, "current file should now point to .001")
// Verify we now get the old value when retrieving the secret // Verify we now get the old value when retrieving the secret
getOutput, err := runSecretWithEnv(map[string]string{ getOutput, err := runSecretWithEnv(map[string]string{
@ -1240,7 +1237,7 @@ func test14SwitchVault(t *testing.T, tempDir string, runSecret func(...string) (
targetBytes, err := os.ReadFile(currentVaultFile) targetBytes, err := os.ReadFile(currentVaultFile)
require.NoError(t, err, "should read currentvault file") require.NoError(t, err, "should read currentvault file")
target := string(targetBytes) target := string(targetBytes)
assert.Equal(t, "vaults.d/default", target, "currentvault should point to default") assert.Equal(t, "default", target, "currentvault should contain vault name")
// Switch to work vault // Switch to work vault
_, err = runSecret("vault", "select", "work") _, err = runSecret("vault", "select", "work")
@ -1250,7 +1247,7 @@ func test14SwitchVault(t *testing.T, tempDir string, runSecret func(...string) (
targetBytes, err = os.ReadFile(currentVaultFile) targetBytes, err = os.ReadFile(currentVaultFile)
require.NoError(t, err, "should read currentvault file") require.NoError(t, err, "should read currentvault file")
target = string(targetBytes) target = string(targetBytes)
assert.Equal(t, "vaults.d/work", target, "currentvault should point to work") assert.Equal(t, "work", target, "currentvault should contain vault name")
// Switch back to default // Switch back to default
_, err = runSecret("vault", "select", "default") _, err = runSecret("vault", "select", "default")
@ -1989,11 +1986,11 @@ func test29SymlinkHandling(t *testing.T, tempDir, secretPath, testMnemonic strin
currentVaultFile := filepath.Join(tempDir, "currentvault") currentVaultFile := filepath.Join(tempDir, "currentvault")
verifyFileExists(t, currentVaultFile) verifyFileExists(t, currentVaultFile)
// Read the file // Read the file - should contain just the vault name
targetBytes, err := os.ReadFile(currentVaultFile) targetBytes, err := os.ReadFile(currentVaultFile)
require.NoError(t, err, "should read currentvault file") require.NoError(t, err, "should read currentvault file")
target := string(targetBytes) target := string(targetBytes)
assert.Contains(t, target, "vaults.d", "should point to vaults.d directory") assert.NotContains(t, target, "/", "should be bare vault name without path")
// Test version current file // Test version current file
defaultVaultDir := filepath.Join(tempDir, "vaults.d", "default") defaultVaultDir := filepath.Join(tempDir, "vaults.d", "default")
@ -2004,7 +2001,7 @@ func test29SymlinkHandling(t *testing.T, tempDir, secretPath, testMnemonic strin
targetBytes, err = os.ReadFile(currentLink) targetBytes, err = os.ReadFile(currentLink)
require.NoError(t, err, "should read current version file") require.NoError(t, err, "should read current version file")
target = string(targetBytes) target = string(targetBytes)
assert.Contains(t, target, "versions", "should point to versions directory") assert.NotContains(t, target, "/", "should be bare version name without path")
// Test that current file updates properly // Test that current file updates properly
// Add new version // Add new version
@ -2024,7 +2021,7 @@ func test29SymlinkHandling(t *testing.T, tempDir, secretPath, testMnemonic strin
require.NoError(t, err, "should read updated current file") require.NoError(t, err, "should read updated current file")
newTarget := string(newTargetBytes) newTarget := string(newTargetBytes)
assert.NotEqual(t, target, newTarget, "current file should point to new version") assert.NotEqual(t, target, newTarget, "current file should point to new version")
assert.Contains(t, newTarget, "versions", "new current file should still point to versions directory") assert.NotContains(t, newTarget, "/", "new current file should be bare version name")
} }
func test30BackupRestore(t *testing.T, tempDir, secretPath, testMnemonic string, runSecretWithEnv func(map[string]string, ...string) (string, error)) { func test30BackupRestore(t *testing.T, tempDir, secretPath, testMnemonic string, runSecretWithEnv func(map[string]string, ...string) (string, error)) {

View File

@ -101,10 +101,9 @@ func (m *MockVault) AddSecret(name string, value *memguard.LockedBuffer, _ bool)
return err return err
} }
// Create current symlink pointing to the version // Create current file pointing to the version (just the version name)
currentLink := filepath.Join(secretDir, "current") currentLink := filepath.Join(secretDir, "current")
// For MemMapFs, write a file with the target path if err := afero.WriteFile(m.fs, currentLink, []byte(versionName), 0o600); err != nil {
if err := afero.WriteFile(m.fs, currentLink, []byte("versions/"+versionName), 0o600); err != nil {
return err return err
} }

View File

@ -431,6 +431,7 @@ func ListVersions(fs afero.Fs, secretDir string) ([]string, error) {
} }
// GetCurrentVersion returns the version that the "current" file points to // GetCurrentVersion returns the version that the "current" file points to
// The file contains just the version name (e.g., "20231215.001")
func GetCurrentVersion(fs afero.Fs, secretDir string) (string, error) { func GetCurrentVersion(fs afero.Fs, secretDir string) (string, error) {
currentPath := filepath.Join(secretDir, "current") currentPath := filepath.Join(secretDir, "current")
@ -439,27 +440,21 @@ func GetCurrentVersion(fs afero.Fs, secretDir string) (string, error) {
return "", fmt.Errorf("failed to read current version file: %w", err) return "", fmt.Errorf("failed to read current version file: %w", err)
} }
target := strings.TrimSpace(string(fileData)) version := strings.TrimSpace(string(fileData))
// Extract version from path (e.g., "versions/20231215.001" -> "20231215.001") return version, nil
parts := strings.Split(target, "/")
if len(parts) >= 2 && parts[0] == "versions" {
return parts[1], nil
}
return "", fmt.Errorf("invalid current version file format: %s", target)
} }
// SetCurrentVersion updates the "current" file to point to a specific version // SetCurrentVersion updates the "current" file to point to a specific version
// The file contains just the version name (e.g., "20231215.001")
func SetCurrentVersion(fs afero.Fs, secretDir string, version string) error { func SetCurrentVersion(fs afero.Fs, secretDir string, version string) error {
currentPath := filepath.Join(secretDir, "current") currentPath := filepath.Join(secretDir, "current")
targetPath := filepath.Join("versions", version)
// Remove existing file if it exists // Remove existing file if it exists
_ = fs.Remove(currentPath) _ = fs.Remove(currentPath)
// Write the relative path to the file // Write just the version name to the file
if err := afero.WriteFile(fs, currentPath, []byte(targetPath), FilePerms); err != nil { if err := afero.WriteFile(fs, currentPath, []byte(version), FilePerms); err != nil {
return fmt.Errorf("failed to create current version file: %w", err) return fmt.Errorf("failed to create current version file: %w", err)
} }

View File

@ -296,12 +296,12 @@ func TestGetCurrentVersion(t *testing.T) {
fs := afero.NewMemMapFs() fs := afero.NewMemMapFs()
secretDir := "/test/secret" secretDir := "/test/secret"
// Simulate symlink with file content (works for both OsFs and MemMapFs) // The current file contains just the version name
currentPath := filepath.Join(secretDir, "current") currentPath := filepath.Join(secretDir, "current")
err := fs.MkdirAll(secretDir, 0o755) err := fs.MkdirAll(secretDir, 0o755)
require.NoError(t, err) require.NoError(t, err)
err = afero.WriteFile(fs, currentPath, []byte("versions/20231216.001"), 0o600) err = afero.WriteFile(fs, currentPath, []byte("20231216.001"), 0o600)
require.NoError(t, err) require.NoError(t, err)
version, err := GetCurrentVersion(fs, secretDir) version, err := GetCurrentVersion(fs, secretDir)

View File

@ -45,16 +45,16 @@ func TestVaultWithRealFilesystem(t *testing.T) {
t.Fatalf("Failed to get vault directory: %v", err) t.Fatalf("Failed to get vault directory: %v", err)
} }
// Verify the currentvault file exists and contains the right relative path // Verify the currentvault file exists and contains just the vault name
currentVaultPath := filepath.Join(stateDir, "currentvault") currentVaultPath := filepath.Join(stateDir, "currentvault")
currentVaultContents, err := os.ReadFile(currentVaultPath) currentVaultContents, err := os.ReadFile(currentVaultPath)
if err != nil { if err != nil {
t.Fatalf("Failed to read currentvault file: %v", err) t.Fatalf("Failed to read currentvault file: %v", err)
} }
expectedRelativePath := "vaults.d/test-vault" expectedVaultName := "test-vault"
if string(currentVaultContents) != expectedRelativePath { if string(currentVaultContents) != expectedVaultName {
t.Errorf("Expected currentvault to contain %q, got %q", expectedRelativePath, string(currentVaultContents)) t.Errorf("Expected currentvault to contain %q, got %q", expectedVaultName, string(currentVaultContents))
} }
// Test that ResolveVaultSymlink correctly resolves the path // Test that ResolveVaultSymlink correctly resolves the path

View File

@ -33,7 +33,7 @@ func isValidVaultName(name string) bool {
} }
// ResolveVaultSymlink reads the currentvault file to get the path to the current vault // ResolveVaultSymlink reads the currentvault file to get the path to the current vault
// The file contains a relative path to the vault directory // The file contains just the vault name (e.g., "default")
func ResolveVaultSymlink(fs afero.Fs, currentVaultPath string) (string, error) { func ResolveVaultSymlink(fs afero.Fs, currentVaultPath string) (string, error) {
secret.Debug("resolveVaultSymlink starting", "path", currentVaultPath) secret.Debug("resolveVaultSymlink starting", "path", currentVaultPath)
@ -44,13 +44,13 @@ func ResolveVaultSymlink(fs afero.Fs, currentVaultPath string) (string, error) {
return "", fmt.Errorf("failed to read currentvault file: %w", err) return "", fmt.Errorf("failed to read currentvault file: %w", err)
} }
// The file contains a relative path like "vaults.d/default" // The file contains just the vault name like "default"
relativePath := strings.TrimSpace(string(fileData)) vaultName := strings.TrimSpace(string(fileData))
secret.Debug("Read relative path from file", "relative_path", relativePath) secret.Debug("Read vault name from file", "vault_name", vaultName)
// Resolve to absolute path relative to the state directory // Resolve to absolute path: stateDir/vaults.d/vaultName
stateDir := filepath.Dir(currentVaultPath) stateDir := filepath.Dir(currentVaultPath)
absolutePath := filepath.Join(stateDir, relativePath) absolutePath := filepath.Join(stateDir, "vaults.d", vaultName)
secret.Debug("Resolved to absolute path", "absolute_path", absolutePath) secret.Debug("Resolved to absolute path", "absolute_path", absolutePath)
@ -256,9 +256,8 @@ func SelectVault(fs afero.Fs, stateDir string, name string) error {
return fmt.Errorf("vault %s does not exist", name) return fmt.Errorf("vault %s does not exist", name)
} }
// Create or update the currentvault file with the relative path // Create or update the currentvault file with just the vault name
currentVaultPath := filepath.Join(stateDir, "currentvault") currentVaultPath := filepath.Join(stateDir, "currentvault")
relativePath := filepath.Join("vaults.d", name)
// Remove existing file if it exists // Remove existing file if it exists
if _, err := fs.Stat(currentVaultPath); err == nil { if _, err := fs.Stat(currentVaultPath); err == nil {
@ -266,9 +265,9 @@ func SelectVault(fs afero.Fs, stateDir string, name string) error {
_ = fs.Remove(currentVaultPath) _ = fs.Remove(currentVaultPath)
} }
// Write the relative path to the file // Write just the vault name to the file
secret.Debug("Writing currentvault file", "relative_path", relativePath) secret.Debug("Writing currentvault file", "vault_name", name)
if err := afero.WriteFile(fs, currentVaultPath, []byte(relativePath), secret.FilePerms); err != nil { if err := afero.WriteFile(fs, currentVaultPath, []byte(name), secret.FilePerms); err != nil {
return fmt.Errorf("failed to select vault: %w", err) return fmt.Errorf("failed to select vault: %w", err)
} }

View File

@ -99,23 +99,23 @@ func (v *Vault) GetCurrentUnlocker() (secret.Unlocker, error) {
} }
// resolveUnlockerDirectory reads the current-unlocker file to get the unlocker directory path // resolveUnlockerDirectory reads the current-unlocker file to get the unlocker directory path
// The file contains a relative path to the unlocker directory // The file contains just the unlocker name (e.g., "passphrase")
func (v *Vault) resolveUnlockerDirectory(currentUnlockerPath string) (string, error) { func (v *Vault) resolveUnlockerDirectory(currentUnlockerPath string) (string, error) {
secret.Debug("Reading current-unlocker file", "path", currentUnlockerPath) secret.Debug("Reading current-unlocker file", "path", currentUnlockerPath)
unlockerDirBytes, err := afero.ReadFile(v.fs, currentUnlockerPath) unlockerNameBytes, err := afero.ReadFile(v.fs, currentUnlockerPath)
if err != nil { if err != nil {
secret.Debug("Failed to read current-unlocker file", "error", err, "path", currentUnlockerPath) secret.Debug("Failed to read current-unlocker file", "error", err, "path", currentUnlockerPath)
return "", fmt.Errorf("failed to read current unlocker: %w", err) return "", fmt.Errorf("failed to read current unlocker: %w", err)
} }
relativePath := strings.TrimSpace(string(unlockerDirBytes)) unlockerName := strings.TrimSpace(string(unlockerNameBytes))
secret.Debug("Read relative path from file", "relative_path", relativePath) secret.Debug("Read unlocker name from file", "unlocker_name", unlockerName)
// Resolve to absolute path relative to the vault directory // Resolve to absolute path: vaultDir/unlockers.d/unlockerName
vaultDir := filepath.Dir(currentUnlockerPath) vaultDir := filepath.Dir(currentUnlockerPath)
absolutePath := filepath.Join(vaultDir, relativePath) absolutePath := filepath.Join(vaultDir, "unlockers.d", unlockerName)
secret.Debug("Resolved to absolute path", "absolute_path", absolutePath) secret.Debug("Resolved to absolute path", "absolute_path", absolutePath)
@ -277,7 +277,7 @@ func (v *Vault) SelectUnlocker(unlockerID string) error {
return fmt.Errorf("unlocker with ID %s not found", unlockerID) return fmt.Errorf("unlocker with ID %s not found", unlockerID)
} }
// Create/update current-unlocker file with relative path // Create/update current-unlocker file with just the unlocker name
currentUnlockerPath := filepath.Join(vaultDir, "current-unlocker") currentUnlockerPath := filepath.Join(vaultDir, "current-unlocker")
// Remove existing file if it exists // Remove existing file if it exists
@ -289,15 +289,12 @@ func (v *Vault) SelectUnlocker(unlockerID string) error {
} }
} }
// Compute relative path from vault directory to unlocker directory // Get just the unlocker name (basename of the directory)
relativePath, err := filepath.Rel(vaultDir, targetUnlockerDir) unlockerName := filepath.Base(targetUnlockerDir)
if err != nil {
return fmt.Errorf("failed to compute relative path: %w", err)
}
// Write the relative path to the file // Write just the unlocker name to the file
secret.Debug("Writing current-unlocker file", "relative_path", relativePath) secret.Debug("Writing current-unlocker file", "unlocker_name", unlockerName)
if err := afero.WriteFile(v.fs, currentUnlockerPath, []byte(relativePath), secret.FilePerms); err != nil { if err := afero.WriteFile(v.fs, currentUnlockerPath, []byte(unlockerName), secret.FilePerms); err != nil {
return fmt.Errorf("failed to create current-unlocker file: %w", err) return fmt.Errorf("failed to create current-unlocker file: %w", err)
} }