diff --git a/cli/internal/kubehelpers/kubehelpers.go b/cli/internal/kubehelpers/kubehelpers.go new file mode 100644 index 0000000000..6d99473ca9 --- /dev/null +++ b/cli/internal/kubehelpers/kubehelpers.go @@ -0,0 +1,98 @@ +package kubehelpers + +import ( + "context" + "os" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" +) + +// Helper to get a Kubernetes client +func getKubeClient() (*kubernetes.Clientset, error) { + config, err := rest.InClusterConfig() + if err != nil { + kubeconfig := os.Getenv("KUBECONFIG") + if kubeconfig == "" { + kubeconfig = os.ExpandEnv("$HOME/.kube/config") + } + config, err = clientcmd.BuildConfigFromFlags("", kubeconfig) + if err != nil { + return nil, err + } + } + return kubernetes.NewForConfig(config) +} + +// Generic function to get labels for a resource type +func GetResourceLabels(resourceType, namespace string) (labels []string, labelToName map[string][]string, err error) { + clientset, err := getKubeClient() + if err != nil { + return nil, nil, err + } + var items []metav1.Object + switch resourceType { + case "pod": + pods, err := clientset.CoreV1().Pods(namespace).List(context.Background(), metav1.ListOptions{}) + if err != nil { + return nil, nil, err + } + for i := range pods.Items { + items = append(items, &pods.Items[i]) + } + case "deployment": + deploys, err := clientset.AppsV1().Deployments(namespace).List(context.Background(), metav1.ListOptions{}) + if err != nil { + return nil, nil, err + } + for i := range deploys.Items { + items = append(items, &deploys.Items[i]) + } + case "daemonset": + daemonsets, err := clientset.AppsV1().DaemonSets(namespace).List(context.Background(), metav1.ListOptions{}) + if err != nil { + return nil, nil, err + } + for i := range daemonsets.Items { + items = append(items, &daemonsets.Items[i]) + } + } + labelSet := make(map[string][]string) + for _, obj := range items { + for k, v := range obj.GetLabels() { + label := k + "=" + v + labelSet[label] = append(labelSet[label], obj.GetName()) + } + } + labels = make([]string, 0, len(labelSet)) + for label := range labelSet { + labels = append(labels, label) + } + return labels, labelSet, nil +} + +// Get namespace labels +func GetNamespaceLabels() (labels []string, labelToNS map[string][]string, err error) { + clientset, err := getKubeClient() + if err != nil { + return nil, nil, err + } + nsList, err := clientset.CoreV1().Namespaces().List(context.Background(), metav1.ListOptions{}) + if err != nil { + return nil, nil, err + } + labelSet := make(map[string][]string) + for _, ns := range nsList.Items { + for k, v := range ns.Labels { + label := k + "=" + v + labelSet[label] = append(labelSet[label], ns.Name) + } + } + labels = make([]string, 0, len(labelSet)) + for label := range labelSet { + labels = append(labels, label) + } + return labels, labelSet, nil +} diff --git a/cli/main.go b/cli/main.go index b6aa648ea0..f1f5574016 100644 --- a/cli/main.go +++ b/cli/main.go @@ -11,6 +11,35 @@ import ( ) func main() { + if len(os.Args) == 1 { + // No arguments: launch TUI + args, err := RunTUI() + if err != nil { + fmt.Println("TUI error:", err) + os.Exit(1) + } + if args != nil { + fmt.Printf("\nRetina CLI args: %s\n", args) + fmt.Print("\nWould you like to run this command? (y/n): ") + var confirm string + _, scanErr := fmt.Scanln(&confirm) + if scanErr != nil { + fmt.Println("Error reading input:", scanErr) + os.Exit(1) + } + if confirm == "y" || confirm == "Y" { + cmd.Retina.SetArgs(args) + if err := cmd.Retina.Execute(); err != nil { + fmt.Println(err) + os.Exit(1) + } + } else { + fmt.Println("Command not executed.") + } + } + // If args is nil, user cancelled or did not confirm; just exit + return + } if err := cmd.Retina.Execute(); err != nil { fmt.Println(err) os.Exit(1) diff --git a/cli/view.go b/cli/view.go new file mode 100644 index 0000000000..e9ec7c6336 --- /dev/null +++ b/cli/view.go @@ -0,0 +1,221 @@ +package main + +import ( + "fmt" + "sort" + + "github.com/charmbracelet/bubbles/table" + tea "github.com/charmbracelet/bubbletea" + captureviews "github.com/microsoft/retina/cli/views/capture" +) + +// Model step constants +const ( + stepType selectStep = iota + stepNamespace + stepLabel + stepDone +) + +type selectStep int + +type model struct { + table table.Model + prompt string + selectedType string + selectedNamespace string + selectedLabel string + selectedNamespaceLabel string + nsSelectorForView string + step selectStep + labelOptions []string + labelToNS map[string][]string + labelToName map[string][]string + done bool // track if final state is reached +} + +// Helper to create a table +func newTable(cols []table.Column, rows []table.Row, prompt string, height int) table.Model { + t := table.New(table.WithColumns(cols), table.WithRows(rows), table.WithFocused(true)) + t.SetHeight(height) + return t +} + +// Initial model +func initialModel() model { + // Get namespace label selectors for the initial prompt + labels, labelToNS, err := captureviews.GetNamespaceLabels() + if err != nil || len(labels) == 0 { + labels = []string{"none found"} + labelToNS = map[string][]string{"none found": {}} + } + rows := make([]table.Row, 0, len(labels)) + for _, label := range labels { + if nsList, ok := labelToNS[label]; ok && len(nsList) > 0 { + rows = append(rows, table.Row{captureviews.JoinOrNone(nsList), label}) + } + } + if len(rows) == 0 { + rows = append(rows, table.Row{"none found", "none found"}) + } + // Sort rows by the left column (Namespace(s)) + sort.Slice(rows, func(i, j int) bool { + return rows[i][0] < rows[j][0] + }) + cols := []table.Column{{Title: "Namespace(s)", Width: 80}, {Title: "Namespace Label Selector", Width: 40}} + t := newTable(cols, rows, "Select a namespace label selector:", 15) + return model{ + table: t, + prompt: "Select a namespace label selector:", + step: stepNamespace, + labelOptions: labels, + labelToNS: labelToNS, + } +} + +// Add back toMM and fromMM helpers for model <-> MainModel conversion +func (m *model) toMM() captureviews.MainModel { + return captureviews.MainModel{ + Table: m.table, + Prompt: m.prompt, + SelectedType: m.selectedType, + SelectedNamespace: m.selectedNamespace, + SelectedLabel: m.selectedLabel, + SelectedNamespaceLabel: m.selectedNamespaceLabel, + NsSelectorForView: m.nsSelectorForView, + Step: int(m.step), + LabelOptions: m.labelOptions, + LabelToNS: m.labelToNS, + LabelToName: m.labelToName, + } +} + +func (m *model) fromMM(mm captureviews.MainModel) { + m.table = mm.Table + m.prompt = mm.Prompt + m.selectedType = mm.SelectedType + m.selectedNamespace = mm.SelectedNamespace + m.selectedLabel = mm.SelectedLabel + m.selectedNamespaceLabel = mm.SelectedNamespaceLabel + m.nsSelectorForView = mm.NsSelectorForView + m.step = selectStep(mm.Step) + m.labelOptions = mm.LabelOptions + m.labelToNS = mm.LabelToNS + m.labelToName = mm.LabelToName +} + +// Main update logic, simplified +func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.KeyMsg: + switch msg.String() { + case "enter": + // Push current state to the stack before progressing + modelStack = append(modelStack, m) + mm := m.toMM() + switch m.step { + case stepType: + captureviews.HandleStepKubernetesResourceType(&mm) + case stepNamespace: + captureviews.HandleStepNamespace(&mm) + case stepLabel: + captureviews.HandleStepLabel(&mm) + } + m.fromMM(mm) + if m.step == stepDone { + m.done = true + return m, tea.Quit + } + return m, nil + case "q", "ctrl+c": + return m, tea.Quit + case "esc": + return goBackStack(), nil + } + } + m.table, _ = m.table.Update(msg) + return m, nil +} + +// Add a global stack to persist model states for back navigation +var modelStack []model + +// Go back one step in the TUI flow using the global stack +func goBackStack() model { + if len(modelStack) > 1 { + // Pop the current state + modelStack = modelStack[:len(modelStack)-1] + // Return the previous state + return modelStack[len(modelStack)-1] + } else if len(modelStack) == 1 { + // Only the initial state remains + return modelStack[0] + } + // If stack is empty, return a fresh initial model + return initialModel() +} + +// Helper to build the CLI args for retina cobra +func buildArgs(ns, podSelector, nsSelector string) []string { + if ns == "" { + ns = "default" + } + return []string{ + "capture", "create", + "--namespace", ns, + fmt.Sprintf("--pod-selectors=%s", podSelector), + fmt.Sprintf("--namespace-selectors=%s", nsSelector), + } +} + +// RunTUI launches the TUI and returns the CLI args if the user confirms, or nil if cancelled. +func RunTUI() ([]string, error) { + m := initialModel() + modelStack = []model{m} + p := tea.NewProgram(m) + finalModel, err := p.Run() + if err != nil { + return nil, err + } + mFinal, ok := finalModel.(model) + if !ok { + return nil, fmt.Errorf("unexpected model type") + } + if mFinal.step == stepDone { + args := buildArgs(mFinal.selectedNamespace, mFinal.selectedLabel, mFinal.selectedNamespaceLabel) + return args, nil + } + return nil, nil // user cancelled or did not confirm +} + +// Step handler for stepType +// moved to handler_step_type.go + +// Step handler for stepNamespace +// moved to handler_step_namespace.go + +// Step handler for stepLabel +// moved to handler_step_label.go + +// Ensure model implements tea.Model +func (m model) Init() tea.Cmd { return nil } + +// View logic remains unchanged +func (m model) View() string { + if m.done { + return "" + } + return fmt.Sprintf("%s\n\n%s\n\n(Use ↑/↓ to move, enter to select, q to quit)", + m.prompt, + m.table.View(), + ) +} + +func needsQuoting(s string) bool { + for _, c := range s { + if c == ' ' || c == '\t' || c == '"' { + return true + } + } + return false +} diff --git a/cli/views/capture/handler_step_label.go b/cli/views/capture/handler_step_label.go new file mode 100644 index 0000000000..fa6fbcf083 --- /dev/null +++ b/cli/views/capture/handler_step_label.go @@ -0,0 +1,13 @@ +package captureviews + +// Exported handler for label step +func HandleStepLabel(m *MainModel) { + selected := m.Table.SelectedRow() + row := ToLabelRow(selected) + m.Prompt = "Confirm your selection:" + if row.LabelSelector != "" { + m.SelectedLabel = row.LabelSelector + m.Confirmed = true + m.Step = StepDone + } +} diff --git a/cli/views/capture/handler_step_namespace.go b/cli/views/capture/handler_step_namespace.go new file mode 100644 index 0000000000..20a1d80367 --- /dev/null +++ b/cli/views/capture/handler_step_namespace.go @@ -0,0 +1,66 @@ +package captureviews + +import ( + "sort" + + "github.com/charmbracelet/bubbles/table" +) + +// Exported handler for namespace step +func HandleStepNamespace(m *MainModel) { + selected := m.Table.SelectedRow() + row := ToResourceRow(selected) + m.Prompt = "Select a pod label selector:" + m.SelectedNamespaceLabel = row.NamespaceLabel + m.SelectedLabel = "" + m.NsSelectorForView = row.NamespaceLabel + + // Gather all namespaces matching the selected label + nsList := m.LabelToNS[row.NamespaceLabel] + if len(nsList) == 0 { + nsList = []string{} + } + + // Collect all pod label selectors for pods in the selected namespaces + labelSet := make(map[string]struct{}) + labelToName := make(map[string][]string) + for _, ns := range nsList { + labels, l2n, err := GetResourceLabels("pod", ns) + if err != nil { + continue + } + for _, label := range labels { + labelSet[label] = struct{}{} + labelToName[label] = append(labelToName[label], l2n[label]...) + } + } + labels := make([]string, 0, len(labelSet)) + for label := range labelSet { + labels = append(labels, label) + } + if len(labels) == 0 { + labels = []string{"none found"} + labelToName = map[string][]string{"none found": {}} + } + rows := make([]table.Row, 0, len(labels)) + for _, label := range labels { + rows = append(rows, table.Row{JoinOrNone(labelToName[label]), label}) + } + if len(rows) == 0 { + rows = append(rows, table.Row{"none found", "none found"}) + } + // Sort rows by the left column (Pod Name(s)) + sort.Slice(rows, func(i, j int) bool { + return rows[i][0] < rows[j][0] + }) + t := NewTable( + []table.Column{{Title: "Pod Name(s)", Width: 80}, {Title: "Pod Label Selector", Width: 40}}, + rows, + m.Prompt, + 15, + ) + m.Table = t + m.LabelOptions = labels + m.LabelToName = labelToName + m.Step = StepLabel +} diff --git a/cli/views/capture/handler_step_type.go b/cli/views/capture/handler_step_type.go new file mode 100644 index 0000000000..1b192c3d54 --- /dev/null +++ b/cli/views/capture/handler_step_type.go @@ -0,0 +1,37 @@ +package captureviews + +import ( + "sort" + + "github.com/charmbracelet/bubbles/table" +) + +// Exported handler for resource type step +func HandleStepKubernetesResourceType(m *MainModel) { + selected := m.Table.SelectedRow() + row := ToResourceRow(selected) + if row.NamespaceNames != "" { + m.SelectedType = row.NamespaceNames // Save resource type (Pod, Deployment, etc.) + m.Prompt = "Select a namespace label selector:" + labels, labelToNS, err := GetNamespaceLabels() + if err != nil || len(labels) == 0 { + labels = []string{"none found"} + } + rows := make([]table.Row, 0, len(labels)) + for _, label := range labels { + if nsList, ok := labelToNS[label]; ok && len(nsList) > 0 { + rows = append(rows, table.Row{JoinOrNone(nsList), label}) + } + } + sort.Slice(rows, func(i, j int) bool { return rows[i][0] < rows[j][0] }) + if len(rows) == 0 { + rows = append(rows, table.Row{"none found", "none found"}) + } + cols := []table.Column{{Title: "Namespace(s)", Width: 80}, {Title: "Namespace Label Selector", Width: 40}} + t := NewTable(cols, rows, m.Prompt, 15) + m.Table = t + m.LabelOptions = labels + m.LabelToNS = labelToNS + m.Step = StepNamespace + } +} diff --git a/cli/views/capture/shared.go b/cli/views/capture/shared.go new file mode 100644 index 0000000000..1dc878ba21 --- /dev/null +++ b/cli/views/capture/shared.go @@ -0,0 +1,82 @@ +package captureviews + +import ( + "strings" + + "github.com/charmbracelet/bubbles/table" + "github.com/microsoft/retina/cli/internal/kubehelpers" +) + +type MainModel struct { + Table table.Model + Prompt string + Confirmed bool + SelectedType string + SelectedNamespace string + SelectedLabel string + SelectedNamespaceLabel string + NsSelectorForView string + Step int + LabelOptions []string + LabelToNS map[string][]string + LabelToName map[string][]string +} + +type ResourceRow struct { + NamespaceNames string + NamespaceLabel string +} + +type LabelRow struct { + ResourceNames string + LabelSelector string +} + +func ToResourceRow(row table.Row) ResourceRow { + return ResourceRow{ + NamespaceNames: getStringAt(row, 0), + NamespaceLabel: getStringAt(row, 1), + } +} + +func ToLabelRow(row table.Row) LabelRow { + return LabelRow{ + ResourceNames: getStringAt(row, 0), + LabelSelector: getStringAt(row, 1), + } +} + +func getStringAt(row table.Row, idx int) string { + if idx < 0 || idx >= len(row) { + return "" + } + return row[idx] +} + +func NewTable(cols []table.Column, rows []table.Row, prompt string, height int) table.Model { + t := table.New(table.WithColumns(cols), table.WithRows(rows), table.WithFocused(true)) + t.SetHeight(height) + return t +} + +func JoinOrNone(items []string) string { + if len(items) == 0 { + return "none" + } + return strings.Join(items, ", ") +} + +// Real implementations for external dependencies +func GetNamespaceLabels() ([]string, map[string][]string, error) { + return kubehelpers.GetNamespaceLabels() +} +func GetResourceLabels(resourceType, ns string) ([]string, map[string][]string, error) { + return kubehelpers.GetResourceLabels(resourceType, ns) +} + +const ( + StepType = iota + StepNamespace + StepLabel + StepDone +) diff --git a/go.mod b/go.mod index 7c0f7a389e..89d76daaad 100644 --- a/go.mod +++ b/go.mod @@ -414,9 +414,14 @@ require ( github.com/ccojocar/zxcvbn-go v1.0.2 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/charithe/durationcheck v0.0.10 // indirect - github.com/charmbracelet/bubbletea v0.22.1 // indirect - github.com/charmbracelet/lipgloss v0.10.0 // indirect + github.com/charmbracelet/bubbles v0.21.0 // indirect + github.com/charmbracelet/bubbletea v1.3.4 // indirect + github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect + github.com/charmbracelet/lipgloss v1.1.0 // indirect + github.com/charmbracelet/x/ansi v0.8.0 // indirect + github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect github.com/charmbracelet/x/exp/ordered v0.0.0-20231010190216-1cb11efc897d // indirect + github.com/charmbracelet/x/term v0.2.1 // indirect github.com/chavacava/garif v0.1.0 // indirect github.com/chrismellard/docker-credential-acr-env v0.0.0-20230304212654-82a0ddb27589 // indirect github.com/ckaznocha/intrange v0.3.0 // indirect @@ -436,6 +441,7 @@ require ( github.com/elliotchance/orderedmap/v2 v2.2.0 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect + github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/ettle/strcase v0.2.0 // indirect github.com/fatih/structtag v1.2.0 // indirect github.com/firefart/nonamedreturns v1.0.5 // indirect @@ -542,14 +548,14 @@ require ( github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/moricho/tparallel v0.3.2 // indirect github.com/mr-tron/base58 v1.2.0 // indirect - github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70 // indirect + github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/mango v0.1.0 // indirect github.com/muesli/mango-cobra v1.2.0 // indirect github.com/muesli/mango-pflag v0.1.0 // indirect github.com/muesli/reflow v0.3.0 // indirect github.com/muesli/roff v0.1.0 // indirect - github.com/muesli/termenv v0.15.2 // indirect + github.com/muesli/termenv v0.16.0 // indirect github.com/multiformats/go-base32 v0.1.0 // indirect github.com/multiformats/go-base36 v0.2.0 // indirect github.com/multiformats/go-multibase v0.2.0 // indirect @@ -621,6 +627,7 @@ require ( github.com/xanzy/go-gitlab v0.105.0 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xen0n/gosmopolitan v1.2.2 // indirect + github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/yagipy/maintidx v1.0.0 // indirect github.com/yeya24/promlinter v0.3.0 // indirect github.com/ykadowak/zerologlint v0.1.5 // indirect diff --git a/go.sum b/go.sum index d169779ec3..0c47ad8b85 100644 --- a/go.sum +++ b/go.sum @@ -361,14 +361,28 @@ github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNS github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= github.com/charithe/durationcheck v0.0.10 h1:wgw73BiocdBDQPik+zcEoBG/ob8uyBHf2iyoHGPf5w4= github.com/charithe/durationcheck v0.0.10/go.mod h1:bCWXb7gYRysD1CU3C+u4ceO49LoGOY1C1L6uouGNreQ= +github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs= +github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg= github.com/charmbracelet/bubbletea v0.22.1 h1:z66q0LWdJNOWEH9zadiAIXp2GN1AWrwNXU8obVY9X24= github.com/charmbracelet/bubbletea v0.22.1/go.mod h1:8/7hVvbPN6ZZPkczLiB8YpLkLJ0n7DMho5Wvfd2X1C0= +github.com/charmbracelet/bubbletea v1.3.4 h1:kCg7B+jSCFPLYRA52SDZjr51kG/fMUEoPoZrkaDHyoI= +github.com/charmbracelet/bubbletea v1.3.4/go.mod h1:dtcUCyCGEX3g9tosuYiut3MXgY/Jsv9nKVdibKKRRXo= +github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs= +github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk= github.com/charmbracelet/keygen v0.5.0 h1:XY0fsoYiCSM9axkrU+2ziE6u6YjJulo/b9Dghnw6MZc= github.com/charmbracelet/keygen v0.5.0/go.mod h1:DfvCgLHxZ9rJxdK0DGw3C/LkV4SgdGbnliHcObV3L+8= github.com/charmbracelet/lipgloss v0.10.0 h1:KWeXFSexGcfahHX+54URiZGkBFazf70JNMtwg/AFW3s= github.com/charmbracelet/lipgloss v0.10.0/go.mod h1:Wig9DSfvANsxqkRsqj6x87irdy123SR4dOXlKa91ciE= +github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= +github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= +github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE= +github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q= +github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8= +github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= github.com/charmbracelet/x/exp/ordered v0.0.0-20231010190216-1cb11efc897d h1:+o+e/8hf7cG0SbAzEAm/usJ8qoZPgFXhudLjop+TM0g= github.com/charmbracelet/x/exp/ordered v0.0.0-20231010190216-1cb11efc897d/go.mod h1:aoG4bThKYIOnyB55r202eHqo6TkN7ZXV+cu4Do3eoBQ= +github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= +github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= github.com/chavacava/garif v0.1.0 h1:2JHa3hbYf5D9dsgseMKAmc/MZ109otzgNFk5s87H9Pc= github.com/chavacava/garif v0.1.0/go.mod h1:XMyYCkEL58DF0oyW4qDjjnPWONs2HBqYKI+UIPD+Gww= github.com/chrismellard/docker-credential-acr-env v0.0.0-20230304212654-82a0ddb27589 h1:krfRl01rzPzxSxyLyrChD+U+MzsBXbm0OwYYB67uF+4= @@ -522,6 +536,8 @@ github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJP github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= +github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= +github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= github.com/ettle/strcase v0.2.0 h1:fGNiVF21fHXpX1niBgk0aROov1LagYsOwV/xqKDKR/Q= github.com/ettle/strcase v0.2.0/go.mod h1:DajmHElDSaX76ITe3/VHVyMin4LWSJN5Z909Wp+ED1A= github.com/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8= @@ -1129,6 +1145,8 @@ github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjW github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho= github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70 h1:kMlmsLSbjkikxQJ1IPwaM+7LJ9ltFu/fi8CRzvSnQmA= github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho= +github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= +github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= github.com/muesli/mango v0.1.0 h1:DZQK45d2gGbql1arsYA4vfg4d7I9Hfx5rX/GCmzsAvI= @@ -1144,6 +1162,8 @@ github.com/muesli/roff v0.1.0/go.mod h1:pjAHQM9hdUUwm/krAfrLGgJkXJ+YuhtsfZ42kieB github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs= github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= +github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= +github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE= github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI= github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0= @@ -1485,6 +1505,8 @@ github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofm github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/yagipy/maintidx v1.0.0 h1:h5NvIsCz+nRDapQ0exNv4aJ0yXSI0420omVANTv3GJM= github.com/yagipy/maintidx v1.0.0/go.mod h1:0qNf/I/CCZXSMhsRsrEPDZ+DkekpKLXAJfsTACwgXLk= github.com/yeya24/promlinter v0.3.0 h1:JVDbMp08lVCP7Y6NP3qHroGAO6z2yGKQtS5JsjqtoFs= @@ -1748,6 +1770,7 @@ golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=