forked from sneak/secret
Add blank lines before return statements in all files to satisfy the nlreturn linter. This improves code readability by providing visual separation before return statements. Changes made across 24 files: - internal/cli/*.go - internal/secret/*.go - internal/vault/*.go - pkg/agehd/agehd.go - pkg/bip85/bip85.go All 143 nlreturn issues have been resolved.
210 lines
5.5 KiB
Go
210 lines
5.5 KiB
Go
package cli
|
|
|
|
import (
|
|
"fmt"
|
|
"path/filepath"
|
|
"strings"
|
|
"text/tabwriter"
|
|
|
|
"git.eeqj.de/sneak/secret/internal/secret"
|
|
"git.eeqj.de/sneak/secret/internal/vault"
|
|
"github.com/spf13/afero"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
const (
|
|
tabWriterPadding = 2
|
|
)
|
|
|
|
// newVersionCmd returns the version management command
|
|
func newVersionCmd() *cobra.Command {
|
|
cli := NewCLIInstance()
|
|
|
|
return VersionCommands(cli)
|
|
}
|
|
|
|
// VersionCommands returns the version management commands
|
|
func VersionCommands(cli *Instance) *cobra.Command {
|
|
versionCmd := &cobra.Command{
|
|
Use: "version",
|
|
Short: "Manage secret versions",
|
|
Long: "Commands for managing secret versions including listing, promoting, and retrieving specific versions",
|
|
}
|
|
|
|
// List versions command
|
|
listCmd := &cobra.Command{
|
|
Use: "list <secret-name>",
|
|
Short: "List all versions of a secret",
|
|
Args: cobra.ExactArgs(1),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
return cli.ListVersions(cmd, args[0])
|
|
},
|
|
}
|
|
|
|
// Promote version command
|
|
promoteCmd := &cobra.Command{
|
|
Use: "promote <secret-name> <version>",
|
|
Short: "Promote a specific version to current",
|
|
Long: "Updates the current symlink to point to the specified version without modifying timestamps",
|
|
Args: cobra.ExactArgs(2), //nolint:mnd // Command requires exactly 2 arguments: secret-name and version
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
return cli.PromoteVersion(cmd, args[0], args[1])
|
|
},
|
|
}
|
|
|
|
versionCmd.AddCommand(listCmd, promoteCmd)
|
|
|
|
return versionCmd
|
|
}
|
|
|
|
// ListVersions lists all versions of a secret
|
|
func (cli *Instance) ListVersions(cmd *cobra.Command, secretName string) error {
|
|
secret.Debug("ListVersions called", "secret_name", secretName)
|
|
|
|
// Get current vault
|
|
vlt, err := vault.GetCurrentVault(cli.fs, cli.stateDir)
|
|
if err != nil {
|
|
secret.Debug("Failed to get current vault", "error", err)
|
|
|
|
return err
|
|
}
|
|
|
|
vaultDir, err := vlt.GetDirectory()
|
|
if err != nil {
|
|
secret.Debug("Failed to get vault directory", "error", err)
|
|
|
|
return err
|
|
}
|
|
|
|
// Get the encoded secret name
|
|
encodedName := strings.ReplaceAll(secretName, "/", "%")
|
|
secretDir := filepath.Join(vaultDir, "secrets.d", encodedName)
|
|
|
|
// Check if secret exists
|
|
exists, err := afero.DirExists(cli.fs, secretDir)
|
|
if err != nil {
|
|
secret.Debug("Failed to check if secret exists", "error", err)
|
|
|
|
return fmt.Errorf("failed to check if secret exists: %w", err)
|
|
}
|
|
if !exists {
|
|
secret.Debug("Secret not found", "secret_name", secretName)
|
|
|
|
return fmt.Errorf("secret '%s' not found", secretName)
|
|
}
|
|
|
|
// List all versions
|
|
versions, err := secret.ListVersions(cli.fs, secretDir)
|
|
if err != nil {
|
|
secret.Debug("Failed to list versions", "error", err)
|
|
|
|
return fmt.Errorf("failed to list versions: %w", err)
|
|
}
|
|
|
|
if len(versions) == 0 {
|
|
cmd.Println("No versions found")
|
|
|
|
return nil
|
|
}
|
|
|
|
// Get current version
|
|
currentVersion, err := secret.GetCurrentVersion(cli.fs, secretDir)
|
|
if err != nil {
|
|
secret.Debug("Failed to get current version", "error", err)
|
|
currentVersion = ""
|
|
}
|
|
|
|
// Get long-term key for decrypting metadata
|
|
ltIdentity, err := vlt.GetOrDeriveLongTermKey()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get long-term key: %w", err)
|
|
}
|
|
|
|
// Create table writer
|
|
w := tabwriter.NewWriter(cmd.OutOrStdout(), 0, 0, tabWriterPadding, ' ', 0)
|
|
_, _ = fmt.Fprintln(w, "VERSION\tCREATED\tSTATUS\tNOT_BEFORE\tNOT_AFTER")
|
|
|
|
// Load and display each version's metadata
|
|
for _, version := range versions {
|
|
sv := secret.NewVersion(vlt, secretName, version)
|
|
|
|
// Load metadata
|
|
if err := sv.LoadMetadata(ltIdentity); err != nil {
|
|
secret.Debug("Failed to load version metadata", "version", version, "error", err)
|
|
// Display version with error
|
|
status := "error"
|
|
if version == currentVersion {
|
|
status = "current (error)"
|
|
}
|
|
_, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", version, "-", status, "-", "-")
|
|
|
|
continue
|
|
}
|
|
|
|
// Determine status
|
|
status := "expired"
|
|
if version == currentVersion {
|
|
status = "current"
|
|
}
|
|
|
|
// Format timestamps
|
|
createdAt := "-"
|
|
if sv.Metadata.CreatedAt != nil {
|
|
createdAt = sv.Metadata.CreatedAt.Format("2006-01-02 15:04:05")
|
|
}
|
|
|
|
notBefore := "-"
|
|
if sv.Metadata.NotBefore != nil {
|
|
notBefore = sv.Metadata.NotBefore.Format("2006-01-02 15:04:05")
|
|
}
|
|
|
|
notAfter := "-"
|
|
if sv.Metadata.NotAfter != nil {
|
|
notAfter = sv.Metadata.NotAfter.Format("2006-01-02 15:04:05")
|
|
}
|
|
|
|
_, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", version, createdAt, status, notBefore, notAfter)
|
|
}
|
|
|
|
_ = w.Flush()
|
|
|
|
return nil
|
|
}
|
|
|
|
// PromoteVersion promotes a specific version to current
|
|
func (cli *Instance) PromoteVersion(cmd *cobra.Command, secretName string, version string) error {
|
|
// Get current vault
|
|
vlt, err := vault.GetCurrentVault(cli.fs, cli.stateDir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
vaultDir, err := vlt.GetDirectory()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Get the encoded secret name
|
|
encodedName := strings.ReplaceAll(secretName, "/", "%")
|
|
secretDir := filepath.Join(vaultDir, "secrets.d", encodedName)
|
|
|
|
// Check if version exists
|
|
versionDir := filepath.Join(secretDir, "versions", version)
|
|
exists, err := afero.DirExists(cli.fs, versionDir)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to check if version exists: %w", err)
|
|
}
|
|
if !exists {
|
|
return fmt.Errorf("version '%s' not found for secret '%s'", version, secretName)
|
|
}
|
|
|
|
// Update the current symlink using the proper function
|
|
if err := secret.SetCurrentVersion(cli.fs, secretDir, version); err != nil {
|
|
return fmt.Errorf("failed to update current version: %w", err)
|
|
}
|
|
|
|
cmd.Printf("Promoted version %s to current for secret '%s'\n", version, secretName)
|
|
|
|
return nil
|
|
}
|