Ask before removing a secret, version, vault or unlocker (closes #39)
check / check (push) Failing after 2s
check / check (push) Failing after 2s
secret rm, secret version rm, secret vault remove and secret unlocker remove ask [y/N] on a terminal, naming what they remove, and go ahead only on y or yes. Without --force, a command whose stdin is not a terminal fails at once. --force, now also on rm and version rm, removes without asking; it replaces the old refusals to remove a vault with secrets or the last unlocker without --force. The checks run and the question is asked before the state directory lock is taken; under the lock the checks run again, and nothing is removed if they would ask a different question. Model: opus-5-5
This commit was merged in pull request #100.
This commit is contained in:
+62
-25
@@ -89,7 +89,8 @@ func VersionCommands(cli *Instance) *cobra.Command {
|
||||
Aliases: []string{"rm"},
|
||||
Short: "Remove a specific version of a secret",
|
||||
Long: "Remove a specific version of a secret. Cannot remove the " +
|
||||
"current version.",
|
||||
"current version. Asks for confirmation first; when stdin " +
|
||||
"is not a terminal, fails unless --force is given.",
|
||||
Args: cobra.ExactArgs(2), //nolint:mnd // secret-name and version args
|
||||
ValidArgsFunction: func(
|
||||
cmd *cobra.Command, args []string, toComplete string,
|
||||
@@ -102,10 +103,15 @@ func VersionCommands(cli *Instance) *cobra.Command {
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return cli.RemoveVersion(cmd, args[0], args[1])
|
||||
force, _ := cmd.Flags().GetBool("force")
|
||||
|
||||
return cli.RemoveVersion(cmd, args[0], args[1], force)
|
||||
},
|
||||
}
|
||||
|
||||
removeCmd.Flags().BoolP("force", "f", false,
|
||||
"Remove without asking for confirmation")
|
||||
|
||||
versionCmd.AddCommand(listCmd, promoteCmd, removeCmd)
|
||||
|
||||
return versionCmd
|
||||
@@ -297,30 +303,62 @@ func (cli *Instance) PromoteVersion(
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveVersion removes a specific version of a secret
|
||||
// RemoveVersion removes a specific version of a secret, after asking the
|
||||
// user to confirm unless force is set.
|
||||
func (cli *Instance) RemoveVersion(
|
||||
cmd *cobra.Command, secretName string, version string,
|
||||
cmd *cobra.Command, secretName string, version string, force bool,
|
||||
) error {
|
||||
err := vault.ValidateSecretName(secretName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
release, err := vault.LockStateDir(cli.fs, cli.stateDir)
|
||||
var found versionToRemove
|
||||
|
||||
release, err := cli.askThenLock(cmd, force, func() (string, error) {
|
||||
var err error
|
||||
|
||||
found, err = cli.findVersionToRemove(secretName, version)
|
||||
|
||||
return found.question, err
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer release()
|
||||
|
||||
// Get current vault
|
||||
err = secret.RemoveDirAtomic(cli.fs, found.dir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to remove version: %w", err)
|
||||
}
|
||||
|
||||
cmd.Printf("Removed version %s of secret '%s'\n", version, secretName)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// versionToRemove is what removing a version removes, as
|
||||
// findVersionToRemove found it.
|
||||
type versionToRemove struct {
|
||||
// dir is the version's directory.
|
||||
dir string
|
||||
// question names what is removed, for the user to confirm.
|
||||
question string
|
||||
}
|
||||
|
||||
// findVersionToRemove checks that the version exists in the secret in the
|
||||
// current vault and is not its current version.
|
||||
func (cli *Instance) findVersionToRemove(
|
||||
secretName, version string,
|
||||
) (versionToRemove, error) {
|
||||
vlt, err := vault.GetCurrentVault(cli.fs, cli.stateDir)
|
||||
if err != nil {
|
||||
return err
|
||||
return versionToRemove{}, err
|
||||
}
|
||||
|
||||
vaultDir, err := vlt.GetDirectory()
|
||||
if err != nil {
|
||||
return err
|
||||
return versionToRemove{}, err
|
||||
}
|
||||
|
||||
// Get the encoded secret name
|
||||
@@ -330,45 +368,44 @@ func (cli *Instance) RemoveVersion(
|
||||
// Check if secret exists
|
||||
exists, err := afero.DirExists(cli.fs, secretDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to check if secret exists: %w", err)
|
||||
return versionToRemove{},
|
||||
fmt.Errorf("failed to check if secret exists: %w", err)
|
||||
}
|
||||
|
||||
if !exists {
|
||||
return fmt.Errorf("secret '%s' %w", secretName, errSecretNotFound)
|
||||
return versionToRemove{},
|
||||
fmt.Errorf("secret '%s' %w", secretName, errSecretNotFound)
|
||||
}
|
||||
|
||||
// Check if version exists
|
||||
exists, err = secret.VersionExists(cli.fs, secretDir, version)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to check if version exists: %w", err)
|
||||
return versionToRemove{},
|
||||
fmt.Errorf("failed to check if version exists: %w", err)
|
||||
}
|
||||
|
||||
if !exists {
|
||||
return fmt.Errorf("version '%s' %w '%s'",
|
||||
return versionToRemove{}, fmt.Errorf("version '%s' %w '%s'",
|
||||
version, errVersionNotFound, secretName)
|
||||
}
|
||||
|
||||
// Get current version
|
||||
currentVersion, err := secret.GetCurrentVersion(cli.fs, secretDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get current version: %w", err)
|
||||
return versionToRemove{},
|
||||
fmt.Errorf("failed to get current version: %w", err)
|
||||
}
|
||||
|
||||
// Don't allow removing the current version
|
||||
if version == currentVersion {
|
||||
return fmt.Errorf("cannot remove the current version '%s'; %w",
|
||||
return versionToRemove{}, fmt.Errorf(
|
||||
"cannot remove the current version '%s'; %w",
|
||||
version, errCannotRemoveCurrentVersion)
|
||||
}
|
||||
|
||||
// Remove the version directory
|
||||
versionDir := filepath.Join(secretDir, "versions", version)
|
||||
|
||||
err = secret.RemoveDirAtomic(cli.fs, versionDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to remove version: %w", err)
|
||||
}
|
||||
|
||||
cmd.Printf("Removed version %s of secret '%s'\n", version, secretName)
|
||||
|
||||
return nil
|
||||
return versionToRemove{
|
||||
dir: filepath.Join(secretDir, "versions", version),
|
||||
question: fmt.Sprintf("Permanently remove version %s of secret "+
|
||||
"'%s' from vault '%s'?", version, secretName, vlt.GetName()),
|
||||
}, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user