Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,9 @@ optional for the other profiles. `--ecosystem` is repeatable and
comma-separated. `--exposure-catalog` accepts a JSON file or a directory
of `*.json` catalogs (merged non-recursively, all files must share
`schema_version`). `--findings-only` requires `--exposure-catalog` and
suppresses package records while keeping findings. `bumblebee scan --help`
lists every flag.
suppresses package records while keeping findings. `--quiet` suppresses
info-level diagnostics on stderr while keeping warn and error diagnostics.
`bumblebee scan --help` lists every flag.

## Output

Expand Down
5 changes: 5 additions & 0 deletions cmd/bumblebee/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ type scanOpts struct {
httpGzip bool

deviceIDEnv string

quiet bool
}

func registerScanFlags(fs *flag.FlagSet, o *scanOpts) {
Expand Down Expand Up @@ -161,6 +163,8 @@ func registerScanFlags(fs *flag.FlagSet, o *scanOpts) {

fs.StringVar(&o.deviceIDEnv, "device-id-env", "",
"env var holding a stable opaque endpoint/device id (e.g. set by MDM, EDR, or a provisioning script); populates endpoint.device_id when set")

fs.BoolVar(&o.quiet, "quiet", false, "suppress info-level diagnostics on stderr (warn and error diagnostics are still emitted)")
}

// runScan executes the scan subcommand. Returns the process exit code.
Expand Down Expand Up @@ -221,6 +225,7 @@ func runScan(args []string) int {

runID := newRunID()
emitter := output.New(recordsW, os.Stderr, runID)
emitter.SetQuiet(o.quiet)

for _, n := range diagNotes {
emitter.Diag("info", "", n)
Expand Down
12 changes: 12 additions & 0 deletions internal/output/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ type Emitter struct {
records io.Writer
diags io.Writer
runID string
quiet bool

mu sync.Mutex
enc *json.Encoder
Expand All @@ -57,6 +58,14 @@ func New(records, diags io.Writer, runID string) *Emitter {
}
}

// SetQuiet suppresses info-level diagnostics on the diagnostics writer.
// Warn and error diagnostics are still emitted.
func (e *Emitter) SetQuiet(quiet bool) {
e.mu.Lock()
defer e.mu.Unlock()
e.quiet = quiet
}

// ObservePackage reserves the package record's dedupe slot and returns the
// canonicalized record plus whether it is new for this run.
func (e *Emitter) ObservePackage(r model.Record) (model.Record, bool) {
Expand Down Expand Up @@ -132,6 +141,9 @@ func (e *Emitter) Diag(level, path, msg string) {
e.mu.Lock()
defer e.mu.Unlock()
e.Diagnostics++
if e.quiet && level == "info" {
return
}
d := model.Diagnostic{
RecordType: model.RecordTypeDiagnostic,
RunID: e.runID,
Expand Down
33 changes: 33 additions & 0 deletions internal/output/output_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,39 @@ func TestEmitSummaryWritesScanSummaryRecord(t *testing.T) {
}
}

func TestDiagQuietSuppressesInfo(t *testing.T) {
var diags bytes.Buffer
e := New(io.Discard, &diags, "run-1")
e.SetQuiet(true)
e.Diag("info", "", "resolved roots")
if diags.Len() != 0 {
t.Fatalf("quiet mode wrote info diagnostic: %q", diags.String())
}
if e.Diagnostics != 1 {
t.Fatalf("Diagnostics=%d, want 1 (info still counted)", e.Diagnostics)
}
}

func TestDiagQuietStillEmitsWarnAndError(t *testing.T) {
var diags bytes.Buffer
e := New(io.Discard, &diags, "run-1")
e.SetQuiet(true)
e.Diag("warn", "", "device id env not set")
e.Diag("error", "", "scan failed")
if got := strings.Count(diags.String(), "\n"); got != 2 {
t.Fatalf("want 2 diagnostic lines, got %d: %q", got, diags.String())
}
}

func TestDiagDefaultEmitsInfo(t *testing.T) {
var diags bytes.Buffer
e := New(io.Discard, &diags, "run-1")
e.Diag("info", "", "scan complete")
if diags.Len() == 0 {
t.Fatal("default mode should emit info diagnostic")
}
}

func TestObservePackageDedupsWithoutWriting(t *testing.T) {
e := New(io.Discard, io.Discard, "run-1")
rec := model.Record{
Expand Down