check / check (push) Failing after 3s
A command killed part-way could leave a temporary file or directory of secret.WriteFileAtomic or secret.TempDirFor, encrypted keys included, for good. LockStateDir now deletes them once it holds the lock, looking only in the directories those helpers make them in (the state directory, each vault, each secret, each version) and only at names that start with "." and hold ".tmp-". A vault may be named ".tmp-1", so vaults.d and the other listed directories are not searched. Commands that only read take no lock and delete nothing. A test shows that `unlocker remove` removes an unlocker directory with no metadata file. Model: opus-5-5
73 lines
2.3 KiB
Go
73 lines
2.3 KiB
Go
package cli_test
|
|
|
|
import (
|
|
"io"
|
|
"testing"
|
|
|
|
"git.eeqj.de/sneak/secret/internal/cli"
|
|
"git.eeqj.de/sneak/secret/internal/secret"
|
|
"git.eeqj.de/sneak/secret/internal/vault"
|
|
"github.com/spf13/afero"
|
|
"github.com/spf13/cobra"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// TestLeftoversRemovedByNextChangingCommand is a regression test for
|
|
// https://git.eeqj.de/sneak/secret/issues/75. It plants what a command
|
|
// killed part-way leaves in each directory where secret.TempDirFor and
|
|
// secret.WriteFileAtomic make temporary entries: a temporary directory
|
|
// holding a vault, secret, unlocker or version being added or removed, and
|
|
// a temporary file beside a file being replaced. `secret list` must leave
|
|
// them all, and the next command that takes the state directory lock, here
|
|
// `secret vault select` of the vault already current, must delete exactly
|
|
// them: a vault named like a temporary directory stays.
|
|
func TestLeftoversRemovedByNextChangingCommand(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
fs := newTwoVaultFs(t)
|
|
|
|
_, err := vault.CreateVault(fs, testStateDir, ".tmp-0", nil)
|
|
require.NoError(t, err)
|
|
require.NoError(t, vault.SelectVault(fs, testStateDir, "default"))
|
|
|
|
before := snapshotStateDir(t, fs)
|
|
|
|
vaultDir := testStateDir + "/vaults.d/default"
|
|
secretDir := vaultDir + "/secrets.d/x"
|
|
|
|
versions, err := secret.ListVersions(fs, secretDir)
|
|
require.NoError(t, err)
|
|
require.Len(t, versions, 1)
|
|
|
|
for _, dir := range []string{
|
|
testStateDir + "/.tmp-1/default",
|
|
vaultDir + "/.tmp-2/x",
|
|
secretDir + "/.tmp-3/" + testVersion,
|
|
} {
|
|
require.NoError(t, fs.MkdirAll(dir, secret.DirPerms))
|
|
require.NoError(t, afero.WriteFile(fs, dir+"/value.age",
|
|
[]byte("encrypted"), secret.FilePerms))
|
|
}
|
|
|
|
for _, file := range []string{
|
|
testStateDir + "/.currentvault.tmp-4",
|
|
vaultDir + "/.current-unlocker.tmp-5",
|
|
secretDir + "/.current.tmp-6",
|
|
secretDir + "/versions/" + versions[0] + "/.metadata.age.tmp-7",
|
|
} {
|
|
require.NoError(t, afero.WriteFile(fs, file,
|
|
[]byte("partial"), secret.FilePerms))
|
|
}
|
|
|
|
planted := snapshotStateDir(t, fs)
|
|
c := cli.NewCLIInstanceWithStateDir(fs, testStateDir)
|
|
cmd := &cobra.Command{}
|
|
cmd.SetOut(io.Discard)
|
|
|
|
require.NoError(t, c.ListSecrets(cmd, false, false, ""))
|
|
require.Equal(t, planted, snapshotStateDir(t, fs))
|
|
|
|
require.NoError(t, c.SelectVault(cmd, "default"))
|
|
require.Equal(t, before, snapshotStateDir(t, fs))
|
|
}
|