Skip to content

Commit

Permalink
WIP: hit l for logs
Browse files Browse the repository at this point in the history
  • Loading branch information
UncleGedd committed Mar 14, 2024
1 parent 3f187f8 commit 66d6c68
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 21 deletions.
6 changes: 5 additions & 1 deletion src/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ func cliSetup() {
}

if !config.SkipLogFile && !config.ListTasks {
utils.UseLogFile()
logFileName, err := utils.UseLogFile()
if err != nil {
message.Fatal(err, "Error creating log file")
}
config.LogFileName = logFileName
}
}
2 changes: 1 addition & 1 deletion src/cmd/uds.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ var deployCmd = &cobra.Command{

// detect tty so CI/containers don't break
if term.IsTerminal(int(os.Stdout.Fd())) {
tui.Program = tea.NewProgram(&m)
tui.Program = tea.NewProgram(&m, tea.WithMouseCellMotion())
} else {
tui.Program = tea.NewProgram(&m, tea.WithInput(nil))
}
Expand Down
10 changes: 2 additions & 8 deletions src/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,6 @@ const (
// UDSCacheLayers is the directory in the cache containing cached bundle layers
UDSCacheLayers = "layers"

// TasksYAML is the default name of the uds run cmd file
TasksYAML = "tasks.yaml"

// EnvVarPrefix is the prefix for environment variables to override bundle helm variables
EnvVarPrefix = "UDS_"

Expand All @@ -79,11 +76,8 @@ var (
// ListTasks is a flag to print available tasks in a TaskFileLocation
ListTasks bool

// TaskFileLocation is the location of the tasks file to run
TaskFileLocation string

// SetRunnerVariables is a map of the run time variables defined using --set
SetRunnerVariables map[string]string
// LogFileName is the name of the UDS log file
LogFileName string

// HelmTimeout is the default timeout for helm deploys
HelmTimeout = 15 * time.Minute
Expand Down
78 changes: 68 additions & 10 deletions src/pkg/bundle/tui/tui.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,21 @@ package tui

import (
"fmt"
"os"
"regexp"
"strconv"
"strings"
"time"

"github.com/charmbracelet/bubbles/spinner"
"github.com/charmbracelet/bubbles/viewport"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/defenseunicorns/uds-cli/src/config"
"github.com/defenseunicorns/uds-cli/src/pkg/utils"
"github.com/defenseunicorns/zarf/src/config"
zarfConfig "github.com/defenseunicorns/zarf/src/config"
"github.com/defenseunicorns/zarf/src/pkg/cluster"
"github.com/defenseunicorns/zarf/src/pkg/message"
zarfTypes "github.com/defenseunicorns/zarf/src/types"
"github.com/fatih/color"
"github.com/goccy/go-yaml/lexer"
Expand Down Expand Up @@ -62,22 +66,35 @@ type Model struct {
done bool
packages []pkgState
deploying bool
inProgress bool
viewLogs bool
viewport viewport.Model
isScrolling bool
}

func InitModel(client bndlClientShim, bundleYAML string) Model {

Check warning on line 75 in src/pkg/bundle/tui/tui.go

View workflow job for this annotation

GitHub Actions / validate

exported function InitModel should have comment or be unexported
message.NoProgress = true
zarfConfig.NoColor = true
var confirmed bool
var inProgress bool
if config.CommonOptions.Confirm {
confirmed = true
inProgress = true
}

c, _ = cluster.NewCluster()

logViewport := viewport.New(1000, 5)
logViewport.MouseWheelEnabled = true

return Model{
bndlClient: client,
completeChan: make(chan int),
fatalChan: make(chan error),
confirmed: confirmed,
bundleYAML: bundleYAML,
inProgress: inProgress,
viewport: logViewport,
}
}

Expand All @@ -100,13 +117,17 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
)
select {
case <-m.fatalChan:
// todo: test failures......
m.packages[m.pkgIdx].spinner.Spinner.Frames = []string{""}
m.packages[m.pkgIdx].spinner.Style = lipgloss.NewStyle().SetString("❌")
s, spinnerCmd := m.packages[m.pkgIdx].spinner.Update(spinner.TickMsg{})
m.packages[m.pkgIdx].spinner.Spinner = s.Spinner
return m, tea.Sequence(spinnerCmd, pause(), tea.Quit)
default:
switch msg := msg.(type) {
case tea.MouseMsg:
m.isScrolling = true
m.viewport, _ = m.viewport.Update(msg)
case spinner.TickMsg:
var cmd tea.Cmd
m.packages[m.pkgIdx].spinner, cmd = m.packages[m.pkgIdx].spinner.Update(msg)
Expand All @@ -132,7 +153,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
successCmds := []tea.Cmd{tea.Println(line)}
for i := 0; i < m.totalPkgs; i++ {
successStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("#32A852"))
successMsg := fmt.Sprintf("\t✅ Package %s deployed", m.packages[i].name)
successMsg := fmt.Sprintf("✅ Package %s deployed", m.packages[i].name)
successCmds = append(successCmds, tea.Println(successStyle.Render(successMsg)))
}

Expand Down Expand Up @@ -167,23 +188,39 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
}

// always update viewport content with logs
file, _ := os.ReadFile(config.LogFileName)
m.viewport.SetContent(string(file))
if !m.isScrolling {
m.viewport.GotoBottom()
}

return m, tickCmd()

case tea.KeyMsg:
switch msg.String() {
case "y", "Y":
if !m.confirmed {
m.confirmed = true
m.inProgress = true
}
return m, func() tea.Msg {
return DeployOp
}
case "n", "N":
if !m.confirmed {
m.confirmed = false
if !m.confirmed && !m.inProgress {
quitMsg := tea.Println("\n👋 Deployment cancelled")
return m, tea.Sequence(quitMsg, tea.Quit)
}
case "ctrl+c", "q":
return m, tea.Quit
case "l", "L":
if m.inProgress && !m.viewLogs {
m.viewLogs = true
m.isScrolling = false
} else if m.inProgress {
m.viewLogs = false
}
}

case operation:
Expand Down Expand Up @@ -245,21 +282,42 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
}

// keep this as Batch for now, spinner breaks if it's a Sequence
return m, tea.Batch(cmds...)
return m, tea.Sequence(cmds...)
}

func (m Model) View() string {

Check warning on line 288 in src/pkg/bundle/tui/tui.go

View workflow job for this annotation

GitHub Actions / validate

exported method Model.View should have comment or be unexported
line := strings.Repeat("─", WIDTH)
if m.done {
// clear the controlled Program's output
return ""
} else if m.viewLogs {
return fmt.Sprintf("%s\n%s", line, m.logView())
} else if m.confirmed {
line := strings.Repeat("─", WIDTH)
return fmt.Sprintf("%s\n%s", line, m.deployView())
}
return ""
}

func (m Model) logView() string {
boxStyle := lipgloss.NewStyle().
Width(75). // todo: make adaptive
Border(lipgloss.RoundedBorder()).
BorderForeground(lipgloss.Color("#874BFD")).
BorderTop(true).
BorderLeft(true).
BorderRight(true).
BorderBottom(true).
Align(lipgloss.Left)
subtle := lipgloss.AdaptiveColor{Light: "#D9DCCF", Dark: "#383838"}
box := lipgloss.Place(0, 3,
lipgloss.Right, lipgloss.Top,
boxStyle.Render(m.viewport.View()),
lipgloss.WithWhitespaceForeground(subtle),
)
ui := lipgloss.JoinHorizontal(lipgloss.Top, m.deployView())
return fmt.Sprintf("%s\n%s\n", ui, box)
}

func (m Model) deployView() string {
view := ""
for _, p := range m.packages {
Expand All @@ -275,13 +333,13 @@ func (m Model) deployView() string {

text := lipgloss.NewStyle().
Align(lipgloss.Left).
Padding(0, 3).
Padding(0, 0).
Render(fmt.Sprintf("%s Package %s deploying (%d / %d components)", p.spinner.View(), p.name, min(numComponentsSuccess+1, p.numComponents), p.numComponents))

if p.complete {
text = lipgloss.NewStyle().
Align(lipgloss.Left).
Padding(0, 3).
Padding(0, 0).
Render(fmt.Sprintf("✅ Package %s deployed", p.name))
}

Expand Down Expand Up @@ -354,7 +412,7 @@ func colorPrintYAML(yaml string) string {

outputYAML := p.PrintTokens(tokens)

if config.NoColor {
if zarfConfig.NoColor {
// If no color is specified strip any color codes from the output - https://regex101.com/r/YFyIwC/2
ansiRegex := regexp.MustCompile(`\x1b\[(.*?)m`)
outputYAML = ansiRegex.ReplaceAllString(outputYAML, "")
Expand Down
5 changes: 4 additions & 1 deletion src/pkg/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ func IsValidTarballPath(path string) bool {
}

// UseLogFile writes output to stderr and a logFile.
func UseLogFile() {
func UseLogFile() (string, error) {
// LogWriter is the stream to write logs to.
var LogWriter io.Writer

Expand All @@ -82,15 +82,18 @@ func UseLogFile() {
// Use the existing log file if logFile is set
LogWriter = io.MultiWriter(logFile)
pterm.SetDefaultOutput(LogWriter)
return logFile.Name(), nil
} else {
// Try to create a temp log file if one hasn't been made already
if logFile, err = os.CreateTemp("", fmt.Sprintf("uds-%s-*.log", ts)); err != nil {
message.WarnErr(err, "Error saving a log file to a temporary directory")
return "", err
} else {

Check warning on line 91 in src/pkg/utils/utils.go

View workflow job for this annotation

GitHub Actions / validate

if block ends with a return statement, so drop this else and outdent its block
LogWriter = io.MultiWriter(logFile)
pterm.SetDefaultOutput(LogWriter)
msg := fmt.Sprintf("Saving log file to %s", logFile.Name())
fmt.Println(msg)
return logFile.Name(), nil
}
}
}
Expand Down

0 comments on commit 66d6c68

Please sign in to comment.