Skip to content

Commit

Permalink
Support full set of column data for component list
Browse files Browse the repository at this point in the history
Signed-off-by: Matt Rutkowski <[email protected]>
  • Loading branch information
mrutkows committed May 6, 2024
1 parent bcda3e0 commit 03b51e6
Show file tree
Hide file tree
Showing 6 changed files with 166 additions and 120 deletions.
78 changes: 51 additions & 27 deletions cmd/component.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,7 @@ var VALID_SUBCOMMANDS_COMPONENT = []string{SUBCOMMAND_COMPONENT_LIST}

// filter keys
// Note: these string values MUST match annotations for the ComponentInfo struct fields
// Type string `json:"type"`
// Publisher string `json:"publisher,omitempty"`
// Scope string `json:"scope,omitempty"`
// Copyright string `json:"copyright,omitempty"`
// Cpe string `json:"cpe,omitempty"` // See: https://nvd.nist.gov/products/cpe
// Purl string `json:"purl,omitempty" scvs:"bom:resource:identifiers:purl"` // See: https://github.com/package-url/purl-spec
// Swid *CDXSwid `json:"swid,omitempty"`
const (
COMPONENT_FILTER_KEY_BOMREF = "bom-ref"
COMPONENT_FILTER_KEY_GROUP = "group"
Expand All @@ -68,6 +62,14 @@ const (
COMPONENT_FILTER_KEY_NUM_HASHES = "number-hashes"
COMPONENT_FILTER_KEY_HAS_PEDIGREE = "has-pedigree"
COMPONENT_FILTER_KEY_HAS_EVIDENCE = "has-evidence"
COMPONENT_FILTER_KEY_MIME_TYPE = "mime-type"
COMPONENT_FILTER_KEY_HAS_SCOPE = "scope"
COMPONENT_FILTER_KEY_HAS_COMPONENTS = "has-components"
COMPONENT_FILTER_KEY_HAS_RELEASE_NOTES = "has-release-notes"
COMPONENT_FILTER_KEY_HAS_MODEL_CARD = "has-model-card"
COMPONENT_FILTER_KEY_HAS_DATA = "has-data"
COMPONENT_FILTER_KEY_HAS_TAGS = "has-tags"
COMPONENT_FILTER_KEY_HAS_SIGNATURE = "has-signature"
)

var VALID_COMPONENT_FILTER_KEYS = []string{
Expand All @@ -90,6 +92,14 @@ var VALID_COMPONENT_FILTER_KEYS = []string{
COMPONENT_FILTER_KEY_NUM_HASHES,
COMPONENT_FILTER_KEY_HAS_PEDIGREE,
COMPONENT_FILTER_KEY_HAS_EVIDENCE,
COMPONENT_FILTER_KEY_MIME_TYPE,
COMPONENT_FILTER_KEY_HAS_SCOPE,
COMPONENT_FILTER_KEY_HAS_COMPONENTS,
COMPONENT_FILTER_KEY_HAS_RELEASE_NOTES,
COMPONENT_FILTER_KEY_HAS_MODEL_CARD,
COMPONENT_FILTER_KEY_HAS_DATA,
COMPONENT_FILTER_KEY_HAS_TAGS,
COMPONENT_FILTER_KEY_HAS_SIGNATURE,
}

var COMPONENT_LIST_ROW_DATA = []ColumnFormatData{
Expand All @@ -108,16 +118,26 @@ var COMPONENT_LIST_ROW_DATA = []ColumnFormatData{
*NewColumnFormatData(COMPONENT_FILTER_KEY_PURL, REPORT_DO_NOT_TRUNCATE, REPORT_SUMMARY_DATA, false),
*NewColumnFormatData(COMPONENT_FILTER_KEY_SWID, REPORT_DO_NOT_TRUNCATE, REPORT_SUMMARY_DATA, false),
*NewColumnFormatData(COMPONENT_FILTER_KEY_CPE, REPORT_DO_NOT_TRUNCATE, REPORT_SUMMARY_DATA, false),
*NewColumnFormatData(COMPONENT_FILTER_KEY_MIME_TYPE, REPORT_DO_NOT_TRUNCATE, false, false),
*NewColumnFormatData(COMPONENT_FILTER_KEY_HAS_SCOPE, REPORT_DO_NOT_TRUNCATE, false, false),
*NewColumnFormatData(COMPONENT_FILTER_KEY_NUM_HASHES, REPORT_DO_NOT_TRUNCATE, REPORT_SUMMARY_DATA, false),
*NewColumnFormatData(COMPONENT_FILTER_KEY_NUM_LICENSES, REPORT_DO_NOT_TRUNCATE, REPORT_SUMMARY_DATA, false),
*NewColumnFormatData(COMPONENT_FILTER_KEY_HAS_PEDIGREE, REPORT_DO_NOT_TRUNCATE, REPORT_SUMMARY_DATA, false),
*NewColumnFormatData(COMPONENT_FILTER_KEY_HAS_EVIDENCE, REPORT_DO_NOT_TRUNCATE, REPORT_SUMMARY_DATA, false),
*NewColumnFormatData(COMPONENT_FILTER_KEY_HAS_PEDIGREE, REPORT_DO_NOT_TRUNCATE, false, false),
*NewColumnFormatData(COMPONENT_FILTER_KEY_HAS_EVIDENCE, REPORT_DO_NOT_TRUNCATE, false, false),
*NewColumnFormatData(COMPONENT_FILTER_KEY_HAS_COMPONENTS, REPORT_DO_NOT_TRUNCATE, false, false),
*NewColumnFormatData(COMPONENT_FILTER_KEY_HAS_RELEASE_NOTES, REPORT_DO_NOT_TRUNCATE, false, false),
*NewColumnFormatData(COMPONENT_FILTER_KEY_HAS_MODEL_CARD, REPORT_DO_NOT_TRUNCATE, false, false),
*NewColumnFormatData(COMPONENT_FILTER_KEY_HAS_DATA, REPORT_DO_NOT_TRUNCATE, false, false),
*NewColumnFormatData(COMPONENT_FILTER_KEY_HAS_TAGS, REPORT_DO_NOT_TRUNCATE, false, false),
*NewColumnFormatData(COMPONENT_FILTER_KEY_HAS_SIGNATURE, REPORT_DO_NOT_TRUNCATE, false, false),
}

// Flags. Reuse query flag values where possible
const (
FLAG_COMPONENT_TYPE = "type"
FLAG_COMPONENT_TYPE_HELP = "filter output by component type(s)"
FLAG_COMPONENT_SUMMARY = "summary"
FLAG_COMPONENT_TYPE = "type"
// FLAG_COMPONENT_TYPE_HELP = "filter output by component type(s)"
FLAG_COMPONENT_SUMMARY_HELP = "summarize component information when listing in supported formats"
)

const (
Expand All @@ -139,7 +159,11 @@ func NewCommandComponent() *cobra.Command {
command.Long = "Report on components found in the BOM input file"
command.Flags().StringVarP(&utils.GlobalFlags.PersistentFlags.OutputFormat, FLAG_FILE_OUTPUT_FORMAT, "", FORMAT_TEXT,
FLAG_COMPONENT_OUTPUT_FORMAT_HELP+COMPONENT_LIST_OUTPUT_SUPPORTED_FORMATS)
command.Flags().StringP(FLAG_COMPONENT_TYPE, "", "", FLAG_COMPONENT_TYPE_HELP)
//command.Flags().StringP(FLAG_COMPONENT_TYPE, "", "", FLAG_COMPONENT_TYPE_HELP)
command.Flags().BoolVarP(
&utils.GlobalFlags.ComponentFlags.Summary,
FLAG_COMPONENT_SUMMARY, "", false,
FLAG_COMPONENT_SUMMARY_HELP)
command.Flags().StringP(FLAG_REPORT_WHERE, "", "", FLAG_REPORT_WHERE_HELP)
command.RunE = componentCmdImpl
command.ValidArgs = VALID_SUBCOMMANDS_COMPONENT
Expand Down Expand Up @@ -190,7 +214,7 @@ func componentCmdImpl(cmd *cobra.Command, args []string) (err error) {
whereFilters, err := processWhereFlag(cmd)

if err == nil {
err = ListComponents(writer, utils.GlobalFlags.PersistentFlags, whereFilters)
err = ListComponents(writer, utils.GlobalFlags.PersistentFlags, utils.GlobalFlags.ComponentFlags, whereFilters)
}

return
Expand All @@ -205,7 +229,7 @@ func processComponentListResults(err error) {
}

// NOTE: resourceType has already been validated
func ListComponents(writer io.Writer, persistentFlags utils.PersistentCommandFlags, whereFilters []common.WhereFilter) (err error) {
func ListComponents(writer io.Writer, persistentFlags utils.PersistentCommandFlags, flags utils.ComponentCommandFlags, whereFilters []common.WhereFilter) (err error) {
getLogger().Enter()
defer getLogger().Exit()

Expand Down Expand Up @@ -236,16 +260,16 @@ func ListComponents(writer io.Writer, persistentFlags utils.PersistentCommandFla
getLogger().Infof("Outputting listing (`%s` format)...", format)
switch format {
case FORMAT_TEXT:
err = DisplayComponentListText(document, writer)
err = DisplayComponentListText(document, writer, flags)
case FORMAT_CSV:
err = DisplayComponentListCSV(document, writer)
err = DisplayComponentListCSV(document, writer, flags)
case FORMAT_MARKDOWN:
err = DisplayComponentListMarkdown(document, writer)
err = DisplayComponentListMarkdown(document, writer, flags)
default:
// Default to Text output for anything else (set as flag default)
getLogger().Warningf("Listing not supported for `%s` format; defaulting to `%s` format...",
format, FORMAT_TEXT)
err = DisplayComponentListText(document, writer)
err = DisplayComponentListText(document, writer, flags)
}
return
}
Expand Down Expand Up @@ -295,7 +319,7 @@ func sortComponents(entries []multimap.Entry) {

// NOTE: This list is NOT de-duplicated
// TODO: Add a --no-title flag to skip title output
func DisplayComponentListText(bom *schema.BOM, writer io.Writer) (err error) {
func DisplayComponentListText(bom *schema.BOM, writer io.Writer, flags utils.ComponentCommandFlags) (err error) {
getLogger().Enter()
defer getLogger().Exit()

Expand All @@ -307,7 +331,7 @@ func DisplayComponentListText(bom *schema.BOM, writer io.Writer) (err error) {
w.Init(writer, 8, 2, 2, ' ', 0)

// create title row and underline row from slices of optional and compulsory titles
titles, underlines := prepareReportTitleData(COMPONENT_LIST_ROW_DATA, true)
titles, underlines := prepareReportTitleData(COMPONENT_LIST_ROW_DATA, flags.Summary)

// Add tabs between column titles for the tabWRiter
fmt.Fprintf(w, "%s\n", strings.Join(titles, "\t"))
Expand All @@ -334,7 +358,7 @@ func DisplayComponentListText(bom *schema.BOM, writer io.Writer) (err error) {
line, err = prepareReportLineData(
*pComponentInfo,
COMPONENT_LIST_ROW_DATA,
true,
flags.Summary,
)
// Only emit line if no error
if err != nil {
Expand All @@ -346,7 +370,7 @@ func DisplayComponentListText(bom *schema.BOM, writer io.Writer) (err error) {
}

// TODO: Add a --no-title flag to skip title output
func DisplayComponentListCSV(bom *schema.BOM, writer io.Writer) (err error) {
func DisplayComponentListCSV(bom *schema.BOM, writer io.Writer, flags utils.ComponentCommandFlags) (err error) {
getLogger().Enter()
defer getLogger().Exit()

Expand All @@ -355,7 +379,7 @@ func DisplayComponentListCSV(bom *schema.BOM, writer io.Writer) (err error) {
defer w.Flush()

// Create title row data as []string
titles, _ := prepareReportTitleData(COMPONENT_LIST_ROW_DATA, true)
titles, _ := prepareReportTitleData(COMPONENT_LIST_ROW_DATA, flags.Summary)

if err = w.Write(titles); err != nil {
return getLogger().Errorf("error writing to output (%v): %s", titles, err)
Expand Down Expand Up @@ -385,7 +409,7 @@ func DisplayComponentListCSV(bom *schema.BOM, writer io.Writer) (err error) {
line, err = prepareReportLineData(
*pComponentInfo,
COMPONENT_LIST_ROW_DATA,
true,
flags.Summary,
)
// Only emit line if no error
if err != nil {
Expand All @@ -399,17 +423,17 @@ func DisplayComponentListCSV(bom *schema.BOM, writer io.Writer) (err error) {
}

// TODO: Add a --no-title flag to skip title output
func DisplayComponentListMarkdown(bom *schema.BOM, writer io.Writer) (err error) {
func DisplayComponentListMarkdown(bom *schema.BOM, writer io.Writer, flags utils.ComponentCommandFlags) (err error) {
getLogger().Enter()
defer getLogger().Exit()

// Create title row data as []string, include all columns that are flagged "summary" data
titles, _ := prepareReportTitleData(COMPONENT_LIST_ROW_DATA, true)
titles, _ := prepareReportTitleData(COMPONENT_LIST_ROW_DATA, flags.Summary)
titleRow := createMarkdownRow(titles)
fmt.Fprintf(writer, "%s\n", titleRow)

// create alignment row, include all columns that are flagged "summary" data
alignments := createMarkdownColumnAlignmentRow(COMPONENT_LIST_ROW_DATA, true)
alignments := createMarkdownColumnAlignmentRow(COMPONENT_LIST_ROW_DATA, flags.Summary)
alignmentRow := createMarkdownRow(alignments)
fmt.Fprintf(writer, "%s\n", alignmentRow)

Expand All @@ -434,7 +458,7 @@ func DisplayComponentListMarkdown(bom *schema.BOM, writer io.Writer) (err error)
line, err = prepareReportLineData(
*pComponentInfo,
COMPONENT_LIST_ROW_DATA,
true,
flags.Summary,
)
// Only emit line if no error
if err != nil {
Expand Down
34 changes: 18 additions & 16 deletions cmd/component_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ const (
TEST_COMPONENT_LIST_CDX_1_6_MLBOM = TEST_CDX_1_6_MACHINE_LEARNING_BOM
)

var COMPONENT_TEST_DEFAULT_FLAGS utils.ComponentCommandFlags

type ComponentTestInfo struct {
CommonTestInfo
}
Expand Down Expand Up @@ -72,7 +74,7 @@ func NewComponentTestInfoBasic(inputFile string, listFormat string, resultExpect
// -------------------------------------------
// resource list test helper functions
// -------------------------------------------
func innerBufferedTestComponentList(testInfo *ComponentTestInfo, whereFilters []common.WhereFilter) (outputBuffer bytes.Buffer, err error) {
func innerBufferedTestComponentList(testInfo *ComponentTestInfo, whereFilters []common.WhereFilter, flags utils.ComponentCommandFlags) (outputBuffer bytes.Buffer, err error) {
// Declare an output outputBuffer/outputWriter to use used during tests
var outputWriter = bufio.NewWriter(&outputBuffer)
// ensure all data is written to buffer before further validation
Expand All @@ -81,11 +83,11 @@ func innerBufferedTestComponentList(testInfo *ComponentTestInfo, whereFilters []
var persistentFlags utils.PersistentCommandFlags
persistentFlags.OutputFormat = testInfo.OutputFormat

err = ListComponents(outputWriter, persistentFlags, whereFilters)
err = ListComponents(outputWriter, persistentFlags, flags, whereFilters)
return
}

func innerTestComponentList(t *testing.T, testInfo *ComponentTestInfo) (outputBuffer bytes.Buffer, basicTestInfo string, err error) {
func innerTestComponentList(t *testing.T, testInfo *ComponentTestInfo, flags utils.ComponentCommandFlags) (outputBuffer bytes.Buffer, basicTestInfo string, err error) {
getLogger().Tracef("TestInfo: %s", testInfo)

// Parse out --where filters and exit out if error detected
Expand Down Expand Up @@ -113,7 +115,7 @@ func innerTestComponentList(t *testing.T, testInfo *ComponentTestInfo) (outputBu
}

// invoke resource list command with a byte buffer
outputBuffer, err = innerBufferedTestComponentList(testInfo, whereFilters)
outputBuffer, err = innerBufferedTestComponentList(testInfo, whereFilters, flags)

// Run all common tests against "result" values in the CommonTestInfo struct
err = innerRunReportResultTests(t, &testInfo.CommonTestInfo, outputBuffer, err)
Expand All @@ -131,7 +133,7 @@ func TestComponentListFormatUnsupportedSPDXMinReq(t *testing.T) {
&fs.PathError{},
)
// verify correct error is returned
innerTestComponentList(t, ti)
innerTestComponentList(t, ti, COMPONENT_TEST_DEFAULT_FLAGS)
}

// -------------------------------------------
Expand All @@ -143,23 +145,23 @@ func TestComponentListCdx13Text(t *testing.T) {
ti.ResultExpectedLineCount = 14 // title + separator + 11 data + EOF LF
ti.ResultLineContainsValuesAtLineNum = 6
ti.ResultLineContainsValues = []string{"Library G"}
innerTestComponentList(t, ti)
innerTestComponentList(t, ti, COMPONENT_TEST_DEFAULT_FLAGS)
}

func TestComponentListCdx13Csv(t *testing.T) {
ti := NewComponentTestInfoBasic(TEST_RESOURCE_LIST_CDX_1_3, FORMAT_CSV, nil)
ti.ResultExpectedLineCount = 13 // title + 11 data + EOF LF
ti.ResultLineContainsValuesAtLineNum = 5
ti.ResultLineContainsValues = []string{"Library G"}
innerTestComponentList(t, ti)
innerTestComponentList(t, ti, COMPONENT_TEST_DEFAULT_FLAGS)
}

func TestComponentListCdx13Markdown(t *testing.T) {
ti := NewComponentTestInfoBasic(TEST_RESOURCE_LIST_CDX_1_3, FORMAT_MARKDOWN, nil)
ti.ResultExpectedLineCount = 14 // title + separator + 11 data + EOF LF
ti.ResultLineContainsValuesAtLineNum = 6
ti.ResultLineContainsValues = []string{"Library G"}
innerTestComponentList(t, ti)
innerTestComponentList(t, ti, COMPONENT_TEST_DEFAULT_FLAGS)
}

// -------------------------------------------
Expand All @@ -173,23 +175,23 @@ func TestComponentListCdx15MatureCsv(t *testing.T) {
ti.ResultExpectedLineCount = 5 // title + 3 data + EOF LF
ti.ResultLineContainsValuesAtLineNum = 3
ti.ResultLineContainsValues = []string{"sample"}
innerTestComponentList(t, ti)
innerTestComponentList(t, ti, COMPONENT_TEST_DEFAULT_FLAGS)
}

func TestComponentListCdx16CryptoBOMCsv(t *testing.T) {
ti := NewComponentTestInfoBasic(TEST_COMPONENT_LIST_CDX_1_6_CBOM, FORMAT_CSV, nil)
ti.ResultExpectedLineCount = 6 // title + 4 data + EOF LF
ti.ResultLineContainsValuesAtLineNum = 2
ti.ResultLineContainsValues = []string{"asset-2"}
innerTestComponentList(t, ti)
innerTestComponentList(t, ti, COMPONENT_TEST_DEFAULT_FLAGS)
}

func TestComponentListCdx16MachineLearningBOMCsv(t *testing.T) {
ti := NewComponentTestInfoBasic(TEST_COMPONENT_LIST_CDX_1_6_MLBOM, FORMAT_CSV, nil)
ti.ResultExpectedLineCount = 3 // title + 1 data + EOF LF
ti.ResultLineContainsValuesAtLineNum = 1
ti.ResultLineContainsValues = []string{"Llama-2-7b"}
innerTestComponentList(t, ti)
innerTestComponentList(t, ti, COMPONENT_TEST_DEFAULT_FLAGS)
}

// ./sbom-utility component list -i test/cyclonedx/cdx-1-3-resource-list.json --where "number-licenses=0" --quiet --format=txt
Expand All @@ -201,25 +203,25 @@ func TestComponentListCdx13WhereNumLicensesCsv(t *testing.T) {
ti.ResultExpectedLineCount = 3 // title + 1 data + EOF LF
ti.ResultLineContainsValuesAtLineNum = 1
ti.ResultLineContainsValues = []string{"NoLicense"}
innerTestComponentList(t, ti)
innerTestComponentList(t, ti, COMPONENT_TEST_DEFAULT_FLAGS)
}

func TestComponentListCdx16ValidBom(t *testing.T) {
ti := NewComponentTestInfoBasic(TEST_CDX_SPEC_1_6_VALID_BOM, FORMAT_CSV, nil)
innerTestComponentList(t, ti)
innerTestComponentList(t, ti, COMPONENT_TEST_DEFAULT_FLAGS)
}

func TestComponentListCdx16ValidComponentIds(t *testing.T) {
ti := NewComponentTestInfoBasic(TEST_CDX_SPEC_1_6_VALID_COMPONENT_IDS, FORMAT_CSV, nil)
innerTestComponentList(t, ti)
innerTestComponentList(t, ti, COMPONENT_TEST_DEFAULT_FLAGS)
}

func TestComponentListCdx16ValidComponentSwid(t *testing.T) {
ti := NewComponentTestInfoBasic(TEST_CDX_SPEC_1_6_VALID_SWID, FORMAT_CSV, nil)
innerTestComponentList(t, ti)
innerTestComponentList(t, ti, COMPONENT_TEST_DEFAULT_FLAGS)
}

func TestComponentListCdx16ValidComponentTypes(t *testing.T) {
ti := NewComponentTestInfoBasic(TEST_CDX_SPEC_1_6_VALID_COMPONENT_TYPES, FORMAT_CSV, nil)
innerTestComponentList(t, ti)
innerTestComponentList(t, ti, COMPONENT_TEST_DEFAULT_FLAGS)
}
Loading

0 comments on commit 03b51e6

Please sign in to comment.