Replace symlinks with plain files containing relative paths

- Remove all symlink creation and resolution in favor of plain files
- currentvault file now contains relative path like "vaults.d/default"
- current-unlocker file now contains relative path like "unlockers.d/passphrase"
- current version file now contains relative path like "versions/20231215.001"
- Simplify path resolution to just read file contents and join with parent dir
- Update all tests to read files instead of using os.Readlink
This commit is contained in:
2025-12-23 11:53:28 +07:00
parent 18fb79e971
commit 949a5aee61
6 changed files with 144 additions and 310 deletions

View File

@@ -332,16 +332,12 @@ func test01Initialize(t *testing.T, tempDir, testMnemonic, testPassphrase string
defaultVaultDir := filepath.Join(vaultsDir, "default")
verifyFileExists(t, defaultVaultDir)
// Check currentvault symlink - it may be absolute or relative
currentVaultLink := filepath.Join(tempDir, "currentvault")
target, err := os.Readlink(currentVaultLink)
require.NoError(t, err, "should be able to read currentvault symlink")
// Check if it points to the right place (handle both absolute and relative)
if filepath.IsAbs(target) {
assert.Equal(t, filepath.Join(tempDir, "vaults.d/default"), target)
} else {
assert.Equal(t, "vaults.d/default", target)
}
// Check currentvault file contains the relative path
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")
// Verify vault structure
pubKeyFile := filepath.Join(defaultVaultDir, "pub.age")
@@ -366,22 +362,12 @@ func test01Initialize(t *testing.T, tempDir, testMnemonic, testPassphrase string
encryptedLTPubKey := filepath.Join(passphraseUnlockerDir, "pub.age")
verifyFileExists(t, encryptedLTPubKey)
// Check current-unlocker file
// Check current-unlocker file contains the relative path
currentUnlockerFile := filepath.Join(defaultVaultDir, "current-unlocker")
verifyFileExists(t, currentUnlockerFile)
// Read the current-unlocker symlink to see what it points to
symlinkTarget, err := os.Readlink(currentUnlockerFile)
if err != nil {
t.Logf("DEBUG: failed to read symlink %s: %v", currentUnlockerFile, err)
// Fallback to reading as file if it's not a symlink
currentUnlockerContent := readFile(t, currentUnlockerFile)
t.Logf("DEBUG: current-unlocker file content: %q", string(currentUnlockerContent))
assert.Contains(t, string(currentUnlockerContent), "passphrase", "current unlocker should be passphrase type")
} else {
t.Logf("DEBUG: current-unlocker symlink points to: %q", symlinkTarget)
assert.Contains(t, symlinkTarget, "passphrase", "current unlocker should be passphrase type")
}
currentUnlockerContent := readFile(t, currentUnlockerFile)
assert.Contains(t, string(currentUnlockerContent), "passphrase", "current unlocker should point to passphrase type")
// Verify vault-metadata.json in vault
vaultMetadata := filepath.Join(defaultVaultDir, "vault-metadata.json")
@@ -472,17 +458,12 @@ func test03CreateVault(t *testing.T, tempDir string, runSecret func(...string) (
workVaultDir := filepath.Join(tempDir, "vaults.d", "work")
verifyFileExists(t, workVaultDir)
// Check currentvault symlink was updated
currentVaultLink := filepath.Join(tempDir, "currentvault")
target, err := os.Readlink(currentVaultLink)
require.NoError(t, err, "should be able to read currentvault symlink")
// The symlink should now point to work vault
if filepath.IsAbs(target) {
assert.Equal(t, filepath.Join(tempDir, "vaults.d/work"), target)
} else {
assert.Equal(t, "vaults.d/work", target)
}
// Check currentvault file was updated
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/work", target, "currentvault should contain relative path to work")
// Verify work vault has basic structure
unlockersDir := filepath.Join(workVaultDir, "unlockers.d")
@@ -608,15 +589,16 @@ func test05AddSecret(t *testing.T, tempDir, testMnemonic string, runSecret func(
metadataFile := filepath.Join(versionDir, "metadata.age")
verifyFileExists(t, metadataFile)
// Check current symlink
// Check current file
currentLink := filepath.Join(secretDir, "current")
verifyFileExists(t, currentLink)
// Verify symlink points to the version directory
target, err := os.Readlink(currentLink)
require.NoError(t, err, "should read current symlink")
// Verify current file contains the version path
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 symlink should point to version")
assert.Equal(t, expectedTarget, target, "current file should point to version")
// Verify we can retrieve the secret
getOutput, err := runSecretWithEnv(map[string]string{
@@ -698,12 +680,13 @@ func test07AddSecretVersion(t *testing.T, tempDir, testMnemonic string, runSecre
verifyFileExists(t, filepath.Join(versionDir, "metadata.age"))
}
// Check current symlink points to new version
// Check current file points to new version
currentLink := filepath.Join(secretDir, "current")
target, err := os.Readlink(currentLink)
require.NoError(t, err, "should read current symlink")
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 symlink should point to new version")
assert.Equal(t, expectedTarget, target, "current file should point to new version")
// Verify we get the new value when retrieving the secret
getOutput, err := runSecretWithEnv(map[string]string{
@@ -815,8 +798,9 @@ func test10PromoteVersion(t *testing.T, tempDir, testMnemonic string, runSecret
// Before promotion, current should point to .002 (from test 07)
currentLink := filepath.Join(defaultVaultDir, "secrets.d", "database%password", "current")
target, err := os.Readlink(currentLink)
require.NoError(t, err, "should read current symlink")
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")
// Promote the old version
@@ -828,11 +812,12 @@ func test10PromoteVersion(t *testing.T, tempDir, testMnemonic string, runSecret
assert.Contains(t, output, "Promoted version", "should confirm promotion")
assert.Contains(t, output, version001, "should mention the promoted version")
// Verify symlink was updated
newTarget, err := os.Readlink(currentLink)
require.NoError(t, err, "should read current symlink after promotion")
// Verify current file was updated
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 symlink 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
getOutput, err := runSecretWithEnv(map[string]string{
@@ -1251,27 +1236,21 @@ func test14SwitchVault(t *testing.T, tempDir string, runSecret func(...string) (
require.NoError(t, err, "vault select default should succeed")
// Verify current vault is default
currentVaultLink := filepath.Join(tempDir, "currentvault")
target, err := os.Readlink(currentVaultLink)
require.NoError(t, err, "should read currentvault symlink")
if filepath.IsAbs(target) {
assert.Contains(t, target, "vaults.d/default")
} else {
assert.Contains(t, target, "default")
}
currentVaultFile := filepath.Join(tempDir, "currentvault")
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")
// Switch to work vault
_, err = runSecret("vault", "select", "work")
require.NoError(t, err, "vault select work should succeed")
// Verify current vault is now work
target, err = os.Readlink(currentVaultLink)
require.NoError(t, err, "should read currentvault symlink")
if filepath.IsAbs(target) {
assert.Contains(t, target, "vaults.d/work")
} else {
assert.Contains(t, target, "work")
}
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")
// Switch back to default
_, err = runSecret("vault", "select", "default")
@@ -2006,26 +1985,28 @@ func test28VaultMetadata(t *testing.T, tempDir string) {
}
func test29SymlinkHandling(t *testing.T, tempDir, secretPath, testMnemonic string) {
// Test currentvault symlink
currentVaultLink := filepath.Join(tempDir, "currentvault")
verifyFileExists(t, currentVaultLink)
// Test currentvault file
currentVaultFile := filepath.Join(tempDir, "currentvault")
verifyFileExists(t, currentVaultFile)
// Read the symlink
target, err := os.Readlink(currentVaultLink)
require.NoError(t, err, "should read currentvault symlink")
// Read the file
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")
// Test version current symlink
// Test version current file
defaultVaultDir := filepath.Join(tempDir, "vaults.d", "default")
secretDir := filepath.Join(defaultVaultDir, "secrets.d", "database%password")
currentLink := filepath.Join(secretDir, "current")
verifyFileExists(t, currentLink)
target, err = os.Readlink(currentLink)
require.NoError(t, err, "should read current version symlink")
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")
// Test that symlinks update properly
// Test that current file updates properly
// Add new version
cmd := exec.Command(secretPath, "add", "database/password", "--force")
cmd.Env = []string{
@@ -2038,11 +2019,12 @@ func test29SymlinkHandling(t *testing.T, tempDir, secretPath, testMnemonic strin
_, err = cmd.CombinedOutput()
require.NoError(t, err, "add new version should succeed")
// Check that symlink was updated
newTarget, err := os.Readlink(currentLink)
require.NoError(t, err, "should read updated symlink")
assert.NotEqual(t, target, newTarget, "symlink should point to new version")
assert.Contains(t, newTarget, "versions", "new symlink should still point to versions directory")
// Check that current file was updated
newTargetBytes, err := os.ReadFile(currentLink)
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")
}
func test30BackupRestore(t *testing.T, tempDir, secretPath, testMnemonic string, runSecretWithEnv func(map[string]string, ...string) (string, error)) {
@@ -2078,18 +2060,11 @@ func test30BackupRestore(t *testing.T, tempDir, secretPath, testMnemonic string,
err = copyDir(filepath.Join(tempDir, "vaults.d"), filepath.Join(backupDir, "vaults.d"))
require.NoError(t, err, "backup vaults should succeed")
// Also backup the currentvault symlink/file
// Also backup the currentvault file
currentVaultSrc := filepath.Join(tempDir, "currentvault")
currentVaultDst := filepath.Join(backupDir, "currentvault")
if target, err := os.Readlink(currentVaultSrc); err == nil {
// It's a symlink, recreate it
err = os.Symlink(target, currentVaultDst)
require.NoError(t, err, "backup currentvault symlink should succeed")
} else {
// It's a regular file, copy it
data := readFile(t, currentVaultSrc)
writeFile(t, currentVaultDst, data)
}
data := readFile(t, currentVaultSrc)
writeFile(t, currentVaultDst, data)
// Add more secrets after backup
cmd := exec.Command(secretPath, "add", "post-backup/secret", "--force")
@@ -2119,13 +2094,8 @@ func test30BackupRestore(t *testing.T, tempDir, secretPath, testMnemonic string,
// Restore currentvault
os.Remove(currentVaultSrc)
if target, err := os.Readlink(currentVaultDst); err == nil {
err = os.Symlink(target, currentVaultSrc)
require.NoError(t, err, "restore currentvault symlink should succeed")
} else {
data := readFile(t, currentVaultDst)
writeFile(t, currentVaultSrc, data)
}
restoredData := readFile(t, currentVaultDst)
writeFile(t, currentVaultSrc, restoredData)
// Verify original secrets are restored
output, err = runSecretWithEnv(map[string]string{
@@ -2267,18 +2237,7 @@ func copyDir(src, dst string) error {
srcPath := filepath.Join(src, entry.Name())
dstPath := filepath.Join(dst, entry.Name())
// Check if it's a symlink
if info, err := os.Lstat(srcPath); err == nil && info.Mode()&os.ModeSymlink != 0 {
// It's a symlink - read and recreate it
target, err := os.Readlink(srcPath)
if err != nil {
return err
}
err = os.Symlink(target, dstPath)
if err != nil {
return err
}
} else if entry.IsDir() {
if entry.IsDir() {
err = copyDir(srcPath, dstPath)
if err != nil {
return err