Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
24ec0e1
Use logrus for tui logging
eharris128 Oct 28, 2024
60e0c99
Rename model -> tui
eharris128 Oct 28, 2024
567a27a
Rename model -> tui
eharris128 Oct 28, 2024
aac6e08
Add attempt to connect to socket before selecting it as runtime
eharris128 Oct 28, 2024
4418ec7
Add skeleton for selecting runtime
eharris128 Oct 28, 2024
47e2afc
Support state subscription
eharris128 Oct 28, 2024
82a1f70
Add initial runtime swap support for debug
eharris128 Oct 28, 2024
b155c90
Only display runtime picker controls when select is displayed
eharris128 Oct 29, 2024
631d2aa
Do not return Home model in standalone mode
eharris128 Oct 29, 2024
dc36c58
Rm dead wood comment
eharris128 Oct 29, 2024
6329a56
Remove the notion of 'chosen'
eharris128 Oct 29, 2024
b928d52
Hide debuggable containers when changing runtime
eharris128 Oct 29, 2024
7690c68
Swallow l keypress while selecting runtime
eharris128 Oct 29, 2024
f0b7904
Cleanup comments, logging and variable naming
eharris128 Oct 29, 2024
89b3487
Use default namespace
eharris128 Oct 29, 2024
4fb097f
Add state output to the start of the kubenetes runtime
eharris128 Oct 29, 2024
875a028
Support tui -> d entrypoint
eharris128 Oct 30, 2024
6427a59
Add skeleton for selecting runtime
eharris128 Oct 28, 2024
800556d
Support state subscription
eharris128 Oct 28, 2024
00bb416
Add initial runtime swap support for debug
eharris128 Oct 28, 2024
6ad93fa
Only display runtime picker controls when select is displayed
eharris128 Oct 29, 2024
355b8ac
Do not return Home model in standalone mode
eharris128 Oct 29, 2024
385aa41
Rm dead wood comment
eharris128 Oct 29, 2024
f775221
Remove the notion of 'chosen'
eharris128 Oct 29, 2024
87e76b5
Hide debuggable containers when changing runtime
eharris128 Oct 29, 2024
e7a08b7
Swallow l keypress while selecting runtime
eharris128 Oct 29, 2024
19b52ca
Cleanup comments, logging and variable naming
eharris128 Oct 29, 2024
fdb133e
Use default namespace
eharris128 Oct 29, 2024
a2c353a
Add state output to the start of the kubenetes runtime
eharris128 Oct 29, 2024
b8e9fea
Support tui -> d entrypoint
eharris128 Oct 30, 2024
e4ae430
Merge branch 'debug-runtime-selector' into add-runtime-selector
eharris128 Oct 30, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion pkg/app/execontext.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ func NewOutput(cmdName string, quiet bool, outputFormat string, channels map[str
// And dump it onto the appropriate DataChannels
go func() {
for data := range ref.internalDataCh {
log.Debugf("execontext internal data: %v\n", data)
if data != nil {
for _, ch := range ref.DataChannels {
ch <- data
Expand Down Expand Up @@ -297,7 +298,7 @@ func (ref *Output) Data(channelKey string, data interface{}) {
}

func (ref *Output) State(state string, params ...OutVars) {
if ref.Quiet {
if ref.Quiet && ref.OutputFormat != ofSubscription {
return
}

Expand Down Expand Up @@ -355,6 +356,8 @@ func (ref *Output) State(state string, params ...OutVars) {
defer color.Unset()

fmt.Printf("cmd=%s state=%s%s%s%s\n", ref.CmdName, state, exitInfo, sep, info)
case ofSubscription:
ref.internalDataCh <- msg // Send data to the internal channel

default:
log.Fatalf("Unknown console output flag: %s\n. It should be either 'text' or 'json", ref.OutputFormat)
Expand Down
1 change: 1 addition & 0 deletions pkg/app/master/command/debug/handle_kubernetes_runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ func HandleKubernetesRuntime(
log.Fields{
"op": "debug.HandleKubernetesRuntime",
})
xc.Out.State("kubernetes.runtime.handler.started")

cpJson, _ := json.Marshal(commandParams)
logger.WithField("cparams", string(cpJson)).Trace("call")
Expand Down
188 changes: 145 additions & 43 deletions pkg/app/master/command/debug/tui.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
package debug

import (
"fmt"
"strconv"

"github.com/charmbracelet/bubbles/key"
"github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/lipgloss/table"
log "github.com/sirupsen/logrus"

"github.com/mintoolkit/mint/pkg/app"
"github.com/mintoolkit/mint/pkg/app/master/command"
"github.com/mintoolkit/mint/pkg/app/master/tui/common"
"github.com/mintoolkit/mint/pkg/app/master/tui/keys"
"github.com/mintoolkit/mint/pkg/crt"

tea "github.com/charmbracelet/bubbletea"
)
Expand All @@ -24,8 +27,14 @@ type TUI struct {
table table.Table

showDebuggableContainers bool
showRuntimeSelectorView bool

gcvalues *command.GenericParams

// runtime selection controls
choice int

runtime string
}

// Styles - move to `common`
Expand All @@ -46,26 +55,20 @@ var (
EvenRowStyle = CellStyle.Foreground(lightGray)
// BorderStyle is the lipgloss style used for the table border.
BorderStyle = lipgloss.NewStyle().Foreground(white)
// CheckboxStyle is the lipgloss style used for the runtime selector
CheckboxStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("212"))
)

// End Styles - move to common - block

func LoadTUI() *TUI {
m := &TUI{
width: 20,
height: 15,
loading: true,
}
return m
}

// InitialTUI returns the initial state of the model.
func InitialTUI(standalone bool, gcvalues *command.GenericParams) *TUI {
m := &TUI{
standalone: standalone,
width: 20,
height: 15,
gcvalues: gcvalues,
runtime: crt.AutoSelectRuntime(),
}

return m
Expand All @@ -85,32 +88,29 @@ type DebuggableContainer struct {
func (m TUI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case common.Event:
debuggableContainersCh := make(chan interface{})
subscriptionChannel := make(chan interface{})
// NOTE -> the names of both the channel map and the channel are misleading
// as more than just the debuggable container information is dumped on it
// at the moment.
debuggableContainersChannelMap := map[string]chan interface{}{
"debuggableContainers": debuggableContainersCh,
subscriptionChannels := map[string]chan interface{}{
"debuggableContainers": subscriptionChannel,
}
// In addition to passing the channel(s) we will use to transport data
// we should pass:
// the outputs we want to subscribe to: State | Info | Error
xc := app.NewExecutionContext(
"tui",
// Quiet -> when set to true, returns on the first line for each
// Execution context method
true,
"subscription",
debuggableContainersChannelMap,
subscriptionChannels,
)

cparams := &CommandParams{
// NOTE -> should not always pass docker here.
Runtime: "docker",
// Note -> we should not pass this by default, and instead pass it when a user asks.
Runtime: m.runtime,
// Passing these three fields all the time does not make sense.
ActionListDebuggableContainers: true,
// How to pass the target ref:
// TargetRef: "my-nginx"
Kubeconfig: crt.KubeconfigDefault,
TargetNamespace: "default",
}

gcValue, ok := msg.Data.(*command.GenericParams)
Expand All @@ -126,21 +126,38 @@ func (m TUI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {

doneCh := make(chan struct{})
go func() {
for debuggableContainersData := range debuggableContainersCh {
channelResponse, ok := debuggableContainersData.(map[string]string)
for subscriptionData := range subscriptionChannel {
channelResponse, ok := subscriptionData.(map[string]string)
if !ok || channelResponse == nil {
continue
}

log.Debugf("Channel response in tui: %v", channelResponse)
stateValue, stateExists := channelResponse["state"]
if stateExists {
log.Debugf("State value: %s", stateValue)
if stateValue == "kubernetes.runtime.handler.started" {
// TODO - what would we like to do with this information?
// && we likely will want to add similar handling for the other runtimes.
} else if stateValue == "completed" {
// We get 'completed' then we get 'done'
log.Debug("Exiting channel listening loop in update. State is complete.")
break
}
}

infoValue, infoExists := channelResponse["info"]
if infoExists {
// Set total debuggable container counter ceiling
if infoValue == "debuggable.containers" && counterCeiling == 0 {
// Start docker runtime driven debuggable container handling
// Set total debuggable container counter ceiling
countInt, err := strconv.Atoi(channelResponse["count"])
if err != nil {
continue
}
counterCeiling = countInt
} else if infoValue == "debuggable.container" {
log.Debugln("-----------------------Got debuggable container-----------------------")
debuggableContainers = append(debuggableContainers, DebuggableContainer{
Name: channelResponse["name"],
Image: channelResponse["image"],
Expand All @@ -149,9 +166,12 @@ func (m TUI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
}

// The notion of a count[er] does not exist for the k8s
// But it does for podman & docker.
if counterCeiling > 0 && counter == counterCeiling {
break
}
// End debuggable container handling
}
m.table = generateTable(debuggableContainers)
close(doneCh)
Expand All @@ -164,25 +184,48 @@ func (m TUI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch {
case key.Matches(msg, keys.Global.Quit):
return m, tea.Quit
// NOTE -> We should only support this back navigation,
// if the tui is not in standalone mode.
case key.Matches(msg, keys.Global.Back):
if m.standalone {
return m, nil
}
return common.TUIsInstance.Home, nil
case key.Matches(msg, keys.Debug.LoadDebuggableContainers):
// Kickoff loading of debuggable containers in standalone mode.
if m.standalone {
loadDebuggableContainers := common.Event{
Type: common.LaunchDebugEvent,
Data: m.gcvalues,
}
m, _ := m.Update(loadDebuggableContainers)
if m.showRuntimeSelectorView {
return m, nil
}

// When used via `tui -> debug`
m.showDebuggableContainers = !m.showDebuggableContainers
loadDebuggableContainers := common.Event{
Type: common.LaunchDebugEvent,
Data: m.gcvalues,
}
m, _ := m.Update(loadDebuggableContainers)
return m, nil
case key.Matches(msg, keys.Debug.ChangeRuntime):
m.showDebuggableContainers = false
m.showRuntimeSelectorView = !m.showRuntimeSelectorView
return m, nil
}
}
return updateChoices(msg, m)
}

func updateChoices(msg tea.Msg, m TUI) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "j", "down":
m.choice++
if m.choice > 3 {
m.choice = 0
}
case "k", "up":
m.choice--
if m.choice < 0 {
m.choice = 3
}
case "enter":
m.runtime = setNewRuntime(m.choice)
m.showRuntimeSelectorView = false
return m, nil
}
}
return m, nil
Expand Down Expand Up @@ -218,6 +261,48 @@ func generateTable(debuggableContainers []DebuggableContainer) table.Table {
return *t
}

func choicesView(m TUI) string {
choice := m.choice

template := "Choose runtime for debug mode\n\n"
template += "%s\n\n"

// NOTE -> the chocies we display here should only be runtiems we can
// establish a connection to.
// Otherwise, we set the user up for failure.

choices := fmt.Sprintf(
"%s\n%s\n%s\n%s",
checkbox("Docker", choice == 0),
checkbox("Containerd", choice == 1),
checkbox("Podman", choice == 2),
checkbox("Kubernetes", choice == 3),
)
return fmt.Sprintf(template, choices)
}

func checkbox(label string, checked bool) string {
if checked {
return CheckboxStyle.Render("[x] " + label)
}
return fmt.Sprintf("[ ] %s", label)
}

func setNewRuntime(choice int) string {
switch choice {
case 0:
return crt.DockerRuntime
case 1:
return crt.ContainerdRuntime
case 2:
return crt.PodmanRuntime
case 3:
return crt.KubernetesRuntime
default:
return crt.AutoRuntime
}
}

// View returns the view that should be displayed.
func (m TUI) View() string {
var components []string
Expand All @@ -229,15 +314,23 @@ func (m TUI) View() string {
// 4. Connect to a debug session
// 5. Start a new debug session

content := "Debug Dashboard\n"
header := "Debug Dashboard\n"

currentRuntime := fmt.Sprintf("Current Runtime: %s.\n", m.runtime)

components = append(components, content)
components = append(components, header, currentRuntime)

if m.showDebuggableContainers {
header := "Debuggable Containers\n"
components = append(components, header, m.table.String())
}

if m.showRuntimeSelectorView {
var runtimeSelectorContent string
runtimeSelectorContent = choicesView(m)
components = append(components, runtimeSelectorContent)
}

components = append(components, m.help())

return lipgloss.JoinVertical(lipgloss.Left,
Expand All @@ -246,17 +339,26 @@ func (m TUI) View() string {
}

func (m TUI) help() string {
var listOrHide string
var debuggableContainersHelp, runtimeSelectorHelp string

if m.showDebuggableContainers {
listOrHide = "hide"
if m.showRuntimeSelectorView {
// Only display the navigation controls if the using is changing their runtime
runtimeSelectorHelp = "cancel • j/k, up/down: select • enter: choose"
} else {
listOrHide = "list"
runtimeSelectorHelp = "change runtime"

// Hide debuggable container help when selecting runtime
if m.showDebuggableContainers {
debuggableContainersHelp = "• l: hide debuggable containers"
} else {
debuggableContainersHelp = "• l: list debuggable containers"
}

}

if m.standalone {
return common.HelpStyle("• l: " + listOrHide + " debuggable containers • q: quit")
return common.HelpStyle(debuggableContainersHelp + " • r: " + runtimeSelectorHelp + " • q: quit")
}

return common.HelpStyle("• l: " + listOrHide + " debuggable containers • esc: back • q: quit")
return common.HelpStyle(debuggableContainersHelp + " • r: " + runtimeSelectorHelp + " • esc: back • q: quit")
}
6 changes: 3 additions & 3 deletions pkg/app/master/tui/home/tui.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@ func (m TUI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
Data: m.Gcvalues,
}

LoadTUI := debug.LoadTUI()
common.TUIsInstance.Debug = LoadTUI
return LoadTUI.Update(launchDebugEvent)
initialTUI := debug.InitialTUI(false, m.Gcvalues)
common.TUIsInstance.Debug = initialTUI
return initialTUI.Update(launchDebugEvent)
}
}
return m, nil
Expand Down
5 changes: 5 additions & 0 deletions pkg/app/master/tui/keys/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ type home struct {

type debug struct {
LoadDebuggableContainers key.Binding
ChangeRuntime key.Binding
}

var Home = home{
Expand All @@ -51,4 +52,8 @@ var Debug = debug{
key.WithKeys("l"),
key.WithHelp("l", "Load debuggable containers"),
),
ChangeRuntime: key.NewBinding(
key.WithKeys("r"),
key.WithHelp("r", "Change runtime"),
),
}
Loading