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.
This commit is contained in:
2026-09-16 11:49:40 +02:00
parent 720064454b
commit 209e93646c
2 changed files with 58 additions and 3 deletions
+10 -1
View File
@@ -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)
```
+48 -2
View File
@@ -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)