Skip to content

Instantly share code, notes, and snippets.

@sarvsav
Created August 31, 2025 18:40
Show Gist options
  • Select an option

  • Save sarvsav/ce7bd63d2beb2a71e32f4c480b917767 to your computer and use it in GitHub Desktop.

Select an option

Save sarvsav/ce7bd63d2beb2a71e32f4c480b917767 to your computer and use it in GitHub Desktop.
Script to generate the vault secret store path
// For generating vault secret store path
// It is a part of medium blog
package main
import (
"fmt"
"os"
"regexp"
"strings"
"github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
)
type field struct {
Key string
Prompt string
Help string
Placeholder string
Required bool
Value string
Validate func(string) (string, bool)
}
var (
titleStyle = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("213"))
labelStyle = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("81"))
errStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("1"))
helpStyle = lipgloss.NewStyle().Faint(true)
confirmStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("10")).Bold(true)
pathStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("13")).Bold(true)
dimStyle = lipgloss.NewStyle().Faint(true)
focusStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("229")).Background(lipgloss.Color("57")).Bold(true).Padding(0, 1)
)
func nonEmpty(v string) (string, bool) {
v = strings.TrimSpace(v)
return v, v != ""
}
var allowedRe = regexp.MustCompile(`^[a-z0-9._:-]+$`)
func axisValidate(allowEmpty bool) func(string) (string, bool) {
return func(s string) (string, bool) {
s = strings.TrimSpace(strings.ToLower(s))
if s == "" {
return s, !(!allowEmpty) // true if allowEmpty, false otherwise
}
return s, allowedRe.MatchString(s)
}
}
type mode int
const (
modeWizard mode = iota
modeReview
modeDone
)
type model struct {
fields []field
index int
ti textinput.Model
errMsg string
m mode
lastPath string
editIndex int // which field to edit in review (if any)
}
func newModel() model {
// Define the decision tree axes (the wizard steps)
fields := []field{
{
Key: "provider",
Prompt: "1) Provider – What system does this unlock?",
Help: "Examples: aws, azure, gcp, bitbucket, onprem",
Placeholder: "aws",
Required: true,
Validate: axisValidate(false),
},
{
Key: "account",
Prompt: "2) Account / Subscription / Project / Org?",
Help: "Examples: 111111111111, sub123, org1",
Placeholder: "111111111111",
Required: true,
Validate: axisValidate(false),
},
{
Key: "region",
Prompt: "3) Region (or 'global' if not region-specific)?",
Help: "Examples: us-east-1, westus, eu-central-1, global",
Placeholder: "us-east-1",
Required: true,
Validate: axisValidate(false),
},
{
Key: "environment",
Prompt: "4) Environment?",
Help: "Examples: prod, staging, dev, test, sandbox",
Placeholder: "prod",
Required: true,
Validate: axisValidate(false),
},
{
Key: "service",
Prompt: "5) Service / Resource Type?",
Help: "Examples: rds, vm, storage-account, workspace",
Placeholder: "rds",
Required: true,
Validate: axisValidate(false),
},
{
Key: "resource",
Prompt: "6) Resource instance name?",
Help: "Examples: orders-db, rg1-myapp, workspace1",
Placeholder: "orders-db",
Required: true,
Validate: axisValidate(false),
},
{
Key: "secret-type",
Prompt: "7) Secret type?",
Help: "Examples: user, ssh, password, api-key, key, cert, connection-string",
Placeholder: "user",
Required: true,
Validate: axisValidate(false),
},
{
Key: "variant",
Prompt: "8) Variant (if multiple of same type)?",
Help: "Examples: admin, readonly, key1, key2, deploy",
Placeholder: "admin",
Required: true,
Validate: axisValidate(false),
},
}
ti := textinput.New()
ti.Prompt = "> "
ti.Placeholder = fields[0].Placeholder
ti.Focus()
ti.CharLimit = 128
return model{
fields: fields,
index: 0,
ti: ti,
m: modeWizard,
}
}
func (m model) Init() tea.Cmd { return textinput.Blink }
// Build the final path
func (m *model) buildPath() string {
parts := make([]string, 0, len(m.fields))
for _, f := range m.fields {
v := strings.TrimSpace(strings.ToLower(f.Value))
parts = append(parts, v)
}
return "/" + strings.Join(parts, "/")
}
func (m model) View() string {
switch m.m {
case modeWizard:
return m.viewWizard()
case modeReview:
return m.viewReview()
case modeDone:
return m.viewDone()
default:
return ""
}
}
func (m model) viewWizard() string {
f := m.fields[m.index]
header := titleStyle.Render("Vault Secret Path Wizard")
step := fmt.Sprintf("Step %d of %d", m.index+1, len(m.fields))
body := strings.Builder{}
body.WriteString(header + "\n")
body.WriteString(dimStyle.Render(step) + "\n\n")
body.WriteString(labelStyle.Render(f.Prompt) + "\n")
body.WriteString(helpStyle.Render(f.Help) + "\n\n")
body.WriteString(m.ti.View() + "\n")
if m.errMsg != "" {
body.WriteString(errStyle.Render("Error: " + m.errMsg))
body.WriteString("\n")
}
body.WriteString("\n")
body.WriteString(dimStyle.Render("Enter to continue • Ctrl+C to quit • Tab to accept placeholder • Ctrl+H to go back"))
return body.String()
}
func (m model) viewReview() string {
header := titleStyle.Render("Review & Confirm")
body := strings.Builder{}
body.WriteString(header + "\n\n")
for i, f := range m.fields {
line := fmt.Sprintf("%s: %s", f.Key, labelStyle.Render(f.Value))
if i == m.editIndex {
line = focusStyle.Render(line + " (editing)")
}
body.WriteString(line + "\n")
}
body.WriteString("\n")
path := m.buildPath()
body.WriteString("Final path:\n")
body.WriteString(pathStyle.Render(path) + "\n\n")
body.WriteString(dimStyle.Render("Press Enter to confirm and print the path.\n"))
body.WriteString(dimStyle.Render("Or type a field name to edit (e.g., 'region') and press Enter.\n"))
body.WriteString(dimStyle.Render("Ctrl+H to go back to the last step • Ctrl+C to quit"))
return body.String()
}
func (m model) viewDone() string {
header := confirmStyle.Render("✓ Secret path generated")
body := strings.Builder{}
body.WriteString(header + "\n\n")
body.WriteString(pathStyle.Render(m.lastPath) + "\n\n")
body.WriteString(dimStyle.Render("Copy the path above.\n"))
return body.String()
}
func (m *model) gotoFieldByKey(key string) bool {
key = strings.TrimSpace(strings.ToLower(key))
for i, f := range m.fields {
if f.Key == key {
m.index = i
m.ti.SetValue(f.Value)
m.ti.Placeholder = f.Placeholder
m.m = modeWizard
m.errMsg = ""
return true
}
}
return false
}
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch m.m {
case modeWizard:
switch msg.Type {
case tea.KeyCtrlC, tea.KeyEsc:
return m, tea.Quit
case tea.KeyCtrlH: // back
if m.index > 0 {
m.index--
prev := m.fields[m.index]
m.ti.SetValue(prev.Value)
m.ti.Placeholder = prev.Placeholder
m.errMsg = ""
return m, nil
}
return m, nil
case tea.KeyTab: // accept placeholder quickly
m.ti.SetValue(m.fields[m.index].Placeholder)
return m, nil
case tea.KeyEnter:
val := m.ti.Value()
normalized, ok := m.fields[m.index].Validate(val)
if !ok {
m.errMsg = "Invalid value. Use lowercase a-z, 0-9, dot, underscore, hyphen, or colon."
return m, nil
}
if m.fields[m.index].Required && normalized == "" {
m.errMsg = "This field is required."
return m, nil
}
// Save and advance
m.fields[m.index].Value = normalized
m.errMsg = ""
if m.index < len(m.fields)-1 {
m.index++
next := m.fields[m.index]
m.ti.SetValue(next.Value)
m.ti.Placeholder = next.Placeholder
return m, nil
}
// Go to review
m.m = modeReview
m.ti.Blur()
return m, nil
default:
// pass to textinput
}
var cmd tea.Cmd
m.ti, cmd = m.ti.Update(msg)
return m, cmd
case modeReview:
switch msg.Type {
case tea.KeyCtrlC, tea.KeyEsc:
return m, tea.Quit
case tea.KeyCtrlH:
// Jump back to last field for quick tweak
m.m = modeWizard
m.index = len(m.fields) - 1
m.ti.SetValue(m.fields[m.index].Value)
m.ti.Placeholder = m.fields[m.index].Placeholder
m.ti.Focus()
return m, nil
case tea.KeyEnter:
// If the user typed something into the (invisible) textinput, treat it as a field key to edit
input := strings.TrimSpace(strings.ToLower(m.ti.Value()))
if input == "" {
// Confirm
m.lastPath = m.buildPath()
m.m = modeDone
return m, nil
}
if m.gotoFieldByKey(input) {
m.ti.Focus()
return m, nil
}
// Not a valid key; ignore and clear
m.ti.SetValue("")
return m, nil
default:
// We keep a hidden textinput for users to type a field to edit
var cmd tea.Cmd
m.ti, cmd = m.ti.Update(msg)
return m, cmd
}
case modeDone:
switch msg.Type {
case tea.KeyCtrlC, tea.KeyEsc, tea.KeyEnter:
// Print the final path to stdout on exit for easy scripting
fmt.Println(m.lastPath)
return m, tea.Quit
default:
return m, nil
}
}
case tea.WindowSizeMsg:
// No special layout logic needed here
return m, nil
}
// Default: forward to textinput if in wizard/review
var cmd tea.Cmd
if m.m == modeWizard || m.m == modeReview {
m.ti, cmd = m.ti.Update(msg)
}
return m, cmd
}
func main() {
p := tea.NewProgram(newModel())
if _, err := p.Run(); err != nil {
fmt.Println("error:", err)
os.Exit(1)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment