Skip to content

Commit

Permalink
Port UI to bubbletea (#1385)
Browse files Browse the repository at this point in the history
* initial port to bubbletea

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* remove jotframe UI

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* add bubbletea component tests

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* update main.go refs to cmd package

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* move goreleaser build dir to cmd

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* upgrade yardstick for grype source installs and fix post-ui tests

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* ensure stable severity map in UI component test

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* add windows support for tui

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
  • Loading branch information
wagoodman authored Jul 13, 2023
1 parent 37f436c commit ebd4643
Showing 70 changed files with 1,654 additions and 848 deletions.
3 changes: 3 additions & 0 deletions .goreleaser.yaml
Original file line number Diff line number Diff line change
@@ -9,6 +9,7 @@ env:

builds:
- id: linux-build
dir: ./cmd/grype
binary: grype
goos:
- linux
@@ -29,6 +30,7 @@ builds:
-X github.com/anchore/grype/internal/version.gitDescription={{.Summary}}

- id: darwin-build
dir: ./cmd/grype
binary: grype
goos:
- darwin
@@ -44,6 +46,7 @@ builds:
- QUILL_LOG_FILE=/tmp/quill-{{ .Target }}.log

- id: windows-build
dir: ./cmd/grype
binary: grype
goos:
- windows
4 changes: 2 additions & 2 deletions DEVELOPING.md
Original file line number Diff line number Diff line change
@@ -6,7 +6,7 @@ There are a few useful things to know before diving into the codebase. This proj

After cloning do the following:

1. run `go build main.go` to get a binary named `main` from the source (use `-o <name>` to get a differently named binary), or optionally `go run main.go` to run from source.
1. run `go build ./cmd/grype` to get a binary named `main` from the source (use `-o <name>` to get a differently named binary), or optionally `go run ./cmd/grype` to run from source.

In order to run tests and build all artifacts:

@@ -31,7 +31,7 @@ to a released version (e.g. `go get github.com/anchore/syft@v<semantic-version>`
The currently supported database format is Sqlite3. Install `sqlite3` in your system and ensure that the `sqlite3` executable is available in your path. Ask `grype` about the location of the database, which will be different depending on the operating system:

```
$ go run main.go db status
$ go run ./cmd/grype db status
Location: /Users/alfredo/Library/Caches/grype/db
Built: 2020-07-31 08:18:29 +0000 UTC
Current DB Version: 1
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -446,7 +446,7 @@ Find complete information on Grype's database commands by running `grype db --he
Grype supplies shell completion through its CLI implementation ([cobra](https://github.com/spf13/cobra/blob/master/shell_completions.md)). Generate the completion code for your shell by running one of the following commands:

- `grype completion <bash|zsh|fish>`
- `go run main.go completion <bash|zsh|fish>`
- `go run ./cmd/grype completion <bash|zsh|fish>`

This will output a shell script to STDOUT, which can then be used as a completion script for Grype. Running one of the above commands with the
`-h` or `--help` flags will provide instructions on how to do that for your chosen shell.
2 changes: 1 addition & 1 deletion cmd/cmd.go → cmd/grype/cli/legacy/cmd.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package cmd
package legacy

import (
"encoding/json"
2 changes: 1 addition & 1 deletion cmd/completion.go → cmd/grype/cli/legacy/completion.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package cmd
package legacy

import (
"context"
2 changes: 1 addition & 1 deletion cmd/db.go → cmd/grype/cli/legacy/db.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package cmd
package legacy

import (
"github.com/spf13/cobra"
2 changes: 1 addition & 1 deletion cmd/db_check.go → cmd/grype/cli/legacy/db_check.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package cmd
package legacy

import (
"fmt"
2 changes: 1 addition & 1 deletion cmd/db_delete.go → cmd/grype/cli/legacy/db_delete.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package cmd
package legacy

import (
"fmt"
27 changes: 10 additions & 17 deletions cmd/db_diff.go → cmd/grype/cli/legacy/db_diff.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
package cmd
package legacy

import (
"fmt"
"os"
"strings"

"github.com/spf13/cobra"

"github.com/anchore/grype/cmd/grype/internal/ui"
"github.com/anchore/grype/grype/db"
"github.com/anchore/grype/grype/differ"
"github.com/anchore/grype/internal/bus"
"github.com/anchore/grype/internal/log"
"github.com/anchore/grype/internal/ui"
"github.com/anchore/stereoscope"
)

@@ -59,15 +58,19 @@ func startDBDiffCmd(base string, target string, deleteDatabases bool) <-chan err
return
}

sb := &strings.Builder{}

if len(*diff) == 0 {
fmt.Println("Databases are identical!")
sb.WriteString("Databases are identical!\n")
} else {
err := d.Present(dbDiffOutputFormat, diff, os.Stdout)
err := d.Present(dbDiffOutputFormat, diff, sb)
if err != nil {
errs <- err
}
}

bus.Report(sb.String())

if deleteDatabases {
errs <- d.DeleteDatabases()
}
@@ -76,16 +79,6 @@ func startDBDiffCmd(base string, target string, deleteDatabases bool) <-chan err
}

func runDBDiffCmd(cmd *cobra.Command, args []string) error {
reporter, closer, err := reportWriter()
defer func() {
if err := closer(); err != nil {
log.Warnf("unable to write to report destination: %+v", err)
}
}()
if err != nil {
return err
}

deleteDatabases, err := cmd.Flags().GetBool(deleteFlag)
if err != nil {
return err
@@ -117,7 +110,7 @@ func runDBDiffCmd(cmd *cobra.Command, args []string) error {
setupSignals(),
eventSubscription,
stereoscope.Cleanup,
ui.Select(isVerbose(), appConfig.Quiet, reporter)...,
ui.Select(isVerbose(), appConfig.Quiet)...,
)
}

2 changes: 1 addition & 1 deletion cmd/db_import.go → cmd/grype/cli/legacy/db_import.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package cmd
package legacy

import (
"fmt"
2 changes: 1 addition & 1 deletion cmd/db_list.go → cmd/grype/cli/legacy/db_list.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package cmd
package legacy

import (
"encoding/json"
2 changes: 1 addition & 1 deletion cmd/db_status.go → cmd/grype/cli/legacy/db_status.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package cmd
package legacy

import (
"fmt"
16 changes: 3 additions & 13 deletions cmd/db_update.go → cmd/grype/cli/legacy/db_update.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
package cmd
package legacy

import (
"fmt"

"github.com/spf13/cobra"

"github.com/anchore/grype/cmd/grype/internal/ui"
"github.com/anchore/grype/grype/db"
"github.com/anchore/grype/internal/bus"
"github.com/anchore/grype/internal/log"
"github.com/anchore/grype/internal/ui"
"github.com/anchore/stereoscope"
)

@@ -50,20 +49,11 @@ func startDBUpdateCmd() <-chan error {
}

func runDBUpdateCmd(_ *cobra.Command, _ []string) error {
reporter, closer, err := reportWriter()
defer func() {
if err := closer(); err != nil {
log.Warnf("unable to write to report destination: %+v", err)
}
}()
if err != nil {
return err
}
return eventLoop(
startDBUpdateCmd(),
setupSignals(),
eventSubscription,
stereoscope.Cleanup,
ui.Select(isVerbose(), appConfig.Quiet, reporter)...,
ui.Select(isVerbose(), appConfig.Quiet)...,
)
}
14 changes: 7 additions & 7 deletions cmd/event_loop.go → cmd/grype/cli/legacy/event_loop.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package cmd
package legacy

import (
"errors"
@@ -8,20 +8,20 @@ import (
"github.com/hashicorp/go-multierror"
"github.com/wagoodman/go-partybus"

"github.com/anchore/clio"
"github.com/anchore/grype/internal/log"
"github.com/anchore/grype/internal/ui"
)

// eventLoop listens to worker errors (from execution path), worker events (from a partybus subscription), and
// signal interrupts. Is responsible for handling each event relative to a given UI an to coordinate eventing until
// an eventual graceful exit.
func eventLoop(workerErrs <-chan error, signals <-chan os.Signal, subscription *partybus.Subscription, cleanupFn func(), uxs ...ui.UI) error {
func eventLoop(workerErrs <-chan error, signals <-chan os.Signal, subscription *partybus.Subscription, cleanupFn func(), uxs ...clio.UI) error {
defer cleanupFn()
events := subscription.Events()
var err error
var ux ui.UI
var ux clio.UI

if ux, err = setupUI(subscription.Unsubscribe, uxs...); err != nil {
if ux, err = setupUI(subscription, uxs...); err != nil {
return err
}

@@ -85,9 +85,9 @@ func eventLoop(workerErrs <-chan error, signals <-chan os.Signal, subscription *
// during teardown. With the given UIs, the first UI which the ui.Setup() function does not return an error
// will be utilized in execution. Providing a set of UIs allows for the caller to provide graceful fallbacks
// when there are environmental problem (e.g. unable to setup a TUI with the current TTY).
func setupUI(unsubscribe func() error, uis ...ui.UI) (ui.UI, error) {
func setupUI(subscription *partybus.Subscription, uis ...clio.UI) (clio.UI, error) {
for _, ux := range uis {
if err := ux.Setup(unsubscribe); err != nil {
if err := ux.Setup(subscription); err != nil {
log.Warnf("unable to setup given UI, falling back to alternative UI: %+v", err)
continue
}
37 changes: 20 additions & 17 deletions cmd/event_loop_test.go → cmd/grype/cli/legacy/event_loop_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package cmd
package legacy

import (
"fmt"
@@ -11,39 +11,42 @@ import (
"github.com/stretchr/testify/mock"
"github.com/wagoodman/go-partybus"

"github.com/anchore/clio"
"github.com/anchore/grype/grype/event"
"github.com/anchore/grype/internal/ui"
)

var _ ui.UI = (*uiMock)(nil)
var _ clio.UI = (*uiMock)(nil)

type uiMock struct {
t *testing.T
finalEvent partybus.Event
unsubscribe func() error
t *testing.T
finalEvent partybus.Event
subscription partybus.Unsubscribable
mock.Mock
}

func (u *uiMock) Setup(unsubscribe func() error) error {
func (u *uiMock) Setup(unsubscribe partybus.Unsubscribable) error {
u.t.Helper()
u.t.Logf("UI Setup called")
u.unsubscribe = unsubscribe
return u.Called(unsubscribe).Error(0)
u.subscription = unsubscribe
return u.Called(unsubscribe.Unsubscribe).Error(0)
}

func (u *uiMock) Handle(event partybus.Event) error {
u.t.Helper()
u.t.Logf("UI Handle called: %+v", event.Type)
if event == u.finalEvent {
assert.NoError(u.t, u.unsubscribe())
assert.NoError(u.t, u.subscription.Unsubscribe())
}
return u.Called(event).Error(0)
}

func (u *uiMock) Teardown(_ bool) error {
u.t.Helper()
u.t.Logf("UI Teardown called")
return u.Called().Error(0)
}

func Test_eventLoop_gracefulExit(t *testing.T) {
func Test_EventLoop_gracefulExit(t *testing.T) {
test := func(t *testing.T) {

testBus := partybus.NewBus()
@@ -110,7 +113,7 @@ func Test_eventLoop_gracefulExit(t *testing.T) {
testWithTimeout(t, 5*time.Second, test)
}

func Test_eventLoop_workerError(t *testing.T) {
func Test_EventLoop_workerError(t *testing.T) {
test := func(t *testing.T) {

testBus := partybus.NewBus()
@@ -175,7 +178,7 @@ func Test_eventLoop_workerError(t *testing.T) {
testWithTimeout(t, 5*time.Second, test)
}

func Test_eventLoop_unsubscribeError(t *testing.T) {
func Test_EventLoop_unsubscribeError(t *testing.T) {
test := func(t *testing.T) {

testBus := partybus.NewBus()
@@ -244,7 +247,7 @@ func Test_eventLoop_unsubscribeError(t *testing.T) {
testWithTimeout(t, 5*time.Second, test)
}

func Test_eventLoop_handlerError(t *testing.T) {
func Test_EventLoop_handlerError(t *testing.T) {
test := func(t *testing.T) {

testBus := partybus.NewBus()
@@ -253,7 +256,7 @@ func Test_eventLoop_handlerError(t *testing.T) {

finalEvent := partybus.Event{
Type: event.CLIExit,
Error: fmt.Errorf("unable to create presenter"),
Error: fmt.Errorf("an exit error occured"),
}

worker := func() <-chan error {
@@ -316,7 +319,7 @@ func Test_eventLoop_handlerError(t *testing.T) {
testWithTimeout(t, 5*time.Second, test)
}

func Test_eventLoop_signalsStopExecution(t *testing.T) {
func Test_EventLoop_signalsStopExecution(t *testing.T) {
test := func(t *testing.T) {

testBus := partybus.NewBus()
@@ -369,7 +372,7 @@ func Test_eventLoop_signalsStopExecution(t *testing.T) {
testWithTimeout(t, 5*time.Second, test)
}

func Test_eventLoop_uiTeardownError(t *testing.T) {
func Test_EventLoop_uiTeardownError(t *testing.T) {
test := func(t *testing.T) {

testBus := partybus.NewBus()
Loading

0 comments on commit ebd4643

Please sign in to comment.