Skip to content

Commit c295288

Browse files
authored
Add runtime selector (#80)
* Use logrus for tui logging * Rename model -> tui Signed-off-by: Evan Harris <echarris@smcm.edu> * Rename model -> tui Signed-off-by: Evan Harris <echarris@smcm.edu> * Add attempt to connect to socket before selecting it as runtime Signed-off-by: Evan Harris <echarris@smcm.edu> * Add skeleton for selecting runtime Signed-off-by: Evan Harris <echarris@smcm.edu> * Support state subscription Signed-off-by: Evan Harris <echarris@smcm.edu> * Add initial runtime swap support for debug Signed-off-by: Evan Harris <echarris@smcm.edu> * Only display runtime picker controls when select is displayed Signed-off-by: Evan Harris <echarris@smcm.edu> * Do not return Home model in standalone mode Signed-off-by: Evan Harris <echarris@smcm.edu> * Rm dead wood comment Signed-off-by: Evan Harris <echarris@smcm.edu> * Remove the notion of 'chosen' Signed-off-by: Evan Harris <echarris@smcm.edu> * Hide debuggable containers when changing runtime Signed-off-by: Evan Harris <echarris@smcm.edu> * Swallow l keypress while selecting runtime Signed-off-by: Evan Harris <echarris@smcm.edu> * Cleanup comments, logging and variable naming Signed-off-by: Evan Harris <echarris@smcm.edu> * Use default namespace Signed-off-by: Evan Harris <echarris@smcm.edu> * Add state output to the start of the kubenetes runtime Signed-off-by: Evan Harris <echarris@smcm.edu> * Support tui -> d entrypoint Signed-off-by: Evan Harris <echarris@smcm.edu> * Add skeleton for selecting runtime Signed-off-by: Evan Harris <echarris@smcm.edu> * Support state subscription Signed-off-by: Evan Harris <echarris@smcm.edu> * Add initial runtime swap support for debug Signed-off-by: Evan Harris <echarris@smcm.edu> * Only display runtime picker controls when select is displayed Signed-off-by: Evan Harris <echarris@smcm.edu> * Do not return Home model in standalone mode Signed-off-by: Evan Harris <echarris@smcm.edu> * Rm dead wood comment Signed-off-by: Evan Harris <echarris@smcm.edu> * Remove the notion of 'chosen' Signed-off-by: Evan Harris <echarris@smcm.edu> * Hide debuggable containers when changing runtime Signed-off-by: Evan Harris <echarris@smcm.edu> * Swallow l keypress while selecting runtime Signed-off-by: Evan Harris <echarris@smcm.edu> * Cleanup comments, logging and variable naming Signed-off-by: Evan Harris <echarris@smcm.edu> * Use default namespace Signed-off-by: Evan Harris <echarris@smcm.edu> * Add state output to the start of the kubenetes runtime Signed-off-by: Evan Harris <echarris@smcm.edu> * Support tui -> d entrypoint Signed-off-by: Evan Harris <echarris@smcm.edu> --------- Signed-off-by: Evan Harris <echarris@smcm.edu>
1 parent 46a2715 commit c295288

File tree

5 files changed

+158
-47
lines changed

5 files changed

+158
-47
lines changed

pkg/app/execontext.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ func NewOutput(cmdName string, quiet bool, outputFormat string, channels map[str
143143
// And dump it onto the appropriate DataChannels
144144
go func() {
145145
for data := range ref.internalDataCh {
146+
log.Debugf("execontext internal data: %v\n", data)
146147
if data != nil {
147148
for _, ch := range ref.DataChannels {
148149
ch <- data
@@ -297,7 +298,7 @@ func (ref *Output) Data(channelKey string, data interface{}) {
297298
}
298299

299300
func (ref *Output) State(state string, params ...OutVars) {
300-
if ref.Quiet {
301+
if ref.Quiet && ref.OutputFormat != ofSubscription {
301302
return
302303
}
303304

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

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

359362
default:
360363
log.Fatalf("Unknown console output flag: %s\n. It should be either 'text' or 'json", ref.OutputFormat)

pkg/app/master/command/debug/handle_kubernetes_runtime.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ func HandleKubernetesRuntime(
4444
log.Fields{
4545
"op": "debug.HandleKubernetesRuntime",
4646
})
47+
xc.Out.State("kubernetes.runtime.handler.started")
4748

4849
cpJson, _ := json.Marshal(commandParams)
4950
logger.WithField("cparams", string(cpJson)).Trace("call")

pkg/app/master/command/debug/tui.go

Lines changed: 145 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
package debug
22

33
import (
4+
"fmt"
45
"strconv"
56

67
"github.com/charmbracelet/bubbles/key"
78
"github.com/charmbracelet/lipgloss"
89
"github.com/charmbracelet/lipgloss/table"
10+
log "github.com/sirupsen/logrus"
911

1012
"github.com/mintoolkit/mint/pkg/app"
1113
"github.com/mintoolkit/mint/pkg/app/master/command"
1214
"github.com/mintoolkit/mint/pkg/app/master/tui/common"
1315
"github.com/mintoolkit/mint/pkg/app/master/tui/keys"
16+
"github.com/mintoolkit/mint/pkg/crt"
1417

1518
tea "github.com/charmbracelet/bubbletea"
1619
)
@@ -24,8 +27,14 @@ type TUI struct {
2427
table table.Table
2528

2629
showDebuggableContainers bool
30+
showRuntimeSelectorView bool
2731

2832
gcvalues *command.GenericParams
33+
34+
// runtime selection controls
35+
choice int
36+
37+
runtime string
2938
}
3039

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

5162
// End Styles - move to common - block
5263

53-
func LoadTUI() *TUI {
54-
m := &TUI{
55-
width: 20,
56-
height: 15,
57-
loading: true,
58-
}
59-
return m
60-
}
61-
6264
// InitialTUI returns the initial state of the model.
6365
func InitialTUI(standalone bool, gcvalues *command.GenericParams) *TUI {
6466
m := &TUI{
6567
standalone: standalone,
6668
width: 20,
6769
height: 15,
6870
gcvalues: gcvalues,
71+
runtime: crt.AutoSelectRuntime(),
6972
}
7073

7174
return m
@@ -85,32 +88,29 @@ type DebuggableContainer struct {
8588
func (m TUI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
8689
switch msg := msg.(type) {
8790
case common.Event:
88-
debuggableContainersCh := make(chan interface{})
91+
subscriptionChannel := make(chan interface{})
8992
// NOTE -> the names of both the channel map and the channel are misleading
9093
// as more than just the debuggable container information is dumped on it
9194
// at the moment.
92-
debuggableContainersChannelMap := map[string]chan interface{}{
93-
"debuggableContainers": debuggableContainersCh,
95+
subscriptionChannels := map[string]chan interface{}{
96+
"debuggableContainers": subscriptionChannel,
9497
}
9598
// In addition to passing the channel(s) we will use to transport data
9699
// we should pass:
97100
// the outputs we want to subscribe to: State | Info | Error
98101
xc := app.NewExecutionContext(
99102
"tui",
100-
// Quiet -> when set to true, returns on the first line for each
101-
// Execution context method
102103
true,
103104
"subscription",
104-
debuggableContainersChannelMap,
105+
subscriptionChannels,
105106
)
106107

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

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

127127
doneCh := make(chan struct{})
128128
go func() {
129-
for debuggableContainersData := range debuggableContainersCh {
130-
channelResponse, ok := debuggableContainersData.(map[string]string)
129+
for subscriptionData := range subscriptionChannel {
130+
channelResponse, ok := subscriptionData.(map[string]string)
131131
if !ok || channelResponse == nil {
132132
continue
133133
}
134+
135+
log.Debugf("Channel response in tui: %v", channelResponse)
136+
stateValue, stateExists := channelResponse["state"]
137+
if stateExists {
138+
log.Debugf("State value: %s", stateValue)
139+
if stateValue == "kubernetes.runtime.handler.started" {
140+
// TODO - what would we like to do with this information?
141+
// && we likely will want to add similar handling for the other runtimes.
142+
} else if stateValue == "completed" {
143+
// We get 'completed' then we get 'done'
144+
log.Debug("Exiting channel listening loop in update. State is complete.")
145+
break
146+
}
147+
}
148+
134149
infoValue, infoExists := channelResponse["info"]
135150
if infoExists {
136-
// Set total debuggable container counter ceiling
137151
if infoValue == "debuggable.containers" && counterCeiling == 0 {
152+
// Start docker runtime driven debuggable container handling
153+
// Set total debuggable container counter ceiling
138154
countInt, err := strconv.Atoi(channelResponse["count"])
139155
if err != nil {
140156
continue
141157
}
142158
counterCeiling = countInt
143159
} else if infoValue == "debuggable.container" {
160+
log.Debugln("-----------------------Got debuggable container-----------------------")
144161
debuggableContainers = append(debuggableContainers, DebuggableContainer{
145162
Name: channelResponse["name"],
146163
Image: channelResponse["image"],
@@ -149,9 +166,12 @@ func (m TUI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
149166
}
150167
}
151168

169+
// The notion of a count[er] does not exist for the k8s
170+
// But it does for podman & docker.
152171
if counterCeiling > 0 && counter == counterCeiling {
153172
break
154173
}
174+
// End debuggable container handling
155175
}
156176
m.table = generateTable(debuggableContainers)
157177
close(doneCh)
@@ -164,25 +184,48 @@ func (m TUI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
164184
switch {
165185
case key.Matches(msg, keys.Global.Quit):
166186
return m, tea.Quit
167-
// NOTE -> We should only support this back navigation,
168-
// if the tui is not in standalone mode.
169187
case key.Matches(msg, keys.Global.Back):
188+
if m.standalone {
189+
return m, nil
190+
}
170191
return common.TUIsInstance.Home, nil
171192
case key.Matches(msg, keys.Debug.LoadDebuggableContainers):
172-
// Kickoff loading of debuggable containers in standalone mode.
173-
if m.standalone {
174-
loadDebuggableContainers := common.Event{
175-
Type: common.LaunchDebugEvent,
176-
Data: m.gcvalues,
177-
}
178-
m, _ := m.Update(loadDebuggableContainers)
193+
if m.showRuntimeSelectorView {
179194
return m, nil
180195
}
181-
182-
// When used via `tui -> debug`
183-
m.showDebuggableContainers = !m.showDebuggableContainers
196+
loadDebuggableContainers := common.Event{
197+
Type: common.LaunchDebugEvent,
198+
Data: m.gcvalues,
199+
}
200+
m, _ := m.Update(loadDebuggableContainers)
184201
return m, nil
202+
case key.Matches(msg, keys.Debug.ChangeRuntime):
203+
m.showDebuggableContainers = false
204+
m.showRuntimeSelectorView = !m.showRuntimeSelectorView
205+
return m, nil
206+
}
207+
}
208+
return updateChoices(msg, m)
209+
}
185210

211+
func updateChoices(msg tea.Msg, m TUI) (tea.Model, tea.Cmd) {
212+
switch msg := msg.(type) {
213+
case tea.KeyMsg:
214+
switch msg.String() {
215+
case "j", "down":
216+
m.choice++
217+
if m.choice > 3 {
218+
m.choice = 0
219+
}
220+
case "k", "up":
221+
m.choice--
222+
if m.choice < 0 {
223+
m.choice = 3
224+
}
225+
case "enter":
226+
m.runtime = setNewRuntime(m.choice)
227+
m.showRuntimeSelectorView = false
228+
return m, nil
186229
}
187230
}
188231
return m, nil
@@ -218,6 +261,48 @@ func generateTable(debuggableContainers []DebuggableContainer) table.Table {
218261
return *t
219262
}
220263

264+
func choicesView(m TUI) string {
265+
choice := m.choice
266+
267+
template := "Choose runtime for debug mode\n\n"
268+
template += "%s\n\n"
269+
270+
// NOTE -> the chocies we display here should only be runtiems we can
271+
// establish a connection to.
272+
// Otherwise, we set the user up for failure.
273+
274+
choices := fmt.Sprintf(
275+
"%s\n%s\n%s\n%s",
276+
checkbox("Docker", choice == 0),
277+
checkbox("Containerd", choice == 1),
278+
checkbox("Podman", choice == 2),
279+
checkbox("Kubernetes", choice == 3),
280+
)
281+
return fmt.Sprintf(template, choices)
282+
}
283+
284+
func checkbox(label string, checked bool) string {
285+
if checked {
286+
return CheckboxStyle.Render("[x] " + label)
287+
}
288+
return fmt.Sprintf("[ ] %s", label)
289+
}
290+
291+
func setNewRuntime(choice int) string {
292+
switch choice {
293+
case 0:
294+
return crt.DockerRuntime
295+
case 1:
296+
return crt.ContainerdRuntime
297+
case 2:
298+
return crt.PodmanRuntime
299+
case 3:
300+
return crt.KubernetesRuntime
301+
default:
302+
return crt.AutoRuntime
303+
}
304+
}
305+
221306
// View returns the view that should be displayed.
222307
func (m TUI) View() string {
223308
var components []string
@@ -229,15 +314,23 @@ func (m TUI) View() string {
229314
// 4. Connect to a debug session
230315
// 5. Start a new debug session
231316

232-
content := "Debug Dashboard\n"
317+
header := "Debug Dashboard\n"
318+
319+
currentRuntime := fmt.Sprintf("Current Runtime: %s.\n", m.runtime)
233320

234-
components = append(components, content)
321+
components = append(components, header, currentRuntime)
235322

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

328+
if m.showRuntimeSelectorView {
329+
var runtimeSelectorContent string
330+
runtimeSelectorContent = choicesView(m)
331+
components = append(components, runtimeSelectorContent)
332+
}
333+
241334
components = append(components, m.help())
242335

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

248341
func (m TUI) help() string {
249-
var listOrHide string
342+
var debuggableContainersHelp, runtimeSelectorHelp string
250343

251-
if m.showDebuggableContainers {
252-
listOrHide = "hide"
344+
if m.showRuntimeSelectorView {
345+
// Only display the navigation controls if the using is changing their runtime
346+
runtimeSelectorHelp = "cancel • j/k, up/down: select • enter: choose"
253347
} else {
254-
listOrHide = "list"
348+
runtimeSelectorHelp = "change runtime"
349+
350+
// Hide debuggable container help when selecting runtime
351+
if m.showDebuggableContainers {
352+
debuggableContainersHelp = "• l: hide debuggable containers"
353+
} else {
354+
debuggableContainersHelp = "• l: list debuggable containers"
355+
}
356+
255357
}
256358

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

261-
return common.HelpStyle("• l: " + listOrHide + " debuggable containers • esc: back • q: quit")
363+
return common.HelpStyle(debuggableContainersHelp + " • r: " + runtimeSelectorHelp + " • esc: back • q: quit")
262364
}

pkg/app/master/tui/home/tui.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,9 @@ func (m TUI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
5151
Data: m.Gcvalues,
5252
}
5353

54-
LoadTUI := debug.LoadTUI()
55-
common.TUIsInstance.Debug = LoadTUI
56-
return LoadTUI.Update(launchDebugEvent)
54+
initialTUI := debug.InitialTUI(false, m.Gcvalues)
55+
common.TUIsInstance.Debug = initialTUI
56+
return initialTUI.Update(launchDebugEvent)
5757
}
5858
}
5959
return m, nil

pkg/app/master/tui/keys/keys.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ type home struct {
3333

3434
type debug struct {
3535
LoadDebuggableContainers key.Binding
36+
ChangeRuntime key.Binding
3637
}
3738

3839
var Home = home{
@@ -51,4 +52,8 @@ var Debug = debug{
5152
key.WithKeys("l"),
5253
key.WithHelp("l", "Load debuggable containers"),
5354
),
55+
ChangeRuntime: key.NewBinding(
56+
key.WithKeys("r"),
57+
key.WithHelp("r", "Change runtime"),
58+
),
5459
}

0 commit comments

Comments
 (0)