Files
secret/internal/cli/completions.go
T
sneak aefec9d8e8
check / check (push) Failing after 3s
Rename the Go module to sneak.berlin/go/secret (closes #43)
REPO_POLICIES.md requires the module root sneak.berlin/go/<name>.
go.mod, every import (rewritten with gofmt -r), the -X flags in
script/build and the examples in the pkg READMEs now use the new path.
go mod tidy lists go-humanize and fatih/color as direct requirements,
since internal/cli imports them.

This breaks anyone who fetched or imported git.eeqj.de/sneak/secret:
they must switch to sneak.berlin/go/secret, which resolves to this
repository.

Model: opus-5-5
2026-10-05 00:10:56 +00:00

186 lines
5.1 KiB
Go

package cli
import (
"maps"
"slices"
"strings"
"github.com/spf13/afero"
"github.com/spf13/cobra"
"sneak.berlin/go/secret/internal/vault"
)
// getSecretNamesCompletionFunc returns a completion function that provides
// secret names
func getSecretNamesCompletionFunc(fs afero.Fs, stateDir string) func(
cmd *cobra.Command, args []string, toComplete string,
) ([]string, cobra.ShellCompDirective) {
return func(
_ *cobra.Command, _ []string, toComplete string,
) ([]string, cobra.ShellCompDirective) {
// Get current vault
vlt, err := vault.GetCurrentVault(fs, stateDir)
if err != nil {
return nil, cobra.ShellCompDirectiveNoFileComp
}
// Get list of secrets
secrets, err := vlt.ListSecrets()
if err != nil {
return nil, cobra.ShellCompDirectiveNoFileComp
}
// Filter secrets based on what user has typed
var completions []string
for _, secret := range secrets {
if strings.HasPrefix(secret, toComplete) {
completions = append(completions, secret)
}
}
return completions, cobra.ShellCompDirectiveNoFileComp
}
}
// getUnlockerIDsCompletionFunc returns a completion function that provides
// unlocker IDs, the names of the unlockers' directories in unlockers.d
func getUnlockerIDsCompletionFunc(fs afero.Fs, stateDir string) func(
cmd *cobra.Command, args []string, toComplete string,
) ([]string, cobra.ShellCompDirective) {
return func(
_ *cobra.Command, _ []string, toComplete string,
) ([]string, cobra.ShellCompDirective) {
// Get current vault
vlt, err := vault.GetCurrentVault(fs, stateDir)
if err != nil {
return nil, cobra.ShellCompDirectiveNoFileComp
}
unlockerMetadata, err := vlt.ListUnlockers()
if err != nil {
return nil, cobra.ShellCompDirectiveNoFileComp
}
var completions []string
for _, id := range slices.Sorted(maps.Keys(unlockerMetadata)) {
if strings.HasPrefix(id, toComplete) {
completions = append(completions, id)
}
}
return completions, cobra.ShellCompDirectiveNoFileComp
}
}
// getVaultNamesCompletionFunc returns a completion function that provides
// vault names
func getVaultNamesCompletionFunc(fs afero.Fs, stateDir string) func(
cmd *cobra.Command, args []string, toComplete string,
) ([]string, cobra.ShellCompDirective) {
return func(
_ *cobra.Command, _ []string, toComplete string,
) ([]string, cobra.ShellCompDirective) {
vaults, err := vault.ListVaults(fs, stateDir)
if err != nil {
return nil, cobra.ShellCompDirectiveNoFileComp
}
var completions []string
for _, v := range vaults {
if strings.HasPrefix(v, toComplete) {
completions = append(completions, v)
}
}
return completions, cobra.ShellCompDirectiveNoFileComp
}
}
// completeVaultQualifiedSecrets completes "vault:secret" references once a
// colon is present in the input. It completes nothing when the vault part
// is not a valid vault name, so that a name such as ".." cannot list a
// directory outside vaults.d.
func completeVaultQualifiedSecrets(
fs afero.Fs, stateDir, toComplete string,
) []string {
var completions []string
// Complete secret names for the specified vault
parts := strings.SplitN(toComplete, ":", vaultSecretParts)
vaultName := parts[0]
secretPrefix := parts[1]
if vault.ValidateVaultName(vaultName) != nil {
return nil
}
vlt := vault.NewVault(fs, stateDir, vaultName)
secrets, err := vlt.ListSecrets()
if err == nil {
for _, secretName := range secrets {
if strings.HasPrefix(secretName, secretPrefix) {
completions = append(completions, vaultName+":"+secretName)
}
}
}
return completions
}
// completeUnqualifiedVaultSecrets completes vault names (with a ":"
// suffix) and secrets from the current vault
func completeUnqualifiedVaultSecrets(
fs afero.Fs, stateDir, toComplete string,
) []string {
var completions []string
// Complete vault names with ":" suffix
vaults, err := vault.ListVaults(fs, stateDir)
if err == nil {
for _, v := range vaults {
if strings.HasPrefix(v, toComplete) {
completions = append(completions, v+":")
}
}
}
// Also complete secrets from current vault (for within-vault moves)
currentVlt, err := vault.GetCurrentVault(fs, stateDir)
if err == nil {
secrets, err := currentVlt.ListSecrets()
if err == nil {
for _, secretName := range secrets {
if strings.HasPrefix(secretName, toComplete) {
completions = append(completions, secretName)
}
}
}
}
return completions
}
// getVaultSecretCompletionFunc returns a completion function for the
// vault:secret format. It completes vault names with ":" suffix, and
// after ":" it completes secrets from that vault.
func getVaultSecretCompletionFunc(fs afero.Fs, stateDir string) func(
cmd *cobra.Command, args []string, toComplete string,
) ([]string, cobra.ShellCompDirective) {
return func(
_ *cobra.Command, _ []string, toComplete string,
) ([]string, cobra.ShellCompDirective) {
// Check if we're completing after a vault: prefix
if strings.Contains(toComplete, ":") {
return completeVaultQualifiedSecrets(fs, stateDir, toComplete),
cobra.ShellCompDirectiveNoFileComp
}
return completeUnqualifiedVaultSecrets(fs, stateDir, toComplete),
cobra.ShellCompDirectiveNoSpace
}
}