Skip to content

Commit

Permalink
Restart jobs, tasks and allocs (#122)
Browse files Browse the repository at this point in the history
  • Loading branch information
cshintov committed Jan 29, 2024
1 parent 1112c1e commit 86fb38c
Show file tree
Hide file tree
Showing 12 changed files with 311 additions and 29 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Expand Up @@ -10,3 +10,6 @@ vendor
ignore.gif
**/*/my_logs.txt
opt
.vscode
__debug_bin*

1 change: 1 addition & 0 deletions README.md
Expand Up @@ -10,6 +10,7 @@
An efficient terminal application/TUI for interacting with your [HashiCorp Nomad](https://www.nomadproject.io/) cluster.

- Browse jobs, allocations, and tasks
- Restart tasks
- Live tail logs
- Tail global or targeted events
- Exec to interact with running tasks
Expand Down
23 changes: 14 additions & 9 deletions internal/dev/dev.go
Expand Up @@ -9,15 +9,20 @@ import (

var debugSet = os.Getenv("WANDER_DEBUG")

// dev
func Debug(msg string) {
if debugSet != "" {
f, err := tea.LogToFile("wander.log", "")
if err != nil {
fmt.Println("fatal:", err)
os.Exit(1)
// Returns a function that prints a message to the log file if the WANDER_DEBUG
// environment variable is set.
func createDebug(path string) func(string) {
return func (msg string) {
if debugSet != "" {
f, err := tea.LogToFile(path, "")
if err != nil {
fmt.Println("fatal:", err)
os.Exit(1)
}
log.Printf("%q", msg)
defer f.Close()
}
log.Printf("%q", msg)
defer f.Close()
}
}

var Debug = createDebug("wander.log")
122 changes: 106 additions & 16 deletions internal/tui/components/app/app.go
Expand Up @@ -2,11 +2,18 @@ package app

import (
"fmt"
"os"
"os/exec"
"path"
"strings"
"time"

"github.com/charmbracelet/bubbles/key"
tea "github.com/charmbracelet/bubbletea"
"github.com/hashicorp/nomad/api"
"github.com/itchyny/gojq"
"github.com/robinovitch61/wander/internal/dev"
"github.com/robinovitch61/wander/internal/tui/components/toast"
"github.com/robinovitch61/wander/internal/tui/components/header"
"github.com/robinovitch61/wander/internal/tui/components/page"
"github.com/robinovitch61/wander/internal/tui/constants"
Expand All @@ -15,11 +22,6 @@ import (
"github.com/robinovitch61/wander/internal/tui/message"
"github.com/robinovitch61/wander/internal/tui/nomad"
"github.com/robinovitch61/wander/internal/tui/style"
"os"
"os/exec"
"path"
"strings"
"time"
)

type TLSConfig struct {
Expand Down Expand Up @@ -61,13 +63,13 @@ type Config struct {
}

type Model struct {
config Config
client api.Client
config Config
client api.Client

header header.Model
compact bool
currentPage nomad.Page
pageModels map[nomad.Page]*page.Model
header header.Model
compact bool
currentPage nomad.Page
pageModels map[nomad.Page]*page.Model

inJobsMode bool
jobID string
Expand All @@ -88,6 +90,9 @@ type Model struct {
logsStream nomad.LogsStream
lastLogFinished bool

// adminAction is a key of TaskAdminActions (or JobAdminActions, when it exists)
adminAction nomad.AdminAction

width, height int
initialized bool
err error
Expand Down Expand Up @@ -180,8 +185,8 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// but returns empty results when one provides an empty token
m.getCurrentPageModel().SetHeader([]string{"Error"})
m.getCurrentPageModel().SetAllPageRows([]page.Row{
{"", "No results. Is the cluster empty or was no nomad token provided?"},
{"", "Press q or ctrl+c to quit."},
{Key: "", Row: "No results. Is the cluster empty or was no nomad token provided?"},
{Key: "", Row: "Press q or ctrl+c to quit."},
})
m.getCurrentPageModel().SetViewportSelectionEnabled(false)
}
Expand Down Expand Up @@ -296,6 +301,28 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return nomad.ExecCompleteMsg{Output: string(stdoutProxy.SavedOutput)}
})
}

case nomad.TaskAdminActionCompleteMsg:
m.getCurrentPageModel().SetToast(
toast.New(
fmt.Sprintf(
"%s completed successfully",
nomad.GetTaskAdminText(
m.adminAction, msg.TaskName, msg.AllocName, msg.AllocID))),
style.SuccessToast,
)

case nomad.TaskAdminActionFailedMsg:
m.getCurrentPageModel().SetToast(
toast.New(
fmt.Sprintf(
"%s failed with error: %s",
nomad.GetTaskAdminText(
m.adminAction, msg.TaskName, msg.AllocName, msg.AllocID),
msg.Error())),
style.ErrorToast,
)

}

currentPageModel = m.getCurrentPageModel()
Expand Down Expand Up @@ -375,6 +402,7 @@ func (m *Model) handleKeyMsg(msg tea.KeyMsg) tea.Cmd {

if !m.currentPageFilterFocused() && !m.currentPageViewportSaving() {
switch {

case key.Matches(msg, keymap.KeyMap.Compact):
m.toggleCompact()
return nil
Expand All @@ -388,6 +416,18 @@ func (m *Model) handleKeyMsg(msg tea.KeyMsg) tea.Cmd {
m.event = selectedPageRow.Key
case nomad.LogsPage:
m.logline = selectedPageRow.Row
case nomad.TaskAdminPage:
m.adminAction = nomad.KeyToAdminAction(selectedPageRow.Key)
case nomad.TaskAdminConfirmPage:
if selectedPageRow.Key == constants.ConfirmationKey {
cmds = append(cmds, nomad.GetCmdForTaskAdminAction(
m.client, m.adminAction, m.taskName, m.alloc.Name, m.alloc.ID))
} else {
backPage := m.currentPage.Backward(m.inJobsMode)
m.setPage(backPage)
cmds = append(cmds, m.getCurrentPageCmd())
return tea.Batch(cmds...)
}
default:
if m.currentPage.ShowsTasks() {
taskInfo, err := nomad.TaskInfoFromKey(selectedPageRow.Key)
Expand All @@ -399,10 +439,11 @@ func (m *Model) handleKeyMsg(msg tea.KeyMsg) tea.Cmd {
}
}

nextPage := m.currentPage.Forward()
nextPage := m.currentPage.Forward(m.inJobsMode)
if nextPage != m.currentPage {
m.setPage(nextPage)
return m.getCurrentPageCmd()
cmds = append(cmds, m.getCurrentPageCmd())
return tea.Batch(cmds...)
}
}

Expand All @@ -426,6 +467,7 @@ func (m *Model) handleKeyMsg(msg tea.KeyMsg) tea.Cmd {
m.getCurrentPageModel().SetLoading(true)
return m.getCurrentPageCmd()
}

}

if key.Matches(msg, keymap.KeyMap.Exec) {
Expand Down Expand Up @@ -534,6 +576,22 @@ func (m *Model) handleKeyMsg(msg tea.KeyMsg) tea.Cmd {
return m.getCurrentPageCmd()
}

if key.Matches(msg, keymap.KeyMap.AdminMenu) && m.currentPage.HasAdminMenu() {
if selectedPageRow, err := m.getCurrentPageModel().GetSelectedPageRow(); err == nil {
// Get task info from the currently selected row
taskInfo, err := nomad.TaskInfoFromKey(selectedPageRow.Key)
if err != nil {
m.err = err
return nil
}
if taskInfo.Running {
m.alloc, m.taskName = taskInfo.Alloc, taskInfo.TaskName
m.setPage(nomad.TaskAdminPage)
return m.getCurrentPageCmd()
}
}
}

if m.currentPage == nomad.LogsPage {
switch {
case key.Matches(msg, keymap.KeyMap.StdOut):
Expand Down Expand Up @@ -654,8 +712,40 @@ func (m Model) getCurrentPageCmd() tea.Cmd {
return nomad.PrettifyLine(m.logline, nomad.LoglinePage)
case nomad.StatsPage:
return nomad.FetchStats(m.client, m.alloc.ID, m.alloc.Name)
case nomad.TaskAdminPage:
return func() tea.Msg {
// this does no async work, just constructs the task admin menu
var rows []page.Row
for action := range nomad.TaskAdminActions {
rows = append(rows, page.Row{
Key: nomad.AdminActionToKey(action),
Row: nomad.GetTaskAdminText(action, m.taskName, m.alloc.Name, m.alloc.ID),
})
}
return nomad.PageLoadedMsg{
Page: nomad.TaskAdminPage,
TableHeader: []string{"Available Admin Actions"},
AllPageRows: rows,
}
}

case nomad.TaskAdminConfirmPage:
return func() tea.Msg {
// this does no async work, just constructs the confirmation page
confirmationText := nomad.GetTaskAdminText(m.adminAction, m.taskName, m.alloc.Name, m.alloc.ID)
confirmationText = strings.ToLower(confirmationText[:1]) + confirmationText[1:]
return nomad.PageLoadedMsg{
Page: nomad.TaskAdminConfirmPage,
TableHeader: []string{"Are you sure?"},
AllPageRows: []page.Row{
{Key: "Cancel", Row: "Cancel"},
{Key: constants.ConfirmationKey, Row: fmt.Sprintf("Yes, %s", confirmationText)},
},
}
}

default:
panic("page load command not found")
panic(fmt.Sprintf("Load command for page:%s not found", m.currentPage))
}
}

Expand Down
7 changes: 6 additions & 1 deletion internal/tui/components/page/page.go
Expand Up @@ -2,6 +2,8 @@ package page

import (
"fmt"
"strings"

"github.com/atotto/clipboard"
"github.com/charmbracelet/bubbles/key"
"github.com/charmbracelet/bubbles/textinput"
Expand All @@ -14,7 +16,6 @@ import (
"github.com/robinovitch61/wander/internal/tui/constants"
"github.com/robinovitch61/wander/internal/tui/keymap"
"github.com/robinovitch61/wander/internal/tui/message"
"strings"
)

type Config struct {
Expand Down Expand Up @@ -245,6 +246,10 @@ func (m *Model) SetViewportXOffset(n int) {
m.viewport.SetXOffset(n)
}

func (m *Model) SetToast(toast toast.Model, style lipgloss.Style) {
m.viewport.SetToast(toast, style)
}

func (m *Model) HideToast() {
m.viewport.HideToast()
}
Expand Down
5 changes: 5 additions & 0 deletions internal/tui/components/viewport/viewport.go
Expand Up @@ -354,6 +354,11 @@ func (m *Model) ToggleWrapText() {
m.updateForWrapText()
}

func (m *Model) SetToast(toast toast.Model, style lipgloss.Style) {
m.toast = toast
m.toast.MessageStyle = style.Copy().Width(m.width)
}

func (m *Model) HideToast() {
m.toast.Visible = false
}
Expand Down
2 changes: 2 additions & 0 deletions internal/tui/constants/constants.go
Expand Up @@ -34,3 +34,5 @@ const DefaultEventJQQuery = `.Events[] | {"1:Index": .Index, "2:Topic": .Topic,

// DefaultAllocEventJQQuery is a single line as this shows up verbatim in `wander --help`
const DefaultAllocEventJQQuery = `.Index as $index | .Events[] | .Type as $type | .Payload.Allocation | .DeploymentStatus.Healthy as $healthy | .ClientStatus as $clientStatus | .Name as $allocName | (.TaskStates // {"":{"Events": [{}]}}) | to_entries[] | .key as $k | .value.Events[] | {"0:Index": $index, "1:AllocName": $allocName, "2:TaskName": $k, "3:Type": $type, "4:Time": ((.Time // 0) / 1000000000 | todate), "5:Msg": .DisplayMessage, "6:Healthy": $healthy, "7:ClientStatus": $clientStatus}`

const ConfirmationKey = "Yes"
5 changes: 5 additions & 0 deletions internal/tui/keymap/keymap.go
Expand Up @@ -25,6 +25,7 @@ type keyMap struct {
StdErr key.Binding
Spec key.Binding
Wrap key.Binding
AdminMenu key.Binding
}

var KeyMap = keyMap{
Expand Down Expand Up @@ -108,4 +109,8 @@ var KeyMap = keyMap{
key.WithKeys("ctrl+w"),
key.WithHelp("ctrl+w", "toggle wrap"),
),
AdminMenu: key.NewBinding(
key.WithKeys("X"),
key.WithHelp("X", "admin"),
),
}
4 changes: 4 additions & 0 deletions internal/tui/nomad/jobtasks.go
@@ -1,3 +1,7 @@
/*
Task related functions
*/

package nomad

import (
Expand Down

0 comments on commit 86fb38c

Please sign in to comment.