more knecht

This commit is contained in:
2026-04-04 15:05:08 +02:00
parent 960e12f967
commit b4fddbb5b6
9 changed files with 463 additions and 84 deletions

View File

@@ -13,10 +13,14 @@ var listCmd = &cobra.Command{
Use: "list",
Short: "List all stacks with status and drift",
RunE: func(cmd *cobra.Command, args []string) error {
_, client, svcPath, err := setup()
cfg, client, svcPath, err := setup()
if err != nil {
return err
}
ignoreSet := make(map[string]bool, len(cfg.Ignore))
for _, name := range cfg.Ignore {
ignoreSet[name] = true
}
stacks, err := client.ListStacks()
if err != nil {
@@ -35,7 +39,12 @@ var listCmd = &cobra.Command{
fmt.Printf("%-20s %-10s %s\n", "STACK", "STATUS", "DRIFT")
fmt.Println(repeat("-", 60))
remoteNames := make(map[string]bool, len(stacks))
for _, s := range stacks {
if ignoreSet[s.Name] {
continue
}
remoteNames[s.Name] = true
status := statusLabel(s.Status)
driftSummary := "no local compose"
@@ -49,6 +58,14 @@ var listCmd = &cobra.Command{
fmt.Printf("%-20s %-10s %s\n", s.Name, status, driftSummary)
}
// Local-only stacks
for _, l := range locals {
if ignoreSet[l.Name] || remoteNames[l.Name] {
continue
}
fmt.Printf("%-20s %-10s %s\n", l.Name, "not deployed", "-")
}
return nil
},
}

View File

@@ -19,7 +19,7 @@ var logsCmd = &cobra.Command{
return err
}
logsBase := deriveLogsURL(cfg.Portainer.URL)
logsBase := deriveLogsURL(cfg.URL)
if len(args) == 0 {
fmt.Printf("Opening %s\n", logsBase)

View File

@@ -44,7 +44,7 @@ func setup() (*config.Config, *portainer.Client, string, error) {
return nil, nil, "", err
}
client, err := portainer.New(cfg.Portainer.URL, cfg.Portainer.Token, cfg.Portainer.Endpoint)
client, err := portainer.New(cfg.URL, cfg.Token, cfg.Endpoint)
if err != nil {
return nil, nil, "", fmt.Errorf("connecting to Portainer: %w", err)
}

View File

@@ -5,6 +5,7 @@ import (
"github.com/jensbecker/homelab/knecht/portainer"
"github.com/jensbecker/homelab/knecht/stack"
"github.com/jensbecker/homelab/knecht/tui"
"github.com/spf13/cobra"
)
@@ -37,15 +38,19 @@ var updateCmd = &cobra.Command{
return err
}
// Check for missing env keys and prompt for values
exampleKeys, err := local.EnvExampleKeys()
if err != nil {
return err
}
env, err := mergeEnvVars(remote.Env, exampleKeys)
env, err := mergeEnvVars(name, remote.Env, exampleKeys)
if err != nil {
return err
}
if env == nil {
fmt.Println("Cancelled.")
return nil
}
s, err := client.UpdateStack(remote.ID, compose, env)
if err != nil {
@@ -56,20 +61,32 @@ var updateCmd = &cobra.Command{
},
}
// mergeEnvVars takes existing Portainer env vars and prompts for any keys
// present in .env.example but missing from Portainer.
func mergeEnvVars(existing []portainer.EnvVar, exampleKeys []string) ([]portainer.EnvVar, error) {
// mergeEnvVars preserves existing Portainer env vars and prompts via TUI for
// any keys present in .env.example but missing from Portainer.
// Returns nil if the user cancelled the form.
func mergeEnvVars(stackName string, existing []portainer.EnvVar, exampleKeys []string) ([]portainer.EnvVar, error) {
envMap := make(map[string]string, len(existing))
for _, e := range existing {
envMap[e.Name] = e.Value
}
var missing []string
for _, key := range exampleKeys {
if _, ok := envMap[key]; !ok {
fmt.Printf("Missing env var %q — enter value (leave empty to skip): ", key)
var val string
fmt.Scanln(&val)
envMap[key] = val
missing = append(missing, key)
}
}
if len(missing) > 0 {
values, err := tui.PromptMissingEnv(stackName, missing)
if err != nil {
return nil, err
}
if values == nil {
return nil, nil // cancelled
}
for k, v := range values {
envMap[k] = v
}
}