- Added internal/vault/client.go for creating a Vault client with configuration settings. - Introduced internal/vault/errors.go to classify Vault API errors for better UI handling. - Created internal/vault/kv.go to manage KV secrets, including listing, reading, writing, and deleting operations. - Implemented internal/vault/mounts.go to list and describe secret engine mounts. - Developed internal/vault/service.go to provide a unified entry point for Vault operations. - Added internal/vault/kv_test.go for comprehensive testing of KV operations. - Introduced internal/ui/toast.go for transient notifications in the UI. - Added renovate.json for dependency management and updates.
108 lines
3.0 KiB
Go
108 lines
3.0 KiB
Go
package cli
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
"git.morlana.online/f.weber/vault-tui/internal/auth"
|
|
"git.morlana.online/f.weber/vault-tui/internal/config"
|
|
)
|
|
|
|
// runWizard interactively builds one profile and writes it to path. It is
|
|
// deliberately generic — it prompts for everything (address, namespace,
|
|
// TLS, auth method and that method's fields) rather than assuming any
|
|
// particular Vault deployment, per this project's design goal of not
|
|
// hard-coding any environment's specifics into the tool itself.
|
|
func runWizard(path string) (*config.File, error) {
|
|
r := bufio.NewReader(os.Stdin)
|
|
fmt.Fprintln(os.Stderr, "No config found — let's set up a Vault profile (you can edit it later at "+path+").")
|
|
|
|
profileName := ask(r, "Profile name", "default")
|
|
addrDefault := "https://127.0.0.1:8200"
|
|
if v, ok := config.OSEnviron(config.EnvAddress); ok && v != "" {
|
|
addrDefault = v
|
|
}
|
|
address := ask(r, "Vault address", addrDefault)
|
|
namespace := ask(r, "Namespace (blank for none)", "")
|
|
|
|
prof := &config.Profile{Address: address, Namespace: namespace}
|
|
|
|
if askYesNo(r, "Configure custom TLS (CA cert / client cert)?", false) {
|
|
prof.TLS.CACert = ask(r, "CA certificate path (blank to use system trust)", "")
|
|
prof.TLS.ClientCert = ask(r, "Client certificate path (blank for none)", "")
|
|
if prof.TLS.ClientCert != "" {
|
|
prof.TLS.ClientKey = ask(r, "Client key path", "")
|
|
}
|
|
if askYesNo(r, "Skip TLS verification (insecure, testing only)?", false) {
|
|
t := true
|
|
prof.TLS.SkipVerify = &t
|
|
}
|
|
}
|
|
|
|
names := auth.Default().Names()
|
|
fmt.Fprintln(os.Stderr, "Available auth methods: "+strings.Join(names, ", "))
|
|
method := ask(r, "Auth method", "oidc")
|
|
m, ok := auth.Default().Get(method)
|
|
if !ok {
|
|
return nil, fmt.Errorf("unknown auth method %q", method)
|
|
}
|
|
prof.Auth.Method = method
|
|
mount := ask(r, "Auth mount path", m.DefaultMount())
|
|
prof.Auth.Mount = mount
|
|
|
|
params := map[string]string{}
|
|
for _, f := range m.Fields() {
|
|
if f.Kind == auth.FieldSecret {
|
|
// Secrets are never written to the config file; the user supplies
|
|
// them at login time (env var, flag, or interactive prompt).
|
|
continue
|
|
}
|
|
v := ask(r, f.Label, f.Default)
|
|
if v != "" {
|
|
params[f.Name] = v
|
|
}
|
|
}
|
|
if len(params) > 0 {
|
|
prof.Auth.Params = params
|
|
}
|
|
|
|
file := &config.File{
|
|
Version: config.SchemaVersion,
|
|
CurrentProfile: profileName,
|
|
Profiles: map[string]*config.Profile{profileName: prof},
|
|
}
|
|
if err := config.Save(path, file); err != nil {
|
|
return nil, err
|
|
}
|
|
fmt.Fprintln(os.Stderr, "Saved "+path)
|
|
return file, nil
|
|
}
|
|
|
|
func ask(r *bufio.Reader, label, def string) string {
|
|
if def != "" {
|
|
fmt.Fprintf(os.Stderr, "%s [%s]: ", label, def)
|
|
} else {
|
|
fmt.Fprintf(os.Stderr, "%s: ", label)
|
|
}
|
|
line, _ := r.ReadString('\n')
|
|
line = strings.TrimSpace(line)
|
|
if line == "" {
|
|
return def
|
|
}
|
|
return line
|
|
}
|
|
|
|
func askYesNo(r *bufio.Reader, label string, def bool) bool {
|
|
d := "y/N"
|
|
if def {
|
|
d = "Y/n"
|
|
}
|
|
line := strings.ToLower(ask(r, label+" ("+d+")", ""))
|
|
if line == "" {
|
|
return def
|
|
}
|
|
return line == "y" || line == "yes"
|
|
}
|