check / check (push) Waiting to run
`secret encrypt` and `secret decrypt` emptied the file named by `--output` before reading any input, so a failure left it empty, and a file given as both `--input` and `--output` was emptied before it was read. They now write into a temporary file of mode 0600 next to it, or next to the file it is a symbolic link to, and rename that over it only on success, with the new `secret.WriteFileAtomicFunc`. An output file that cannot be written still fails. Ctrl-C, SIGTERM or a hangup removes the temporary file. A device or pipe, such as `/dev/null`, is written to directly. Judgement calls: the device and pipe case; a command started with `nohup` still ignores a hangup. Model: opus-5-5
75 lines
2.4 KiB
Go
75 lines
2.4 KiB
Go
package cli_test
|
|
|
|
import (
|
|
"io"
|
|
"testing"
|
|
|
|
"github.com/spf13/afero"
|
|
"github.com/spf13/cobra"
|
|
"github.com/stretchr/testify/require"
|
|
"sneak.berlin/go/secret/internal/cli"
|
|
"sneak.berlin/go/secret/internal/secret"
|
|
"sneak.berlin/go/secret/internal/vault"
|
|
)
|
|
|
|
// 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. The copy has no
|
|
// lock file yet, so that command, as after a killed one, finds no mark that
|
|
// the last holder of the lock finished.
|
|
func TestLeftoversRemovedByNextChangingCommand(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
fs := newTwoVaultFs(t)
|
|
|
|
_, err := vault.CreateVault(fs, testStateDir, ".tmp-0", nil, 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 + "/.tmp-4",
|
|
vaultDir + "/.tmp-5",
|
|
secretDir + "/.tmp-6",
|
|
secretDir + "/versions/" + versions[0] + "/.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))
|
|
}
|