Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: beautiful TUI round 3 #509

Merged
merged 7 commits into from
Mar 25, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
Prev Previous commit
updates Zarf to 0.32.6
  • Loading branch information
UncleGedd committed Mar 25, 2024
commit 4613dd378877a4215302659c9198e3127198267c
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
- [Inspect](#bundle-inspect)
- [Publish](#bundle-publish)
- [Remove](#bundle-remove)
- [Logs](#logs)
1. [Bundle Architecture and Multi-Arch Support](#bundle-architecture-and-multi-arch-support)
1. [Configuration](#configuration)
1. [Sharing Variables](#sharing-variables)
Expand Down Expand Up @@ -127,6 +128,13 @@ By default all the packages in the bundle are removed, but you can also remove o

As an example: `uds remove uds-bundle-<name>.tar.zst --packages init,nginx`

### Logs

> [!NOTE]
> Only works with `uds deploy` for now, may work for other operations but isn't guaranteed.

The `uds logs` command can be used to view the most recent logs of a bundle operation. Note that depending on your OS temporary directory and file settings, recent logs are purged after a certain amount of time, so this command may return an error if the logs are no longer available.

## Bundle Architecture and Multi-Arch Support
There are several ways to specify the architecture of a bundle:
1. Setting `--architecture` or `-a` flag during `uds ...` operations: `uds create <dir> --architecture arm64`
Expand Down
10 changes: 3 additions & 7 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,14 @@ module github.com/defenseunicorns/uds-cli

go 1.21.6

replace github.com/defenseunicorns/zarf v0.32.5 => github.com/defenseunicorns/zarf v0.32.6-0.20240321202634-329a1ae1f69c

replace github.com/defenseunicorns/maru-runner v0.0.2 => github.com/defenseunicorns/maru-runner v0.0.3-0.20240322171619-a7974e34d1d0

require (
github.com/AlecAivazis/survey/v2 v2.3.7
github.com/alecthomas/jsonschema v0.0.0-20220216202328-9eeeec9d044b
github.com/charmbracelet/bubbles v0.16.1
github.com/charmbracelet/bubbletea v0.25.0
github.com/charmbracelet/lipgloss v0.9.1
github.com/defenseunicorns/maru-runner v0.0.2
github.com/defenseunicorns/zarf v0.32.5
github.com/defenseunicorns/maru-runner v0.1.0
github.com/defenseunicorns/zarf v0.32.6
github.com/fatih/color v1.16.0
github.com/fsnotify/fsnotify v1.7.0
github.com/goccy/go-yaml v1.11.3
Expand Down Expand Up @@ -180,7 +176,7 @@ require (
github.com/distribution/reference v0.5.0 // indirect
github.com/docker/cli v24.0.9+incompatible // indirect
github.com/docker/distribution v2.8.3+incompatible // indirect
github.com/docker/docker v24.0.7+incompatible // indirect
github.com/docker/docker v24.0.9+incompatible // indirect
github.com/docker/docker-credential-helpers v0.8.0 // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect
Expand Down
12 changes: 6 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -598,10 +598,10 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/daviddengcn/go-colortext v1.0.0 h1:ANqDyC0ys6qCSvuEK7l3g5RaehL/Xck9EX8ATG8oKsE=
github.com/daviddengcn/go-colortext v1.0.0/go.mod h1:zDqEI5NVUop5QPpVJUxE9UO10hRnmkD5G4Pmri9+m4c=
github.com/defenseunicorns/maru-runner v0.0.3-0.20240322171619-a7974e34d1d0 h1:HgtK7fLR1+3PeEgZlykkN40hfngrAZrY2Oc1gv3Enas=
github.com/defenseunicorns/maru-runner v0.0.3-0.20240322171619-a7974e34d1d0/go.mod h1:hyQyYIFMb1b284uVEIbhc9qySyVCDbYcjteniHuNKE0=
github.com/defenseunicorns/zarf v0.32.6-0.20240321202634-329a1ae1f69c h1:EtW8FLWEJ8ducXN7Y1Z7kAmLGuVeicMOSLknYcvV/Q8=
github.com/defenseunicorns/zarf v0.32.6-0.20240321202634-329a1ae1f69c/go.mod h1:EISjg8T4jZUgc1QAMkX/RNQ0PS4WegKcJDZwztvQ2Bw=
github.com/defenseunicorns/maru-runner v0.1.0 h1:LCrMzA+TaQMV2LdvMVrd319jDAIL9CuwhIes0vKf7Kg=
github.com/defenseunicorns/maru-runner v0.1.0/go.mod h1:/0Qu2O8WyZuYB+6LkUsgUwuHiQS8AgXU/Tw7Re0GaTk=
github.com/defenseunicorns/zarf v0.32.6 h1:zWfKmAo898P7OHosKQaiV4E6YjDrqpsryz0BqrQqgg4=
github.com/defenseunicorns/zarf v0.32.6/go.mod h1:EISjg8T4jZUgc1QAMkX/RNQ0PS4WegKcJDZwztvQ2Bw=
github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da h1:ZOjWpVsFZ06eIhnh4mkaceTiVoktdU67+M7KDHJ268M=
github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da/go.mod h1:B3tI9iGHi4imdLi4Asdha1Sc6feLMTfPLXh9IUYmysk=
github.com/depcheck-test/depcheck-test v0.0.0-20220607135614-199033aaa936 h1:foGzavPWwtoyBvjWyKJYDYsyzy+23iBV7NKTwdk+LRY=
Expand Down Expand Up @@ -638,8 +638,8 @@ github.com/docker/cli v24.0.9+incompatible h1:OxbimnP/z+qVjDLpq9wbeFU3Nc30XhSe+L
github.com/docker/cli v24.0.9+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM=
github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v24.0.9+incompatible h1:HPGzNmwfLZWdxHqK9/II92pyi1EpYKsAqcl4G0Of9v0=
github.com/docker/docker v24.0.9+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker-credential-helpers v0.8.0 h1:YQFtbBQb4VrpoPxhFuzEBPQ9E16qz5SpHLS+uswaCp8=
github.com/docker/docker-credential-helpers v0.8.0/go.mod h1:UGFXcuoQ5TxPiB54nHOZ32AWRqQdECoh/Mg0AlEYb40=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
Expand Down
6 changes: 6 additions & 0 deletions src/pkg/bundle/tui/common.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023-Present The UDS Authors

package tui

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

View workflow job for this annotation

GitHub Actions / validate

should have a package comment

import (
"time"

tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
)

var (
// IndentStyle is the style for indenting text
IndentStyle = lipgloss.NewStyle().Padding(0, 4)
)

func Pause() tea.Cmd {

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

View workflow job for this annotation

GitHub Actions / validate

exported function Pause should have comment or be unexported
return tea.Tick(time.Millisecond*500, func(_ time.Time) tea.Msg {
return nil
})
Expand Down
34 changes: 13 additions & 21 deletions src/pkg/bundle/tui/deploy/handlers.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023-Present The UDS Authors

package deploy

Check warning on line 4 in src/pkg/bundle/tui/deploy/handlers.go

View workflow job for this annotation

GitHub Actions / validate

should have a package comment

import (
"fmt"
Expand All @@ -18,7 +18,7 @@
zarfTypes "github.com/defenseunicorns/zarf/src/types"
)

func (m *Model) handleNewPackage(pkgName string, currentPkgIdx int) tea.Cmd {
func (m *model) handleNewPackage(pkgName string, currentPkgIdx int) tea.Cmd {
// see if pkg has already been deployed
deployedPkg, _ := c.GetDeployedPackage(pkgName)
newPkg := pkgState{
Expand Down Expand Up @@ -49,19 +49,14 @@
downloadSpinner.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("205"))
newPkg.downloadSpinner = downloadSpinner

// create spinner to track total bundle progress
bundleSpinner := spinner.New()
bundleSpinner.Spinner = spinner.Ellipsis
bundleSpinner.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("205"))

m.packages = append(m.packages, newPkg)
return tea.Batch(m.packages[m.pkgIdx].deploySpinner.Tick,
m.packages[m.pkgIdx].verifySpinner.Tick,
m.packages[m.pkgIdx].downloadSpinner.Tick,
)
}

func (m *Model) handlePreDeploy() tea.Cmd {
func (m *model) handlePreDeploy() tea.Cmd {
cmd := func() tea.Msg {
name, bundleYAML, source, err := m.bndlClient.PreDeployValidation()
if err != nil {
Expand All @@ -80,7 +75,7 @@
return cmd
}

func (m *Model) handleDeploy() tea.Cmd {
func (m *model) handleDeploy() tea.Cmd {
// ensure bundle deployment is confirmed and is only being deployed once
if m.confirmed && !m.deploying {
// run Deploy concurrently so we can update the TUI while it runs
Expand All @@ -102,27 +97,26 @@
return nil
}

func (m *Model) handleDone(err error) tea.Cmd {
//uds := " __ ______ _____\n / / / / __ \\/ ___/\n / / / / / / /\\__ \\ \n/ /_/ / /_/ /___/ / \n\\____/_____//____/ \n \n"
func (m *model) handleDone(err error) tea.Cmd {
cmds := []tea.Cmd{tea.Println(), tea.Println(m.udsTitle()), tea.Println()}
m.done = true // remove the current view
cmds = append(cmds, genSuccessOrFailCmds(m)...)
cmds = append(cmds, genSuccessCmds(m)...)
if err != nil {
hint := lightBlueText.Render("uds logs")
message.Debug(err) // capture err in debug logs
errMsg := lipgloss.NewStyle().Padding(0, 4).Render(fmt.Sprintf("\n❌ Error deploying bundle: %s\n\nRun %s to view deployment logs", lightGrayText.Render(err.Error()), hint) + "\n")
errMsg := tui.IndentStyle.Render(fmt.Sprintf("\n❌ Error deploying bundle: %s\n\nRun %s to view deployment logs", lightGrayText.Render(err.Error()), hint) + "\n")
cmds = []tea.Cmd{tea.Println(errMsg), tui.Pause(), tea.Quit}
return tea.Sequence(cmds...)
}
styledBundleName := lipgloss.NewStyle().Foreground(lipgloss.Color("#FFF258")).Render(m.bundleName)
successMsg := tea.Println(lipgloss.NewStyle().
Padding(0, 4).
Render(fmt.Sprintf("\n✨ Bundle %s deployed successfully\n", styledBundleName)))
successMsg := tea.Println(
tui.IndentStyle.
Render(fmt.Sprintf("\n✨ Bundle %s deployed successfully\n", styledBundleName)))
cmds = append(cmds, successMsg, tui.Pause(), tea.Quit)
return tea.Sequence(cmds...)
}

func (m *Model) handleDeployTick() (tea.Model, tea.Cmd) {
func (m *model) handleDeployTick() (tea.Model, tea.Cmd) {
// check if all pkgs are complete
numComplete := 0
if len(m.packages) == m.totalPkgs {
Expand Down Expand Up @@ -152,10 +146,10 @@
if deployedPkg == nil {
break
}
// handle upgrade scenario by resetting the progress bar, otherwise increment it
// handle upgrade scenario by resetting the component progress, otherwise increment it
if p.resetProgress {
// if upgraded len(deployedPkg.DeployedComponents) will be equal to the number of components in the package
if deployedPkg != nil && len(deployedPkg.DeployedComponents) == 1 {
if deployedPkg != nil && len(deployedPkg.DeployedComponents) > 0 {
m.packages[i].resetProgress = false
}
break
Expand All @@ -172,9 +166,7 @@
// always update logViewport content with logs
file, _ := os.ReadFile(config.LogFileName)
m.logViewport.SetContent(string(file))
if !m.isScrolling {
m.logViewport.GotoBottom()
}
m.logViewport.GotoBottom()

return m, tickCmd()
}
42 changes: 18 additions & 24 deletions src/pkg/bundle/tui/deploy/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/defenseunicorns/uds-cli/src/config"
"github.com/defenseunicorns/uds-cli/src/pkg/bundle/tui"
"github.com/defenseunicorns/zarf/src/pkg/cluster"
"golang.org/x/term"
)
Expand All @@ -34,11 +35,10 @@
)

var (
Program *tea.Program

Check warning on line 38 in src/pkg/bundle/tui/deploy/model.go

View workflow job for this annotation

GitHub Actions / validate

exported var Program should have comment or be unexported
c *cluster.Cluster
logVpWidthScale = 0.9
logVpHeightScale = 0.4
lineWidthScale = 0.75
)

// private interface to decouple tui pkg from bundle pkg
Expand All @@ -52,20 +52,20 @@
type pkgState struct {
name string
numComponents int
percLayersVerified int64
percLayersVerified int
componentStatuses []bool
deploySpinner spinner.Model
downloadSpinner spinner.Model
verifySpinner spinner.Model
complete bool
resetProgress bool
percDownloaded int64
percDownloaded int
downloaded bool
verified bool
isRemote bool
}

type Model struct {
type model struct {
bndlClient bndlClientShim
bundleYAML string
doneChan chan int
Expand All @@ -78,7 +78,6 @@
inProgress bool
viewLogs bool
logViewport viewport.Model
isScrolling bool
errChan chan error
yamlViewport viewport.Model
isRemoteBundle bool
Expand All @@ -87,7 +86,7 @@
validatingBundleSpinner spinner.Model
}

func InitModel(client bndlClientShim) Model {
func InitModel(client bndlClientShim) model {

Check warning on line 89 in src/pkg/bundle/tui/deploy/model.go

View workflow job for this annotation

GitHub Actions / validate

exported func InitModel returns unexported type deploy.model, which can be annoying to use

Check warning on line 89 in src/pkg/bundle/tui/deploy/model.go

View workflow job for this annotation

GitHub Actions / validate

exported function InitModel should have comment or be unexported
var confirmed bool
var inProgress bool
var isRemoteBundle bool
Expand Down Expand Up @@ -116,16 +115,12 @@

// set up logViewport for logs, adjust width and height of logViewport
logViewport := viewport.New(int(float64(termWidth)*logVpWidthScale), int(float64(termHeight)*logVpHeightScale))
logViewport.MouseWheelEnabled = true
logViewport.MouseWheelDelta = 1

// set up yamlViewport to ensure the preDeploy YAML is scrollable
numYamlLines := 10
yamlViewport := viewport.New(termWidth, numYamlLines)
yamlViewport.MouseWheelEnabled = true
yamlViewport.MouseWheelDelta = 1
numYAMLLines := 10
yamlViewport := viewport.New(termWidth, numYAMLLines)

return Model{
return model{
bndlClient: client,
doneChan: make(chan int),
errChan: make(chan error),
Expand All @@ -139,13 +134,13 @@
}
}

func (m *Model) Init() tea.Cmd {
func (m *model) Init() tea.Cmd {
return func() tea.Msg {
return doPreDeploy
}
}

func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
select {
case err := <-m.errChan:
cmd := m.handleDone(err)
Expand Down Expand Up @@ -202,7 +197,7 @@
case "n", "N":
if !m.confirmed && !m.inProgress {
m.done = true
quitMsg := tea.Println("\n👋 Deployment cancelled")
quitMsg := tea.Println(tui.IndentStyle.Render("\n👋 Deployment cancelled"))
return m, tea.Sequence(quitMsg, tea.Println(), tea.Quit)
}
case "ctrl+c", "q":
Expand All @@ -220,7 +215,6 @@
case "l", "L":
if m.inProgress && !m.viewLogs {
m.viewLogs = true
m.isScrolling = false
} else if m.inProgress {
m.viewLogs = false
}
Expand All @@ -247,23 +241,23 @@
cmd := m.handleNewPackage(pkgName, pkgIdx)
return m, cmd
case totalComponents:
if totalComponents, err := strconv.Atoi(strings.Split(msg, ":")[1]); err == nil {
m.packages[m.pkgIdx].numComponents = totalComponents
m.packages[m.pkgIdx].componentStatuses = make([]bool, totalComponents)
if tc, err := strconv.Atoi(strings.Split(msg, ":")[1]); err == nil {
m.packages[m.pkgIdx].numComponents = tc
m.packages[m.pkgIdx].componentStatuses = make([]bool, tc)
}
case totalPackages:
if totalPkgs, err := strconv.Atoi(strings.Split(msg, ":")[1]); err == nil {
m.totalPkgs = totalPkgs
}
case verifying:
if perc, err := strconv.ParseInt(strings.Split(msg, ":")[1], 10, 8); err == nil {
if perc, err := strconv.Atoi(strings.Split(msg, ":")[1]); err == nil {
m.packages[m.pkgIdx].percLayersVerified = perc
if perc == 100 {
m.packages[m.pkgIdx].verified = true
}
}
case downloading:
if perc, err := strconv.ParseInt(strings.Split(msg, ":")[1], 10, 8); err == nil {
if perc, err := strconv.Atoi(strings.Split(msg, ":")[1]); err == nil {
m.packages[m.pkgIdx].percDownloaded = perc
if perc == 100 {
m.packages[m.pkgIdx].downloaded = true
Expand All @@ -279,18 +273,18 @@
return m, nil
}

func (m *Model) View() string {
func (m *model) View() string {
if m.done {
// no errors, clear the controlled Program's output
return ""
} else if m.validatingBundle {
validatingBundleMsg := lightGrayText.Render("Validating bundle")
return lipgloss.NewStyle().Padding(0, 4).Render(fmt.Sprintf("\n%s %s", validatingBundleMsg, m.validatingBundleSpinner.View()))
return tui.IndentStyle.Render(fmt.Sprintf("\n%s %s", validatingBundleMsg, m.validatingBundleSpinner.View()))
} else if m.viewLogs {
return fmt.Sprintf("\n%s\n\n%s\n%s\n\n%s\n", m.udsTitle(), m.bundleDeployProgress(), logMsg, m.logView())
} else if m.confirmed {
return fmt.Sprintf("\n%s\n\n%s\n%s\n%s\n", m.udsTitle(), m.bundleDeployProgress(), logMsg, m.deployView())
} else {

Check warning on line 287 in src/pkg/bundle/tui/deploy/model.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
return fmt.Sprintf("%s\n", m.preDeployView())
}
}
Loading
Loading