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 #210

Closed
wants to merge 10 commits into from
10 changes: 5 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ go 1.21.6
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.1
github.com/defenseunicorns/zarf v0.32.4
github.com/fsnotify/fsnotify v1.7.0
Expand All @@ -19,6 +22,7 @@ require (
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225
golang.org/x/mod v0.16.0
golang.org/x/sync v0.6.0
golang.org/x/term v0.17.0
helm.sh/helm/v3 v3.14.2
oras.land/oras-go/v2 v2.4.0
)
Expand Down Expand Up @@ -86,7 +90,7 @@ require (
github.com/alibabacloud-go/tea-xml v1.1.3 // indirect
github.com/aliyun/credentials-go v1.3.1 // indirect
github.com/anchore/bubbly v0.0.0-20231115134915-def0aba654a9 // indirect
github.com/anchore/clio v0.0.0-20240209204744-cb94e40a4f65 // indirect
github.com/anchore/clio v0.0.0-20240301210832-abcb7197da84 // indirect
github.com/anchore/fangs v0.0.0-20231201140849-5075d28d6d8b // indirect
github.com/anchore/go-logger v0.0.0-20230725134548-c21dafa1ec5a // indirect
github.com/anchore/go-macholibre v0.0.0-20220308212642-53e6d0aaf6fb // indirect
Expand Down Expand Up @@ -138,10 +142,7 @@ require (
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/chai2010/gettext-go v1.0.2 // indirect
github.com/charmbracelet/bubbles v0.16.1 // indirect
github.com/charmbracelet/bubbletea v0.25.0 // indirect
github.com/charmbracelet/harmonica v0.2.0 // indirect
github.com/charmbracelet/lipgloss v0.9.1 // indirect
github.com/chrismellard/docker-credential-acr-env v0.0.0-20230304212654-82a0ddb27589 // indirect
github.com/clbanning/mxj/v2 v2.7.0 // indirect
github.com/cloudflare/circl v1.3.7 // indirect
Expand Down Expand Up @@ -460,7 +461,6 @@ require (
golang.org/x/net v0.21.0 // indirect
golang.org/x/oauth2 v0.16.0 // indirect
golang.org/x/sys v0.17.0 // indirect
golang.org/x/term v0.17.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.18.0 // indirect
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -364,8 +364,8 @@ github.com/aliyun/credentials-go v1.3.1 h1:uq/0v7kWrxmoLGpqjx7vtQ/s03f0zR//0br/x
github.com/aliyun/credentials-go v1.3.1/go.mod h1:8jKYhQuDawt8x2+fusqa1Y6mPxemTsBEN04dgcAcYz0=
github.com/anchore/bubbly v0.0.0-20231115134915-def0aba654a9 h1:p0ZIe0htYOX284Y4axJaGBvXHU0VCCzLN5Wf5XbKStU=
github.com/anchore/bubbly v0.0.0-20231115134915-def0aba654a9/go.mod h1:3ZsFB9tzW3vl4gEiUeuSOMDnwroWxIxJelOOHUp8dSw=
github.com/anchore/clio v0.0.0-20240209204744-cb94e40a4f65 h1:u9XrEabKlGPsrmRvAER+kUKkwXiJfLyqGhmOTFsXjX4=
github.com/anchore/clio v0.0.0-20240209204744-cb94e40a4f65/go.mod h1:8Jr7CjmwFVcBPtkJdTpaAGHimoGJGfbExypjzOu87Og=
github.com/anchore/clio v0.0.0-20240301210832-abcb7197da84 h1:z8C0o7URkxvuh6isbZbz3la5m6sINzIOmt6UfZ4ey1o=
github.com/anchore/clio v0.0.0-20240301210832-abcb7197da84/go.mod h1:XEh8WpqQSsNNZZjCZ6xWAk/DoNYfyISjUQ+Bv06xHEc=
github.com/anchore/fangs v0.0.0-20231201140849-5075d28d6d8b h1:L/djgY7ZbZ/38+wUtdkk398W3PIBJLkt1N8nU/7e47A=
github.com/anchore/fangs v0.0.0-20231201140849-5075d28d6d8b/go.mod h1:TLcE0RE5+8oIx2/NPWem/dq1DeaMoC+fPEH7hoSzPLo=
github.com/anchore/go-logger v0.0.0-20230725134548-c21dafa1ec5a h1:nJ2G8zWKASyVClGVgG7sfM5mwoZlZ2zYpIzN2OhjWkw=
Expand Down
28 changes: 25 additions & 3 deletions src/cmd/uds.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,18 @@ import (
"strings"

"github.com/AlecAivazis/survey/v2"
tea "github.com/charmbracelet/bubbletea"
"github.com/defenseunicorns/uds-cli/src/config"
"github.com/defenseunicorns/uds-cli/src/config/lang"
"github.com/defenseunicorns/uds-cli/src/pkg/bundle"
"github.com/defenseunicorns/uds-cli/src/pkg/bundle/tui"
zarfConfig "github.com/defenseunicorns/zarf/src/config"
"github.com/defenseunicorns/zarf/src/pkg/message"
zarfUtils "github.com/defenseunicorns/zarf/src/pkg/utils"
zarfTypes "github.com/defenseunicorns/zarf/src/types"
goyaml "github.com/goccy/go-yaml"
"github.com/spf13/cobra"
"golang.org/x/term"
)

var createCmd = &cobra.Command{
Expand Down Expand Up @@ -80,12 +83,29 @@ var deployCmd = &cobra.Command{
return
}
}
// create new bundle client
bndlClient := bundle.NewOrDie(&bundleCfg)
defer bndlClient.ClearPaths()

if err := bndlClient.Deploy(); err != nil {
bndlClient.ClearPaths()
message.Fatalf(err, "Failed to deploy bundle: %s", err.Error())
// pre-deploy validation
bundleYAML := ""
bundleYAML, err := bndlClient.PreDeployValidation()
if err != nil {
return
}

// start up bubbletea
m := tui.InitModel(bndlClient, bundleYAML)

// detect tty so CI/containers don't break
if term.IsTerminal(int(os.Stdout.Fd())) {
tui.Program = tea.NewProgram(&m)
} else {
tui.Program = tea.NewProgram(&m, tea.WithInput(nil))
}

if _, err := tui.Program.Run(); err != nil {
message.Fatalf(err, "TUI program error: %s", err.Error())
}
},
}
Expand Down Expand Up @@ -257,6 +277,8 @@ func configureZarf() {
Confirm: config.CommonOptions.Confirm,
CachePath: config.CommonOptions.CachePath, // use uds-cache instead of zarf-cache
}
// when using bubbletea, disable progress bars in pterm
message.NoProgress = true
}

// chooseBundle provides a file picker when users don't specify a file
Expand Down
134 changes: 69 additions & 65 deletions src/pkg/bundle/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,15 @@ import (

"github.com/AlecAivazis/survey/v2"
"github.com/defenseunicorns/uds-cli/src/config"
"github.com/defenseunicorns/uds-cli/src/pkg/bundle/tui"
"github.com/defenseunicorns/uds-cli/src/pkg/sources"
"github.com/defenseunicorns/uds-cli/src/types"
zarfConfig "github.com/defenseunicorns/zarf/src/config"
"github.com/defenseunicorns/zarf/src/pkg/message"
"github.com/defenseunicorns/zarf/src/pkg/packager"
"github.com/defenseunicorns/zarf/src/pkg/utils"
zarfTypes "github.com/defenseunicorns/zarf/src/types"
"github.com/pterm/pterm"
goyaml "github.com/goccy/go-yaml"
"golang.org/x/exp/slices"
"helm.sh/helm/v3/pkg/cli/values"
"helm.sh/helm/v3/pkg/getter"
Expand All @@ -36,65 +37,8 @@ var templatedVarRegex = regexp.MustCompile(`\${([^}]+)}`)

// Deploy deploys a bundle
func (b *Bundle) Deploy() error {
ctx := context.TODO()

pterm.Println()
metadataSpinner := message.NewProgressSpinner("Loading bundle metadata")

defer metadataSpinner.Stop()

// Check that provided oci source path is valid, and update it if it's missing the full path
source, err := CheckOCISourcePath(b.cfg.DeployOpts.Source)
if err != nil {
return err
}
b.cfg.DeployOpts.Source = source

// validate config's arch against cluster
err = ValidateArch(config.GetArch())
if err != nil {
return err
}

// create a new provider
provider, err := NewBundleProvider(ctx, b.cfg.DeployOpts.Source, b.tmp)
if err != nil {
return err
}

// pull the bundle's metadata + sig
loaded, err := provider.LoadBundleMetadata()
if err != nil {
return err
}

// validate the sig (if present)
if err := ValidateBundleSignature(loaded[config.BundleYAML], loaded[config.BundleYAMLSignature], b.cfg.DeployOpts.PublicKeyPath); err != nil {
return err
}

// read the bundle's metadata into memory
// todo: we also read the SHAs from the uds-bundle.yaml here, should we refactor so that we use the bundle's root manifest?
if err := utils.ReadYaml(loaded[config.BundleYAML], &b.bundle); err != nil {
return err
}

metadataSpinner.Successf("Loaded bundle metadata")

// confirm deploy
if ok := b.confirmBundleDeploy(); !ok {
return fmt.Errorf("bundle deployment cancelled")
}

// Check if --resume is set
resume := b.cfg.DeployOpts.Resume

// Maps name given to zarf package in the bundle to the actual name of the zarf package
zarfPackageNameMap, err := provider.ZarfPackageNameMap()
if err != nil {
return err
}

// Check if --packages flag is set and zarf packages have been specified
var packagesToDeploy []types.Package
if len(b.cfg.DeployOpts.Packages) != 0 {
Expand All @@ -110,13 +54,13 @@ func (b *Bundle) Deploy() error {
if len(userSpecifiedPackages) != len(packagesToDeploy) {
return fmt.Errorf("invalid zarf packages specified by --packages")
}
return deployPackages(packagesToDeploy, resume, b, zarfPackageNameMap)
return deployPackages(packagesToDeploy, resume, b)
}

return deployPackages(b.bundle.Packages, resume, b, zarfPackageNameMap)
return deployPackages(b.bundle.Packages, resume, b)
}

func deployPackages(packages []types.Package, resume bool, b *Bundle, zarfPackageNameMap map[string]string) error {
func deployPackages(packages []types.Package, resume bool, b *Bundle) error {
// map of Zarf pkgs and their vars
bundleExportedVars := make(map[string]map[string]string)

Expand All @@ -133,8 +77,11 @@ func deployPackages(packages []types.Package, resume bool, b *Bundle, zarfPackag
packagesToDeploy = packages
}

// let TUI know how many packages are being deployed
tui.Program.Send(fmt.Sprintf("totalPackages:%d", len(b.bundle.Packages)))

// deploy each package
for _, pkg := range packagesToDeploy {
for i, pkg := range packagesToDeploy {
sha := strings.Split(pkg.Ref, "@sha256:")[1] // using appended SHA from create!
pkgTmp, err := utils.MakeTempDir(config.CommonOptions.TempDirectory)
if err != nil {
Expand Down Expand Up @@ -180,7 +127,7 @@ func deployPackages(packages []types.Package, resume bool, b *Bundle, zarfPackag
// Automatically confirm the package deployment
zarfConfig.CommonOptions.Confirm = true

source, err := sources.New(b.cfg.DeployOpts.Source, zarfPackageNameMap[pkg.Name], opts, sha)
source, err := sources.New(b.cfg.DeployOpts.Source, b.cfg.DeployOpts.ZarfPackageNameMap[pkg.Name], opts, sha)
if err != nil {
return err
}
Expand All @@ -189,10 +136,15 @@ func deployPackages(packages []types.Package, resume bool, b *Bundle, zarfPackag
if err != nil {
return err
}

tui.Program.Send(fmt.Sprintf("package:%s:%d", pkg.Name, i))

if err := pkgClient.Deploy(); err != nil {
return err
}

tui.Program.Send(fmt.Sprintf("complete:%d", i))

// save exported vars
pkgExportedVars := make(map[string]string)
for _, exp := range pkg.Exports {
Expand Down Expand Up @@ -271,8 +223,6 @@ func (b *Bundle) confirmBundleDeploy() (confirm bool) {
Message: "Deploy this bundle?",
}

pterm.Println()

if err := survey.AskOne(prompt, &confirm); err != nil || !confirm {
return false
}
Expand Down Expand Up @@ -330,6 +280,60 @@ func (b *Bundle) loadChartOverrides(pkg types.Package, pkgVars map[string]string
return processed, nil
}

func (b *Bundle) PreDeployValidation() (string, error) {
ctx := context.TODO()

// Check that provided oci source path is valid, and update it if it's missing the full path
source, err := CheckOCISourcePath(b.cfg.DeployOpts.Source)
if err != nil {
return "", err
}
b.cfg.DeployOpts.Source = source

// validate config's arch against cluster
err = ValidateArch(config.GetArch())
if err != nil {
return "", err
}

// create a new provider
provider, err := NewBundleProvider(ctx, b.cfg.DeployOpts.Source, b.tmp)
if err != nil {
return "", err
}

// pull the bundle's metadata + sig
loaded, err := provider.LoadBundleMetadata()
if err != nil {
return "", err
}

// validate the sig (if present)
if err := ValidateBundleSignature(loaded[config.BundleYAML], loaded[config.BundleYAMLSignature], b.cfg.DeployOpts.PublicKeyPath); err != nil {
return "", err
}

// read in file at config.BundleYAML
message.Debugf("Reading YAML at %s", loaded[config.BundleYAML])
bundleYAML, err := os.ReadFile(loaded[config.BundleYAML])
if err != nil {
return "", err
}

// todo: we also read the SHAs from the uds-bundle.yaml here, should we refactor so that we use the bundle's root manifest?
if err := goyaml.Unmarshal(bundleYAML, &b.bundle); err != nil {
return "", err
}

// Maps name given to zarf package in the bundle to the actual name of the zarf package
zarfPackageNameMap, err := provider.ZarfPackageNameMap()
if err != nil {
return "", err
}
b.cfg.DeployOpts.ZarfPackageNameMap = zarfPackageNameMap
return string(bundleYAML), err
}

// processOverrideValues processes a bundles values overrides and adds them to the override map
func (b *Bundle) processOverrideValues(overrideMap *map[string]map[string]*values.Options, values *[]types.BundleChartValue, componentName string, chartName string, pkgVars map[string]string) error {
for _, v := range *values {
Expand Down
Loading