mirror of
https://github.com/skoelle/kctl-tui.git
synced 2026-09-17 20:10:24 +00:00
Add internal/kubeexec wrapper and cmd/kctl-tui entrypoint (main, full mode)
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
package main
|
||||
|
||||
import "github.com/skoelle/kctl-tui/internal/kctl"
|
||||
|
||||
func distinctLabelValues(namespaces map[string]map[string]string, labelKey string) []string {
|
||||
return kctl.DistinctLabelValues(namespaces, labelKey)
|
||||
}
|
||||
|
||||
func namespacesForLabelValue(namespaces map[string]map[string]string, labelKey, value string) []string {
|
||||
return kctl.NamespacesForLabelValue(namespaces, labelKey, value)
|
||||
}
|
||||
|
||||
func findNextContext(current string, pairs []kctl.ContextPair) (string, bool) {
|
||||
return kctl.FindNextContext(current, pairs)
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package main
|
||||
|
||||
// simpleItem is a minimal implementation of list.Item used for all
|
||||
// selection screens (contexts, teams, namespaces, menu actions).
|
||||
type simpleItem struct {
|
||||
label string // what is shown to the user
|
||||
value string // the underlying value (context name, team value, ...)
|
||||
}
|
||||
|
||||
func (i simpleItem) Title() string { return i.label }
|
||||
func (i simpleItem) Description() string { return "" }
|
||||
func (i simpleItem) FilterValue() string { return i.label }
|
||||
@@ -0,0 +1,25 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if len(os.Args) > 1 && os.Args[1] == "panel" {
|
||||
if err := runPanel(os.Args[2:]); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "kctl-tui panel error:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
m := newFullModel()
|
||||
p := tea.NewProgram(m, tea.WithAltScreen())
|
||||
if _, err := p.Run(); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "kctl-tui error:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
// Package kubeexec wraps kubectl/aws-cli invocations used by kctl-tui.
|
||||
// All functions here have side effects (they run external processes) and
|
||||
// are therefore not covered by unit tests; the pure logic they depend on
|
||||
// lives in the kctl package instead.
|
||||
package kubeexec
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func runOutput(name string, args ...string) (string, error) {
|
||||
cmd := exec.Command(name, args...)
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("%s %s failed: %w\n%s", name, strings.Join(args, " "), err, string(out))
|
||||
}
|
||||
return strings.TrimSpace(string(out)), nil
|
||||
}
|
||||
|
||||
// GetContexts returns all configured kubectl context names.
|
||||
func GetContexts() ([]string, error) {
|
||||
out, err := runOutput("kubectl", "config", "get-contexts", "-o", "name")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if out == "" {
|
||||
return []string{}, nil
|
||||
}
|
||||
return strings.Split(out, "\n"), nil
|
||||
}
|
||||
|
||||
// GetCurrentContext returns the currently active kubectl context, or an
|
||||
// empty string if none is set.
|
||||
func GetCurrentContext() string {
|
||||
out, _ := runOutput("kubectl", "config", "current-context")
|
||||
return out
|
||||
}
|
||||
|
||||
// GetCurrentNamespace returns the namespace bound to the current context,
|
||||
// defaulting to "default" if unset.
|
||||
func GetCurrentNamespace() string {
|
||||
out, _ := runOutput("kubectl", "config", "view", "--minify", "-o", "jsonpath={..namespace}")
|
||||
if out == "" {
|
||||
return "default"
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// UseContext switches the active kubectl context.
|
||||
func UseContext(ctx string) error {
|
||||
_, err := runOutput("kubectl", "config", "use-context", ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
// SetNamespace binds a namespace to the current kubectl context.
|
||||
func SetNamespace(ns string) error {
|
||||
_, err := runOutput("kubectl", "config", "set-context", "--current", "--namespace="+ns)
|
||||
return err
|
||||
}
|
||||
|
||||
type nsItem struct {
|
||||
Metadata struct {
|
||||
Name string `json:"name"`
|
||||
Labels map[string]string `json:"labels"`
|
||||
} `json:"metadata"`
|
||||
}
|
||||
|
||||
type nsList struct {
|
||||
Items []nsItem `json:"items"`
|
||||
}
|
||||
|
||||
// GetNamespacesWithLabels returns a map of namespace name -> labels for all
|
||||
// namespaces visible in the current context.
|
||||
func GetNamespacesWithLabels() (map[string]map[string]string, error) {
|
||||
out, err := runOutput("kubectl", "get", "ns", "-o", "json")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var list nsList
|
||||
if err := json.Unmarshal([]byte(out), &list); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := make(map[string]map[string]string, len(list.Items))
|
||||
for _, item := range list.Items {
|
||||
result[item.Metadata.Name] = item.Metadata.Labels
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// GetDeployments lists deployment names in the given namespace.
|
||||
func GetDeployments(namespace string) ([]string, error) {
|
||||
out, err := runOutput("kubectl", "-n", namespace, "get", "deploy",
|
||||
"-o", `jsonpath={range .items[*]}{.metadata.name}{"\n"}{end}`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if out == "" {
|
||||
return []string{}, nil
|
||||
}
|
||||
return strings.Split(out, "\n"), nil
|
||||
}
|
||||
|
||||
// RolloutRestart triggers a rolling restart of a deployment.
|
||||
func RolloutRestart(namespace, deployment string) (string, error) {
|
||||
return runOutput("kubectl", "-n", namespace, "rollout", "restart", "deploy/"+deployment)
|
||||
}
|
||||
|
||||
// RolloutStatus waits for and returns the rollout status of a deployment.
|
||||
func RolloutStatus(namespace, deployment string) (string, error) {
|
||||
return runOutput("kubectl", "-n", namespace, "rollout", "status", "deploy/"+deployment)
|
||||
}
|
||||
|
||||
// GetSecretValueBase64 returns the raw (still base64-encoded) value of a
|
||||
// single field in a Kubernetes secret.
|
||||
func GetSecretValueBase64(namespace, secretName, field string) (string, error) {
|
||||
path := fmt.Sprintf("jsonpath={.data.%s}", field)
|
||||
return runOutput("kubectl", "-n", namespace, "get", "secret", secretName, "-o", path)
|
||||
}
|
||||
|
||||
// DecodeBase64 decodes a base64-encoded Kubernetes secret value.
|
||||
func DecodeBase64(value string) (string, error) {
|
||||
decoded, err := base64.StdEncoding.DecodeString(value)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(decoded), nil
|
||||
}
|
||||
|
||||
// AnnotateForceSync sets the force-sync annotation on an ExternalSecret
|
||||
// object to trigger an immediate re-sync from the upstream secret store.
|
||||
func AnnotateForceSync(namespace, externalSecretName string, unixTimestamp int64) (string, error) {
|
||||
annotation := fmt.Sprintf("force-sync=%d", unixTimestamp)
|
||||
return runOutput("kubectl", "-n", namespace, "annotate", "externalsecret",
|
||||
externalSecretName, annotation, "--overwrite")
|
||||
}
|
||||
|
||||
// GetAWSSecretString fetches the SecretString of an AWS Secrets Manager
|
||||
// secret via the aws-cli.
|
||||
func GetAWSSecretString(secretID, region string) (string, error) {
|
||||
return runOutput("aws", "secretsmanager", "get-secret-value",
|
||||
"--secret-id", secretID, "--region", region,
|
||||
"--query", "SecretString", "--output", "text")
|
||||
}
|
||||
Reference in New Issue
Block a user