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")
verifyFileExists(t, defaultVaultDir)
// Check currentvault file contains the relative path
// Check currentvault file contains the vault name
currentVaultFile := filepath.Join(tempDir, "currentvault")
targetBytes, err := os.ReadFile(currentVaultFile)
require.NoError(t, err, "should be able to read currentvault file")
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
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)
require.NoError(t, err, "should be able to read currentvault file")
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
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")
verifyFileExists(t, currentLink)
// Verify current file contains the version path
// Verify current file contains the version name
targetBytes, err := os.ReadFile(currentLink)
require.NoError(t, err, "should read current file")
target := string(targetBytes)
expectedTarget := filepath.Join("versions", versionName)
assert.Equal(t, expectedTarget, target, "current file should point to version")
assert.Equal(t, versionName, target, "current file should contain version name")
// Verify we can retrieve the secret
getOutput, err := runSecretWithEnv(map[string]string{
@ -685,8 +684,7 @@ func test07AddSecretVersion(t *testing.T, tempDir, testMnemonic string, runSecre
targetBytes, err := os.ReadFile(currentLink)
require.NoError(t, err, "should read current file")
target := string(targetBytes)
expectedTarget := filepath.Join("versions", newVersion)
assert.Equal(t, expectedTarget, target, "current file should point to new version")
assert.Equal(t, newVersion, target, "current file should contain version name")
// Verify we get the new value when retrieving the secret
getOutput, err := runSecretWithEnv(map[string]string{
@ -801,7 +799,7 @@ func test10PromoteVersion(t *testing.T, tempDir, testMnemonic string, runSecret
targetBytes, err := os.ReadFile(currentLink)
require.NoError(t, err, "should read current file")
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
output, err := runSecretWithEnv(map[string]string{
@ -816,8 +814,7 @@ func test10PromoteVersion(t *testing.T, tempDir, testMnemonic string, runSecret
newTargetBytes, err := os.ReadFile(currentLink)
require.NoError(t, err, "should read current file after promotion")
newTarget := string(newTargetBytes)
expectedTarget := filepath.Join("versions", version001)
assert.Equal(t, expectedTarget, newTarget, "current file should now point to .001")
assert.Equal(t, version001, newTarget, "current file should now point to .001")
// Verify we now get the old value when retrieving the secret
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)
require.NoError(t, err, "should read currentvault file")
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
_, err = runSecret("vault", "select", "work")
@ -1250,7 +1247,7 @@ func test14SwitchVault(t *testing.T, tempDir string, runSecret func(...string) (
targetBytes, err = os.ReadFile(currentVaultFile)
require.NoError(t, err, "should read currentvault file")
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
_, err = runSecret("vault", "select", "default")
@ -1989,11 +1986,11 @@ func test29SymlinkHandling(t *testing.T, tempDir, secretPath, testMnemonic strin
currentVaultFile := filepath.Join(tempDir, "currentvault")
verifyFileExists(t, currentVaultFile)
// Read the file
// Read the file - should contain just the vault name
targetBytes, err := os.ReadFile(currentVaultFile)
require.NoError(t, err, "should read currentvault file")
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
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)
require.NoError(t, err, "should read current version file")
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
// 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")
newTarget := string(newTargetBytes)
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)) {

View File

@ -101,10 +101,9 @@ func (m *MockVault) AddSecret(name string, value *memguard.LockedBuffer, _ bool)
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")
// For MemMapFs, write a file with the target path
if err := afero.WriteFile(m.fs, currentLink, []byte("versions/"+versionName), 0o600); err != nil {
if err := afero.WriteFile(m.fs, currentLink, []byte(versionName), 0o600); err != nil {
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
// The file contains just the version name (e.g., "20231215.001")
func GetCurrentVersion(fs afero.Fs, secretDir string) (string, error) {
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)
}
target := strings.TrimSpace(string(fileData))
version := strings.TrimSpace(string(fileData))
// Extract version from path (e.g., "versions/20231215.001" -> "20231215.001")
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)
return version, nil
}
// 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 {
currentPath := filepath.Join(secretDir, "current")
targetPath := filepath.Join("versions", version)
// Remove existing file if it exists
_ = fs.Remove(currentPath)
// Write the relative path to the file
if err := afero.WriteFile(fs, currentPath, []byte(targetPath), FilePerms); err != nil {
// Write just the version name to the file
if err := afero.WriteFile(fs, currentPath, []byte(version), FilePerms); err != nil {
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()
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")
err := fs.MkdirAll(secretDir, 0o755)
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)
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)
}
// 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")
currentVaultContents, err := os.ReadFile(currentVaultPath)
if err != nil {
t.Fatalf("Failed to read currentvault file: %v", err)
}
expectedRelativePath := "vaults.d/test-vault"
if string(currentVaultContents) != expectedRelativePath {
t.Errorf("Expected currentvault to contain %q, got %q", expectedRelativePath, string(currentVaultContents))
expectedVaultName := "test-vault"
if string(currentVaultContents) != expectedVaultName {
t.Errorf("Expected currentvault to contain %q, got %q", expectedVaultName, string(currentVaultContents))
}
// 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
// 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) {
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)
}
// The file contains a relative path like "vaults.d/default"
relativePath := strings.TrimSpace(string(fileData))
secret.Debug("Read relative path from file", "relative_path", relativePath)
// The file contains just the vault name like "default"
vaultName := strings.TrimSpace(string(fileData))
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)
absolutePath := filepath.Join(stateDir, relativePath)
absolutePath := filepath.Join(stateDir, "vaults.d", vaultName)
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)
}
// 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")
relativePath := filepath.Join("vaults.d", name)
// Remove existing file if it exists
if _, err := fs.Stat(currentVaultPath); err == nil {
@ -266,9 +265,9 @@ func SelectVault(fs afero.Fs, stateDir string, name string) error {
_ = fs.Remove(currentVaultPath)
}
// Write the relative path to the file
secret.Debug("Writing currentvault file", "relative_path", relativePath)
if err := afero.WriteFile(fs, currentVaultPath, []byte(relativePath), secret.FilePerms); err != nil {
// Write just the vault name to the file
secret.Debug("Writing currentvault file", "vault_name", name)
if err := afero.WriteFile(fs, currentVaultPath, []byte(name), secret.FilePerms); err != nil {
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
// 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) {
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 {
secret.Debug("Failed to read current-unlocker file", "error", err, "path", currentUnlockerPath)
return "", fmt.Errorf("failed to read current unlocker: %w", err)
}
relativePath := strings.TrimSpace(string(unlockerDirBytes))
secret.Debug("Read relative path from file", "relative_path", relativePath)
unlockerName := strings.TrimSpace(string(unlockerNameBytes))
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)
absolutePath := filepath.Join(vaultDir, relativePath)
absolutePath := filepath.Join(vaultDir, "unlockers.d", unlockerName)
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)
}
// Create/update current-unlocker file with relative path
// Create/update current-unlocker file with just the unlocker name
currentUnlockerPath := filepath.Join(vaultDir, "current-unlocker")
// 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
relativePath, err := filepath.Rel(vaultDir, targetUnlockerDir)
if err != nil {
return fmt.Errorf("failed to compute relative path: %w", err)
}
// Get just the unlocker name (basename of the directory)
unlockerName := filepath.Base(targetUnlockerDir)
// Write the relative path to the file
secret.Debug("Writing current-unlocker file", "relative_path", relativePath)
if err := afero.WriteFile(v.fs, currentUnlockerPath, []byte(relativePath), secret.FilePerms); err != nil {
// Write just the unlocker name to the file
secret.Debug("Writing current-unlocker file", "unlocker_name", unlockerName)
if err := afero.WriteFile(v.fs, currentUnlockerPath, []byte(unlockerName), secret.FilePerms); err != nil {
return fmt.Errorf("failed to create current-unlocker file: %w", err)
}