mirror of
https://github.com/anchore/syft.git
synced 2026-10-30 14:02:26 +01:00
Add option to output SBOM report to a file (#530)
* add output to file option Signed-off-by: Alex Goodman <alex.goodman@anchore.com> * log errors on close of the report destination Signed-off-by: Alex Goodman <alex.goodman@anchore.com> * remove file option from persistent args Signed-off-by: Alex Goodman <alex.goodman@anchore.com> * update file option comments and logging Signed-off-by: Alex Goodman <alex.goodman@anchore.com> * allow for multiple UI fallback options Signed-off-by: Alex Goodman <alex.goodman@anchore.com> * update UI select signatures + tests Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
This commit is contained in:
@@ -33,25 +33,28 @@ import (
|
||||
// or in the shared ui package as a function on the main handler object. All handler functions should be completed
|
||||
// processing an event before the ETUI exits (coordinated with a sync.WaitGroup)
|
||||
type ephemeralTerminalUI struct {
|
||||
unsubscribe func() error
|
||||
handler *ui.Handler
|
||||
waitGroup *sync.WaitGroup
|
||||
frame *frame.Frame
|
||||
logBuffer *bytes.Buffer
|
||||
output *os.File
|
||||
unsubscribe func() error
|
||||
handler *ui.Handler
|
||||
waitGroup *sync.WaitGroup
|
||||
frame *frame.Frame
|
||||
logBuffer *bytes.Buffer
|
||||
uiOutput *os.File
|
||||
reportOutput io.Writer
|
||||
}
|
||||
|
||||
func NewEphemeralTerminalUI() UI {
|
||||
// NewEphemeralTerminalUI writes all events to a TUI and writes the final report to the given writer.
|
||||
func NewEphemeralTerminalUI(reportWriter io.Writer) UI {
|
||||
return &ephemeralTerminalUI{
|
||||
handler: ui.NewHandler(),
|
||||
waitGroup: &sync.WaitGroup{},
|
||||
output: os.Stderr,
|
||||
handler: ui.NewHandler(),
|
||||
waitGroup: &sync.WaitGroup{},
|
||||
uiOutput: os.Stderr,
|
||||
reportOutput: reportWriter,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *ephemeralTerminalUI) Setup(unsubscribe func() error) error {
|
||||
h.unsubscribe = unsubscribe
|
||||
hideCursor(h.output)
|
||||
hideCursor(h.uiOutput)
|
||||
|
||||
// prep the logger to not clobber the screen from now on (logrus only)
|
||||
h.logBuffer = bytes.NewBufferString("")
|
||||
@@ -81,7 +84,7 @@ func (h *ephemeralTerminalUI) Handle(event partybus.Event) error {
|
||||
// are about to write bytes to stdout, so we should reset the terminal state first
|
||||
h.closeScreen(false)
|
||||
|
||||
if err := handleCatalogerPresenterReady(event); err != nil {
|
||||
if err := handleCatalogerPresenterReady(event, h.reportOutput); err != nil {
|
||||
log.Errorf("unable to show %s event: %+v", event.Type, err)
|
||||
}
|
||||
|
||||
@@ -95,7 +98,7 @@ func (h *ephemeralTerminalUI) openScreen() error {
|
||||
config := frame.Config{
|
||||
PositionPolicy: frame.PolicyFloatForward,
|
||||
// only report output to stderr, reserve report output for stdout
|
||||
Output: h.output,
|
||||
Output: h.uiOutput,
|
||||
}
|
||||
|
||||
fr, err := frame.New(config)
|
||||
@@ -128,15 +131,15 @@ func (h *ephemeralTerminalUI) flushLog() {
|
||||
logWrapper, ok := log.Log.(*logger.LogrusLogger)
|
||||
if ok {
|
||||
fmt.Fprint(logWrapper.Output, h.logBuffer.String())
|
||||
logWrapper.Logger.SetOutput(h.output)
|
||||
logWrapper.Logger.SetOutput(h.uiOutput)
|
||||
} else {
|
||||
fmt.Fprint(h.output, h.logBuffer.String())
|
||||
fmt.Fprint(h.uiOutput, h.logBuffer.String())
|
||||
}
|
||||
}
|
||||
|
||||
func (h *ephemeralTerminalUI) Teardown(force bool) error {
|
||||
h.closeScreen(force)
|
||||
showCursor(h.output)
|
||||
showCursor(h.uiOutput)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
@@ -17,14 +16,14 @@ import (
|
||||
|
||||
// handleCatalogerPresenterReady is a UI function for processing the CatalogerFinished bus event, displaying the catalog
|
||||
// via the given presenter to stdout.
|
||||
func handleCatalogerPresenterReady(event partybus.Event) error {
|
||||
func handleCatalogerPresenterReady(event partybus.Event, reportOutput io.Writer) error {
|
||||
// show the report to stdout
|
||||
pres, err := syftEventParsers.ParsePresenterReady(event)
|
||||
if err != nil {
|
||||
return fmt.Errorf("bad CatalogerFinished event: %w", err)
|
||||
}
|
||||
|
||||
if err := pres.Present(os.Stdout); err != nil {
|
||||
if err := pres.Present(reportOutput); err != nil {
|
||||
return fmt.Errorf("unable to show package catalog report: %w", err)
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -1,17 +1,23 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
syftEvent "github.com/anchore/syft/syft/event"
|
||||
"github.com/wagoodman/go-partybus"
|
||||
)
|
||||
|
||||
type loggerUI struct {
|
||||
unsubscribe func() error
|
||||
unsubscribe func() error
|
||||
reportOutput io.Writer
|
||||
}
|
||||
|
||||
func NewLoggerUI() UI {
|
||||
return &loggerUI{}
|
||||
// NewLoggerUI writes all events to the common application logger and writes the final report to the given writer.
|
||||
func NewLoggerUI(reportWriter io.Writer) UI {
|
||||
return &loggerUI{
|
||||
reportOutput: reportWriter,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *loggerUI) Setup(unsubscribe func() error) error {
|
||||
@@ -25,7 +31,7 @@ func (l loggerUI) Handle(event partybus.Event) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := handleCatalogerPresenterReady(event); err != nil {
|
||||
if err := handleCatalogerPresenterReady(event, l.reportOutput); err != nil {
|
||||
log.Warnf("unable to show catalog image finished event: %+v", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
@@ -10,20 +11,21 @@ import (
|
||||
// TODO: build tags to exclude options from windows
|
||||
|
||||
// Select is responsible for determining the specific UI function given select user option, the current platform
|
||||
// config values, and environment status (such as a TTY being present).
|
||||
func Select(verbose, quiet bool) UI {
|
||||
var ui UI
|
||||
|
||||
// config values, and environment status (such as a TTY being present). The first UI in the returned slice of UIs
|
||||
// is intended to be used and the UIs that follow are meant to be attempted only in a fallback posture when there
|
||||
// are environmental problems (e.g. cannot write to the terminal). A writer is provided to capture the output of
|
||||
// the final SBOM report.
|
||||
func Select(verbose, quiet bool, reportWriter io.Writer) (uis []UI) {
|
||||
isStdoutATty := terminal.IsTerminal(int(os.Stdout.Fd()))
|
||||
isStderrATty := terminal.IsTerminal(int(os.Stderr.Fd()))
|
||||
notATerminal := !isStderrATty && !isStdoutATty
|
||||
|
||||
switch {
|
||||
case runtime.GOOS == "windows" || verbose || quiet || notATerminal || !isStderrATty:
|
||||
ui = NewLoggerUI()
|
||||
uis = append(uis, NewLoggerUI(reportWriter))
|
||||
default:
|
||||
ui = NewEphemeralTerminalUI()
|
||||
uis = append(uis, NewEphemeralTerminalUI(reportWriter), NewLoggerUI(reportWriter))
|
||||
}
|
||||
|
||||
return ui
|
||||
return uis
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user