From 20690ba65214d0571dc9b65c24358d8af265e0d9 Mon Sep 17 00:00:00 2001 From: sneak Date: Tue, 23 Dec 2025 13:43:10 +0700 Subject: [PATCH] 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 --- internal/cli/integration_test.go | 31 ++++++++++++++---------------- internal/secret/secret_test.go | 5 ++--- internal/secret/version.go | 17 ++++++---------- internal/secret/version_test.go | 4 ++-- internal/vault/integration_test.go | 8 ++++---- internal/vault/management.go | 21 ++++++++++---------- internal/vault/unlockers.go | 27 ++++++++++++-------------- 7 files changed, 50 insertions(+), 63 deletions(-) diff --git a/internal/cli/integration_test.go b/internal/cli/integration_test.go index 47881c2..c0296f1 100644 --- a/internal/cli/integration_test.go +++ b/internal/cli/integration_test.go @@ -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)) { diff --git a/internal/secret/secret_test.go b/internal/secret/secret_test.go index 390f58a..c307d80 100644 --- a/internal/secret/secret_test.go +++ b/internal/secret/secret_test.go @@ -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 } diff --git a/internal/secret/version.go b/internal/secret/version.go index 8ad51a1..483b993 100644 --- a/internal/secret/version.go +++ b/internal/secret/version.go @@ -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) } diff --git a/internal/secret/version_test.go b/internal/secret/version_test.go index 6a250f1..17ee4d0 100644 --- a/internal/secret/version_test.go +++ b/internal/secret/version_test.go @@ -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) diff --git a/internal/vault/integration_test.go b/internal/vault/integration_test.go index 732323f..01fc497 100644 --- a/internal/vault/integration_test.go +++ b/internal/vault/integration_test.go @@ -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 diff --git a/internal/vault/management.go b/internal/vault/management.go index ce22315..e35ee3b 100644 --- a/internal/vault/management.go +++ b/internal/vault/management.go @@ -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) } diff --git a/internal/vault/unlockers.go b/internal/vault/unlockers.go index 04e13cb..b7a2087 100644 --- a/internal/vault/unlockers.go +++ b/internal/vault/unlockers.go @@ -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) }