From 209e93646c436f9050bbb497c645b1faf50285c8 Mon Sep 17 00:00:00 2001 From: Stefan Koelle Date: Wed, 16 Sep 2026 11:49:40 +0200 Subject: [PATCH] feat: add config edit command to open config in editor New subcommand 'kctl-tui config edit' opens ~/.kctl-tui/config.yaml in the user's editor. Respects VISUAL/EDITOR env vars, falls back to notepad on Windows and vim on Linux/macOS. Creates the config directory and a starter file if they don't exist yet. --- README.md | 11 +++++++++- cmd/kctl-tui/main.go | 50 ++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 58 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a36cda4..dfbf0e4 100644 --- a/README.md +++ b/README.md @@ -119,7 +119,8 @@ matching asset from the [Releases page](https://github.com/skoelle/kctl-tui/rele ## ⚙️ Configuration Copy [config.example.yaml](config.example.yaml) to `~/.kctl-tui/config.yaml` -and adjust it to your own setup: +and adjust it to your own setup — or use `kctl-tui config edit` to open the +file directly in your editor (creates the file and directory if needed): ```yaml contexts: @@ -136,6 +137,7 @@ aws_account_id: "123456789012" secret_name_template: "tf-{namespace}-{env}-secrets" k8s_secret_name_template: "{namespace}-common-secrets" +external_secret_name_template: "{namespace}" context_template: "arn:aws:eks:{region}:{account_id}:cluster/tf-{env}-{context}-1" team_label_key: "example.org/team" @@ -159,6 +161,12 @@ aws_sso_login_command: "aws sso login" chosen namespace. Kept separate from `secret_name_template` because the two sides commonly follow different naming conventions. Placeholders: `{namespace}`. +- 🎯 `external_secret_name_template`: builds the ExternalSecret CRD object + name to annotate when a force-sync is requested. This is often different + from the Kubernetes secret name because the ExternalSecret CRD and the + resulting Secret are separate objects (e.g. ExternalSecret `"job-apply"` + produces Secret `"job-apply-common-secrets"`). Falls back to + `k8s_secret_name_template` if omitted. Placeholders: `{namespace}`. - 🔗 `context_template`: builds the actual kubectl context name/ARN from region, account ID, environment, and context. Placeholders: `{region}`, `{account_id}`, `{env}`, `{context}`. Adjust the literal parts (`tf-`, @@ -205,6 +213,7 @@ kctl-tui --version # print version kctl-tui --verbose # enable debug logging to stderr kctl-tui update # update to the latest release kctl-tui doctor # check if all tools, config and connections are OK +kctl-tui config edit # open ~/.kctl-tui/config.yaml in your editor kctl-tui config check # validate ~/.kctl-tui/config.yaml kctl-tui panel --context=... --ns=... --team=... # internal (called by tmux) ``` diff --git a/cmd/kctl-tui/main.go b/cmd/kctl-tui/main.go index e7745b4..e863e4e 100644 --- a/cmd/kctl-tui/main.go +++ b/cmd/kctl-tui/main.go @@ -6,6 +6,9 @@ package main import ( "fmt" "os" + "os/exec" + "path/filepath" + "runtime" tea "github.com/charmbracelet/bubbletea" @@ -113,6 +116,7 @@ Usage: kctl-tui [flags] start the TUI (full navigation mode) kctl-tui doctor check tools, config and connections kctl-tui update update to the latest release + kctl-tui config edit open ~/.kctl-tui/config.yaml in editor kctl-tui config check validate ~/.kctl-tui/config.yaml kctl-tui panel [options] control pane (called internally by tmux) @@ -126,14 +130,56 @@ Examples: kctl-tui doctor # verify everything is installed kctl-tui update # update to the latest version kctl-tui --verbose 2>debug.log # log commands to a file + kctl-tui config edit # open config in editor kctl-tui config check # validate config `) } func runConfig(args []string) error { - if len(args) == 0 || args[0] != "check" { - return fmt.Errorf("usage: kctl-tui config check") + if len(args) > 0 && args[0] == "check" { + return runConfigCheck() } + if len(args) == 0 || args[0] == "edit" { + return runConfigEdit() + } + return fmt.Errorf("usage: kctl-tui config [check|edit]") +} + +func runConfigEdit() error { + cfgPath, err := config.DefaultPath() + if err != nil { + return fmt.Errorf("cannot determine config path: %w", err) + } + dir := filepath.Dir(cfgPath) + if err := os.MkdirAll(dir, 0o700); err != nil { + return fmt.Errorf("cannot create config directory %s: %w", dir, err) + } + if _, err := os.Stat(cfgPath); os.IsNotExist(err) { + if err := os.WriteFile(cfgPath, []byte("# kctl-tui configuration\n# See https://github.com/skoelle/kctl-tui for examples.\n"), 0o600); err != nil { + return fmt.Errorf("cannot create config file %s: %w", cfgPath, err) + } + fmt.Printf("Created new config file: %s\n", cfgPath) + } + editor := os.Getenv("VISUAL") + if editor == "" { + editor = os.Getenv("EDITOR") + } + if editor == "" { + switch runtime.GOOS { + case "windows": + editor = "notepad" + default: + editor = "vim" + } + } + cmd := exec.Command(editor, cfgPath) + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + return cmd.Run() +} + +func runConfigCheck() error { cfgPath, err := config.DefaultPath() if err != nil { return fmt.Errorf("cannot determine config path: %w", err)