mirror of
https://github.com/anchore/syft.git
synced 2025-11-17 16:33:21 +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:
parent
f47a6a88b1
commit
1b23a94015
@ -2,6 +2,7 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/anchore/syft/internal/log"
|
"github.com/anchore/syft/internal/log"
|
||||||
@ -14,11 +15,13 @@ import (
|
|||||||
// signal interrupts. Is responsible for handling each event relative to a given UI an to coordinate eventing until
|
// signal interrupts. Is responsible for handling each event relative to a given UI an to coordinate eventing until
|
||||||
// an eventual graceful exit.
|
// an eventual graceful exit.
|
||||||
// nolint:gocognit,funlen
|
// nolint:gocognit,funlen
|
||||||
func eventLoop(workerErrs <-chan error, signals <-chan os.Signal, subscription *partybus.Subscription, ux ui.UI, cleanupFn func()) error {
|
func eventLoop(workerErrs <-chan error, signals <-chan os.Signal, subscription *partybus.Subscription, cleanupFn func(), uxs ...ui.UI) error {
|
||||||
defer cleanupFn()
|
defer cleanupFn()
|
||||||
events := subscription.Events()
|
events := subscription.Events()
|
||||||
var err error
|
var err error
|
||||||
if ux, err = setupUI(subscription.Unsubscribe, ux); err != nil {
|
var ux ui.UI
|
||||||
|
|
||||||
|
if ux, err = setupUI(subscription.Unsubscribe, uxs...); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,15 +81,18 @@ func eventLoop(workerErrs <-chan error, signals <-chan os.Signal, subscription *
|
|||||||
return retErr
|
return retErr
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupUI(unsubscribe func() error, ux ui.UI) (ui.UI, error) {
|
// setupUI takes one or more UIs that responds to events and takes a event bus unsubscribe function for use
|
||||||
if err := ux.Setup(unsubscribe); err != nil {
|
// during teardown. With the given UIs, the first UI which the ui.Setup() function does not return an error
|
||||||
// replace the existing UI with a (simpler) logger UI
|
// will be utilized in execution. Providing a set of UIs allows for the caller to provide graceful fallbacks
|
||||||
ux = ui.NewLoggerUI()
|
// 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) {
|
||||||
|
for _, ux := range uis {
|
||||||
if err := ux.Setup(unsubscribe); err != nil {
|
if err := ux.Setup(unsubscribe); err != nil {
|
||||||
// something is very wrong, bail.
|
log.Warnf("unable to setup given UI, falling back to alternative UI: %+v", err)
|
||||||
return ux, err
|
continue
|
||||||
}
|
}
|
||||||
log.Errorf("unable to setup given UI, falling back to logger: %+v", err)
|
|
||||||
|
return ux, nil
|
||||||
}
|
}
|
||||||
return ux, nil
|
return nil, fmt.Errorf("unable to setup any UI")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -96,8 +96,8 @@ func Test_eventLoop_gracefulExit(t *testing.T) {
|
|||||||
worker(),
|
worker(),
|
||||||
signaler(),
|
signaler(),
|
||||||
subscription,
|
subscription,
|
||||||
ux,
|
|
||||||
cleanupFn,
|
cleanupFn,
|
||||||
|
ux,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -159,8 +159,8 @@ func Test_eventLoop_workerError(t *testing.T) {
|
|||||||
worker(),
|
worker(),
|
||||||
signaler(),
|
signaler(),
|
||||||
subscription,
|
subscription,
|
||||||
ux,
|
|
||||||
cleanupFn,
|
cleanupFn,
|
||||||
|
ux,
|
||||||
),
|
),
|
||||||
workerErr,
|
workerErr,
|
||||||
"should have seen a worker error, but did not",
|
"should have seen a worker error, but did not",
|
||||||
@ -230,8 +230,8 @@ func Test_eventLoop_unsubscribeError(t *testing.T) {
|
|||||||
worker(),
|
worker(),
|
||||||
signaler(),
|
signaler(),
|
||||||
subscription,
|
subscription,
|
||||||
ux,
|
|
||||||
cleanupFn,
|
cleanupFn,
|
||||||
|
ux,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -300,8 +300,8 @@ func Test_eventLoop_handlerError(t *testing.T) {
|
|||||||
worker(),
|
worker(),
|
||||||
signaler(),
|
signaler(),
|
||||||
subscription,
|
subscription,
|
||||||
ux,
|
|
||||||
cleanupFn,
|
cleanupFn,
|
||||||
|
ux,
|
||||||
),
|
),
|
||||||
finalEvent.Error,
|
finalEvent.Error,
|
||||||
"should have seen a event error, but did not",
|
"should have seen a event error, but did not",
|
||||||
@ -355,8 +355,8 @@ func Test_eventLoop_signalsStopExecution(t *testing.T) {
|
|||||||
worker(),
|
worker(),
|
||||||
signaler(),
|
signaler(),
|
||||||
subscription,
|
subscription,
|
||||||
ux,
|
|
||||||
cleanupFn,
|
cleanupFn,
|
||||||
|
ux,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -425,8 +425,8 @@ func Test_eventLoop_uiTeardownError(t *testing.T) {
|
|||||||
worker(),
|
worker(),
|
||||||
signaler(),
|
signaler(),
|
||||||
subscription,
|
subscription,
|
||||||
ux,
|
|
||||||
cleanupFn,
|
cleanupFn,
|
||||||
|
ux,
|
||||||
),
|
),
|
||||||
teardownError,
|
teardownError,
|
||||||
"should have seen a UI teardown error, but did not",
|
"should have seen a UI teardown error, but did not",
|
||||||
|
|||||||
@ -113,6 +113,11 @@ func setPackageFlags(flags *pflag.FlagSet) {
|
|||||||
fmt.Sprintf("report output formatter, options=%v", packages.AllPresenters),
|
fmt.Sprintf("report output formatter, options=%v", packages.AllPresenters),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
flags.StringP(
|
||||||
|
"file", "", "",
|
||||||
|
"file to write the report output to (default is STDOUT)",
|
||||||
|
)
|
||||||
|
|
||||||
///////// Upload options //////////////////////////////////////////////////////////
|
///////// Upload options //////////////////////////////////////////////////////////
|
||||||
flags.StringP(
|
flags.StringP(
|
||||||
"host", "H", "",
|
"host", "H", "",
|
||||||
@ -156,6 +161,10 @@ func bindPackagesConfigOptions(flags *pflag.FlagSet) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := viper.BindPFlag("file", flags.Lookup("file")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
///////// Upload options //////////////////////////////////////////////////////////
|
///////// Upload options //////////////////////////////////////////////////////////
|
||||||
|
|
||||||
if err := viper.BindPFlag("anchore.host", flags.Lookup("host")); err != nil {
|
if err := viper.BindPFlag("anchore.host", flags.Lookup("host")); err != nil {
|
||||||
@ -188,12 +197,24 @@ func bindPackagesConfigOptions(flags *pflag.FlagSet) error {
|
|||||||
func packagesExec(_ *cobra.Command, args []string) error {
|
func packagesExec(_ *cobra.Command, args []string) error {
|
||||||
// could be an image or a directory, with or without a scheme
|
// could be an image or a directory, with or without a scheme
|
||||||
userInput := args[0]
|
userInput := args[0]
|
||||||
|
|
||||||
|
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(
|
return eventLoop(
|
||||||
packagesExecWorker(userInput),
|
packagesExecWorker(userInput),
|
||||||
setupSignals(),
|
setupSignals(),
|
||||||
eventSubscription,
|
eventSubscription,
|
||||||
ui.Select(appConfig.CliOptions.Verbosity > 0, appConfig.Quiet),
|
|
||||||
stereoscope.Cleanup,
|
stereoscope.Cleanup,
|
||||||
|
ui.Select(appConfig.CliOptions.Verbosity > 0, appConfig.Quiet, reporter)...,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/anchore/stereoscope"
|
"github.com/anchore/stereoscope"
|
||||||
"github.com/anchore/syft/internal"
|
"github.com/anchore/syft/internal"
|
||||||
"github.com/anchore/syft/internal/bus"
|
"github.com/anchore/syft/internal/bus"
|
||||||
|
"github.com/anchore/syft/internal/log"
|
||||||
"github.com/anchore/syft/internal/presenter/poweruser"
|
"github.com/anchore/syft/internal/presenter/poweruser"
|
||||||
"github.com/anchore/syft/internal/ui"
|
"github.com/anchore/syft/internal/ui"
|
||||||
"github.com/anchore/syft/syft/event"
|
"github.com/anchore/syft/syft/event"
|
||||||
@ -73,12 +74,24 @@ func init() {
|
|||||||
func powerUserExec(_ *cobra.Command, args []string) error {
|
func powerUserExec(_ *cobra.Command, args []string) error {
|
||||||
// could be an image or a directory, with or without a scheme
|
// could be an image or a directory, with or without a scheme
|
||||||
userInput := args[0]
|
userInput := args[0]
|
||||||
|
|
||||||
|
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(
|
return eventLoop(
|
||||||
powerUserExecWorker(userInput),
|
powerUserExecWorker(userInput),
|
||||||
setupSignals(),
|
setupSignals(),
|
||||||
eventSubscription,
|
eventSubscription,
|
||||||
ui.Select(appConfig.CliOptions.Verbosity > 0, appConfig.Quiet),
|
|
||||||
stereoscope.Cleanup,
|
stereoscope.Cleanup,
|
||||||
|
ui.Select(appConfig.CliOptions.Verbosity > 0, appConfig.Quiet, reporter)...,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
29
cmd/report_writer.go
Normal file
29
cmd/report_writer.go
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/internal/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func reportWriter() (io.Writer, func() error, error) {
|
||||||
|
nop := func() error { return nil }
|
||||||
|
|
||||||
|
path := strings.TrimSpace(appConfig.File)
|
||||||
|
switch len(path) {
|
||||||
|
case 0:
|
||||||
|
return os.Stdout, nop, nil
|
||||||
|
default:
|
||||||
|
reportFile, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nop, fmt.Errorf("unable to create report file: %w", err)
|
||||||
|
}
|
||||||
|
return reportFile, func() error {
|
||||||
|
log.Infof("report written to file=%q", path)
|
||||||
|
return reportFile.Close()
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -29,6 +29,7 @@ type parser interface {
|
|||||||
type Application struct {
|
type Application struct {
|
||||||
ConfigPath string `yaml:",omitempty" json:"configPath"` // the location where the application config was read from (either from -c or discovered while loading)
|
ConfigPath string `yaml:",omitempty" json:"configPath"` // the location where the application config was read from (either from -c or discovered while loading)
|
||||||
Output string `yaml:"output" json:"output" mapstructure:"output"` // -o, the Presenter hint string to use for report formatting
|
Output string `yaml:"output" json:"output" mapstructure:"output"` // -o, the Presenter hint string to use for report formatting
|
||||||
|
File string `yaml:"file" json:"file" mapstructure:"file"` // --file, the file to write report output to
|
||||||
Quiet bool `yaml:"quiet" json:"quiet" mapstructure:"quiet"` // -q, indicates to not show any status output to stderr (ETUI or logging UI)
|
Quiet bool `yaml:"quiet" json:"quiet" mapstructure:"quiet"` // -q, indicates to not show any status output to stderr (ETUI or logging UI)
|
||||||
CheckForAppUpdate bool `yaml:"check-for-app-update" json:"check-for-app-update" mapstructure:"check-for-app-update"` // whether to check for an application update on start up or not
|
CheckForAppUpdate bool `yaml:"check-for-app-update" json:"check-for-app-update" mapstructure:"check-for-app-update"` // whether to check for an application update on start up or not
|
||||||
Anchore anchore `yaml:"anchore" json:"anchore" mapstructure:"anchore"` // options for interacting with Anchore Engine/Enterprise
|
Anchore anchore `yaml:"anchore" json:"anchore" mapstructure:"anchore"` // options for interacting with Anchore Engine/Enterprise
|
||||||
|
|||||||
@ -168,6 +168,7 @@
|
|||||||
"configuration": {
|
"configuration": {
|
||||||
"configPath": "",
|
"configPath": "",
|
||||||
"output": "",
|
"output": "",
|
||||||
|
"file": "",
|
||||||
"quiet": false,
|
"quiet": false,
|
||||||
"check-for-app-update": false,
|
"check-for-app-update": false,
|
||||||
"anchore": {
|
"anchore": {
|
||||||
|
|||||||
@ -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
|
// 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)
|
// processing an event before the ETUI exits (coordinated with a sync.WaitGroup)
|
||||||
type ephemeralTerminalUI struct {
|
type ephemeralTerminalUI struct {
|
||||||
unsubscribe func() error
|
unsubscribe func() error
|
||||||
handler *ui.Handler
|
handler *ui.Handler
|
||||||
waitGroup *sync.WaitGroup
|
waitGroup *sync.WaitGroup
|
||||||
frame *frame.Frame
|
frame *frame.Frame
|
||||||
logBuffer *bytes.Buffer
|
logBuffer *bytes.Buffer
|
||||||
output *os.File
|
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{
|
return &ephemeralTerminalUI{
|
||||||
handler: ui.NewHandler(),
|
handler: ui.NewHandler(),
|
||||||
waitGroup: &sync.WaitGroup{},
|
waitGroup: &sync.WaitGroup{},
|
||||||
output: os.Stderr,
|
uiOutput: os.Stderr,
|
||||||
|
reportOutput: reportWriter,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *ephemeralTerminalUI) Setup(unsubscribe func() error) error {
|
func (h *ephemeralTerminalUI) Setup(unsubscribe func() error) error {
|
||||||
h.unsubscribe = unsubscribe
|
h.unsubscribe = unsubscribe
|
||||||
hideCursor(h.output)
|
hideCursor(h.uiOutput)
|
||||||
|
|
||||||
// prep the logger to not clobber the screen from now on (logrus only)
|
// prep the logger to not clobber the screen from now on (logrus only)
|
||||||
h.logBuffer = bytes.NewBufferString("")
|
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
|
// are about to write bytes to stdout, so we should reset the terminal state first
|
||||||
h.closeScreen(false)
|
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)
|
log.Errorf("unable to show %s event: %+v", event.Type, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,7 +98,7 @@ func (h *ephemeralTerminalUI) openScreen() error {
|
|||||||
config := frame.Config{
|
config := frame.Config{
|
||||||
PositionPolicy: frame.PolicyFloatForward,
|
PositionPolicy: frame.PolicyFloatForward,
|
||||||
// only report output to stderr, reserve report output for stdout
|
// only report output to stderr, reserve report output for stdout
|
||||||
Output: h.output,
|
Output: h.uiOutput,
|
||||||
}
|
}
|
||||||
|
|
||||||
fr, err := frame.New(config)
|
fr, err := frame.New(config)
|
||||||
@ -128,15 +131,15 @@ func (h *ephemeralTerminalUI) flushLog() {
|
|||||||
logWrapper, ok := log.Log.(*logger.LogrusLogger)
|
logWrapper, ok := log.Log.(*logger.LogrusLogger)
|
||||||
if ok {
|
if ok {
|
||||||
fmt.Fprint(logWrapper.Output, h.logBuffer.String())
|
fmt.Fprint(logWrapper.Output, h.logBuffer.String())
|
||||||
logWrapper.Logger.SetOutput(h.output)
|
logWrapper.Logger.SetOutput(h.uiOutput)
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprint(h.output, h.logBuffer.String())
|
fmt.Fprint(h.uiOutput, h.logBuffer.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *ephemeralTerminalUI) Teardown(force bool) error {
|
func (h *ephemeralTerminalUI) Teardown(force bool) error {
|
||||||
h.closeScreen(force)
|
h.closeScreen(force)
|
||||||
showCursor(h.output)
|
showCursor(h.uiOutput)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/anchore/syft/internal"
|
"github.com/anchore/syft/internal"
|
||||||
@ -17,14 +16,14 @@ import (
|
|||||||
|
|
||||||
// handleCatalogerPresenterReady is a UI function for processing the CatalogerFinished bus event, displaying the catalog
|
// handleCatalogerPresenterReady is a UI function for processing the CatalogerFinished bus event, displaying the catalog
|
||||||
// via the given presenter to stdout.
|
// 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
|
// show the report to stdout
|
||||||
pres, err := syftEventParsers.ParsePresenterReady(event)
|
pres, err := syftEventParsers.ParsePresenterReady(event)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("bad CatalogerFinished event: %w", err)
|
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 fmt.Errorf("unable to show package catalog report: %w", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@ -1,17 +1,23 @@
|
|||||||
package ui
|
package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
"github.com/anchore/syft/internal/log"
|
"github.com/anchore/syft/internal/log"
|
||||||
syftEvent "github.com/anchore/syft/syft/event"
|
syftEvent "github.com/anchore/syft/syft/event"
|
||||||
"github.com/wagoodman/go-partybus"
|
"github.com/wagoodman/go-partybus"
|
||||||
)
|
)
|
||||||
|
|
||||||
type loggerUI struct {
|
type loggerUI struct {
|
||||||
unsubscribe func() error
|
unsubscribe func() error
|
||||||
|
reportOutput io.Writer
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLoggerUI() UI {
|
// NewLoggerUI writes all events to the common application logger and writes the final report to the given writer.
|
||||||
return &loggerUI{}
|
func NewLoggerUI(reportWriter io.Writer) UI {
|
||||||
|
return &loggerUI{
|
||||||
|
reportOutput: reportWriter,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *loggerUI) Setup(unsubscribe func() error) error {
|
func (l *loggerUI) Setup(unsubscribe func() error) error {
|
||||||
@ -25,7 +31,7 @@ func (l loggerUI) Handle(event partybus.Event) error {
|
|||||||
return nil
|
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)
|
log.Warnf("unable to show catalog image finished event: %+v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package ui
|
package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
@ -10,20 +11,21 @@ import (
|
|||||||
// TODO: build tags to exclude options from windows
|
// TODO: build tags to exclude options from windows
|
||||||
|
|
||||||
// Select is responsible for determining the specific UI function given select user option, the current platform
|
// 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).
|
// config values, and environment status (such as a TTY being present). The first UI in the returned slice of UIs
|
||||||
func Select(verbose, quiet bool) UI {
|
// is intended to be used and the UIs that follow are meant to be attempted only in a fallback posture when there
|
||||||
var ui UI
|
// 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()))
|
isStdoutATty := terminal.IsTerminal(int(os.Stdout.Fd()))
|
||||||
isStderrATty := terminal.IsTerminal(int(os.Stderr.Fd()))
|
isStderrATty := terminal.IsTerminal(int(os.Stderr.Fd()))
|
||||||
notATerminal := !isStderrATty && !isStdoutATty
|
notATerminal := !isStderrATty && !isStdoutATty
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case runtime.GOOS == "windows" || verbose || quiet || notATerminal || !isStderrATty:
|
case runtime.GOOS == "windows" || verbose || quiet || notATerminal || !isStderrATty:
|
||||||
ui = NewLoggerUI()
|
uis = append(uis, NewLoggerUI(reportWriter))
|
||||||
default:
|
default:
|
||||||
ui = NewEphemeralTerminalUI()
|
uis = append(uis, NewEphemeralTerminalUI(reportWriter), NewLoggerUI(reportWriter))
|
||||||
}
|
}
|
||||||
|
|
||||||
return ui
|
return uis
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user