Skip to content

Commit edc06ac

Browse files
authored
Improve resource list command buy adding group and description columns (#84)
* Add columns and to resource list command Signed-off-by: Matt Rutkowski <[email protected]> * reorder default columns Signed-off-by: Matt Rutkowski <[email protected]> * Fix JSON map key for CDXServiceData.Flow Signed-off-by: Matt Rutkowski <[email protected]> * Support ResourceInfo as embedded struct in ComponentInfo Signed-off-by: Matt Rutkowski <[email protected]> * Fix typo. in JSON test files Signed-off-by: Matt Rutkowski <[email protected]> * Add group to resource list testcase Signed-off-by: Matt Rutkowski <[email protected]> * Add group, version to resource list default sort Signed-off-by: Matt Rutkowski <[email protected]> * Adjust test case to reflect new resource sort ordering Signed-off-by: Matt Rutkowski <[email protected]> * Fix trace output for hasmap Put() Signed-off-by: Matt Rutkowski <[email protected]> * Fix trace output for hasmap Put() Signed-off-by: Matt Rutkowski <[email protected]> * Update README to reflect new columns on resource list command Signed-off-by: Matt Rutkowski <[email protected]> * Create reusable methods to map CDX types to hash types Signed-off-by: Matt Rutkowski <[email protected]> --------- Signed-off-by: Matt Rutkowski <[email protected]>
1 parent b7c6487 commit edc06ac

13 files changed

+220
-178
lines changed

README.md

+34-27
Original file line numberDiff line numberDiff line change
@@ -1276,21 +1276,22 @@ Currently, all `resource list` command results are sorted by resource `type` the
12761276
```
12771277

12781278
```bash
1279-
type name version bom-ref
1280-
---- ---- ------- -------
1281-
component ACME Application 2.0.0 pkg:app/[email protected]
1282-
component Library A 1.0.0 pkg:lib/[email protected]
1283-
component Library B 1.0.0 pkg:lib/[email protected]
1284-
component Library C 1.0.0 pkg:lib/[email protected]
1285-
component Library D 1.0.0 pkg:lib/[email protected]
1286-
component Library E 1.0.0 pkg:lib/[email protected]
1287-
component Library F 1.0.0 pkg:lib/[email protected]
1288-
component Library G 1.0.0 pkg:lib/[email protected]
1289-
component Library H 1.0.0 pkg:lib/[email protected]
1290-
component Library J 1.0.0 pkg:lib/[email protected]
1291-
component Library NoLicense 1.0.0 pkg:lib/[email protected]
1292-
service Bar service:example.com/myservices/bar
1293-
service Foo service:example.com/myservices/foo
1279+
resource-type group name version description bom-ref
1280+
------------- ----- ---- ------- ----------- -------
1281+
component ACME Application 2.0.0 ACME sample application pkg:app/[email protected]
1282+
component Library A 1.0.0 Library A description pkg:lib/[email protected]
1283+
component Library C 1.0.0 Library C description. pkg:lib/[email protected]
1284+
component Library F 1.0.0 Library F description. pkg:lib/[email protected]
1285+
component Library G 1.0.0 Library G description. pkg:lib/[email protected]
1286+
component Library H 1.0.0 Library H description. pkg:lib/[email protected]
1287+
component Library NoLicense 1.0.0 Library "NoLicense" description. pkg:lib/[email protected]
1288+
component blue Library B 1.0.0 Library B description. pkg:lib/[email protected]
1289+
component blue Library E 1.0.0 Library E description. pkg:lib/[email protected]
1290+
component green Library D 1.0.0 Library D description. pkg:lib/[email protected]
1291+
component green Library J 1.0.0 Library J description. pkg:lib/[email protected]
1292+
service Bar Bar service service:example.com/myservices/bar
1293+
service Foo Foo service service:example.com/myservices/foo
1294+
12941295
```
12951296

12961297
##### Example: resource list using `--type service`
@@ -1302,10 +1303,16 @@ This example uses the `type` flag to specific `service`. The other valid type i
13021303
```
13031304

13041305
```bash
1305-
type name version bom-ref
1306-
---- ---- ------- -------
1307-
service Bar service:example.com/myservices/bar
1308-
service Foo service:example.com/myservices/foo
1306+
resource-type group name version description bom-ref
1307+
------------- ----- ---- ------- ----------- -------
1308+
service Bar Bar service service:example.com/myservices/bar
1309+
service Foo Foo service service:example.com/myservices/foo
1310+
```
1311+
1312+
**Note** The results would be equivalent to using the `--where` filter:
1313+
1314+
```bash
1315+
./sbom-utility resource list -i test/cyclonedx/cdx-1-3-resource-list.json --where "resource-type=service" --quiet
13091316
```
13101317

13111318
##### Example: list with `name` regex match
@@ -1317,20 +1324,16 @@ This example uses the `where` filter on the `name` field. In this case we supply
13171324
```
13181325

13191326
```bash
1320-
type name version bom-ref
1321-
---- ---- ------- -------
1322-
component Library A 1.0.0 pkg:lib/[email protected]
1327+
resource-type group name version description bom-ref
1328+
------------- ----- ---- ------- ----------- -------
1329+
component Library A 1.0.0 Library A description pkg:lib/[email protected]
13231330
```
13241331

13251332
---
13261333

13271334
### Schema
13281335

1329-
You can verify which formats, schemas, versions and variants are available for validation by using the `schema` command:
1330-
1331-
```bash
1332-
./sbom-utility schema list
1333-
```
1336+
You can verify which formats, schemas, versions and variants are available for validation by using the `schema` command.
13341337

13351338
- **Note**: The `schema` command will default to the `list` subcommand if omitted.
13361339

@@ -1348,6 +1351,10 @@ This command supports the `--format` flag with any of the following values:
13481351

13491352
##### Example: schema list
13501353

1354+
```bash
1355+
./sbom-utility schema list -q
1356+
```
1357+
13511358
```bash
13521359
name variant format version file url
13531360
---- ------- ------ ------- ---- ---

cmd/component.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -235,8 +235,8 @@ func loadDocumentComponents(document *schema.BOM, whereFilters []common.WhereFil
235235
func sortComponents(entries []multimap.Entry) {
236236
// Sort by Type then Name
237237
sort.Slice(entries, func(i, j int) bool {
238-
resource1 := (entries[i].Value).(schema.CDXResourceInfo)
239-
resource2 := (entries[j].Value).(schema.CDXResourceInfo)
238+
resource1 := (entries[i].Value).(schema.CDXComponentInfo)
239+
resource2 := (entries[j].Value).(schema.CDXComponentInfo)
240240
if resource1.ResourceType != resource2.ResourceType {
241241
return resource1.ResourceType < resource2.ResourceType
242242
}
@@ -265,7 +265,7 @@ func DisplayComponentListText(bom *schema.BOM, writer io.Writer) (err error) {
265265
fmt.Fprintf(w, "%s\n", strings.Join(underlines, "\t"))
266266

267267
// Display a warning "missing" in the actual output and return (short-circuit)
268-
entries := bom.ResourceMap.Entries()
268+
entries := bom.ComponentMap.Entries()
269269

270270
// Emit no license warning into output
271271
if len(entries) == 0 {
@@ -280,7 +280,7 @@ func DisplayComponentListText(bom *schema.BOM, writer io.Writer) (err error) {
280280
var line []string
281281
for _, entry := range entries {
282282
line, err = prepareReportLineData(
283-
entry.Value.(schema.CDXResourceInfo),
283+
entry.Value.(schema.CDXComponentInfo),
284284
COMPONENT_LIST_ROW_DATA,
285285
true,
286286
)

cmd/errors.go

+14-7
Original file line numberDiff line numberDiff line change
@@ -46,24 +46,31 @@ const (
4646
ERR_TYPE_IETF_RFC6902_TEST_FAILED = "IETF RFC6902 test operation error"
4747
)
4848

49-
// Validation messages
49+
// Custom Validation messages
50+
// TODO: Need to define a profile that supports these validation checks/messages
5051
const (
51-
MSG_FORMAT_TYPE = "format: `%s`"
52-
MSG_SCHEMA_ERRORS = "schema errors found"
52+
MSG_PROPERTY_NOT_UNIQUE = "check failed: property not unique"
5353
MSG_INVALID_METADATA_PROPERTIES = "field `metadata.properties` is missing or invalid"
5454
MSG_INVALID_METADATA_COMPONENT_COMPONENTS = "field `metadata.component.components` array should be empty"
5555
MSG_INVALID_METADATA_COMPONENT = "field `metadata.component` is missing or invalid"
56-
MSG_PROPERTY_NOT_FOUND = "property not found"
57-
MSG_PROPERTY_NOT_UNIQUE = "check failed: property not unique"
58-
MSG_PROPERTY_REGEX_FAILED = "check failed: property regex mismatch"
59-
MSG_IETF_RFC6902_OPERATION_SUCCESS = "IETF RFC6902 test operation success"
56+
)
57+
58+
// Validation messages
59+
const (
60+
MSG_FORMAT_TYPE = "format: `%s`"
61+
MSG_SCHEMA_ERRORS = "schema errors found"
62+
MSG_PROPERTY_NOT_FOUND = "property not found"
63+
MSG_PROPERTY_REGEX_FAILED = "check failed: property regex mismatch"
64+
MSG_IETF_RFC6902_OPERATION_SUCCESS = "IETF RFC6902 test operation success"
6065
)
6166

6267
// License messages
6368
const (
6469
MSG_LICENSE_INVALID_DATA = "invalid license data"
6570
MSG_LICENSE_INVALID_POLICY = "invalid license policy"
71+
MSG_LICENSE_NOT_FOUND = "license not found"
6672
MSG_LICENSES_NOT_FOUND = "licenses not found"
73+
MSG_LICENSE_HASH_ERROR = "hash of license failed"
6774
)
6875

6976
// Query error details

cmd/license.go

+33-69
Original file line numberDiff line numberDiff line change
@@ -129,28 +129,33 @@ func loadDocumentLicenses(bom *schema.BOM, policyConfig *schema.LicensePolicyCon
129129
return
130130
}
131131
}
132-
133132
return
134133
}
135134

135+
// Note: An actual error SHOULD ONLY be returned by the custom validation code.
136+
func warnNoLicenseFound(bom *schema.BOM, location int) {
137+
message := fmt.Sprintf("%s (%s)",
138+
MSG_LICENSES_NOT_FOUND, // "licenses not found"
139+
schema.GetLicenseChoiceLocationName(location))
140+
sbomError := NewInvalidSBOMError(bom, message, nil, nil)
141+
getLogger().Warning(sbomError)
142+
}
143+
144+
func warnInvalidResourceLicense(resourceType string, bomRef string, name string, version string) {
145+
getLogger().Warningf("%s. resourceType: `%s`: bomRef: `%s`, name:`%s`, version: `%s`",
146+
MSG_LICENSE_NOT_FOUND,
147+
resourceType, bomRef, name, version)
148+
}
149+
136150
// Hash the license found in the (root).metadata.licenses[] array
137151
func hashMetadataLicenses(bom *schema.BOM, policyConfig *schema.LicensePolicyConfig, location int, whereFilters []common.WhereFilter, licenseFlags utils.LicenseCommandFlags) (err error) {
138152
getLogger().Enter()
139153
defer getLogger().Exit(err)
140154

141155
pLicenses := bom.GetCdxMetadataLicenses()
156+
// Issue a warning that the SBOM does not declare at least one, top-level component license.
142157
if pLicenses == nil {
143-
sbomError := NewInvalidSBOMError(
144-
bom,
145-
fmt.Sprintf("%s (%s)",
146-
MSG_LICENSES_NOT_FOUND,
147-
schema.GetLicenseChoiceLocationName(location)),
148-
nil, nil)
149-
// Issue a warning as an SBOM without at least one, top-level license
150-
// (in the metadata license summary) SHOULD be noted.
151-
// Note: An actual error SHOULD ONLY be returned by
152-
// the custom validation code.
153-
getLogger().Warning(sbomError)
158+
warnNoLicenseFound(bom, location)
154159
return
155160
}
156161

@@ -168,7 +173,6 @@ func hashMetadataLicenses(bom *schema.BOM, policyConfig *schema.LicensePolicyCon
168173
return
169174
}
170175
}
171-
172176
return
173177
}
174178

@@ -179,22 +183,10 @@ func hashMetadataComponentLicenses(bom *schema.BOM, policyConfig *schema.License
179183

180184
component := bom.GetCdxMetadataComponent()
181185
if component == nil {
182-
sbomError := NewInvalidSBOMError(
183-
bom,
184-
fmt.Sprintf("%s (%s)",
185-
MSG_LICENSES_NOT_FOUND,
186-
schema.GetLicenseChoiceLocationName(location)),
187-
nil, nil)
188-
// Issue a warning as an SBOM without at least one
189-
// top-level component license declared SHOULD be noted.
190-
// Note: An actual error SHOULD ONLY be returned by
191-
// the custom validation code.
192-
getLogger().Warning(sbomError)
186+
warnNoLicenseFound(bom, location)
193187
return
194188
}
195-
196189
_, err = hashComponentLicense(bom, policyConfig, *component, location, whereFilters, licenseFlags)
197-
198190
return
199191
}
200192

@@ -240,38 +232,23 @@ func hashComponentLicense(bom *schema.BOM, policyConfig *schema.LicensePolicyCon
240232
getLogger().Debugf("licenseChoice: %s", getLogger().FormatStruct(licenseChoice))
241233
getLogger().Tracef("hashing license for component=`%s`", cdxComponent.Name)
242234

243-
licenseInfo.LicenseChoice = licenseChoice
244-
licenseInfo.Component = cdxComponent
245-
licenseInfo.BOMLocationValue = location
246-
licenseInfo.ResourceName = cdxComponent.Name
247-
if cdxComponent.BOMRef != nil {
248-
licenseInfo.BOMRef = *cdxComponent.BOMRef
249-
}
235+
licenseInfo = *schema.NewLicenseInfoFromComponent(cdxComponent, licenseChoice, location)
250236
err = hashLicenseInfoByLicenseType(bom, policyConfig, licenseInfo, whereFilters, licenseFlags)
251237

252238
if err != nil {
253239
// Show intent to not check for error returns as there no intent to recover
254-
_ = getLogger().Errorf("Unable to hash empty license: %v", licenseInfo)
240+
_ = getLogger().Errorf("%s. license: %+v", MSG_LICENSE_HASH_ERROR, licenseInfo)
255241
return
256242
}
257243
}
258244
} else {
259245
// Account for component with no license with an "UNDEFINED" entry
260-
// hash any component w/o a license using special key name
261-
licenseInfo.Component = cdxComponent
262-
licenseInfo.BOMLocationValue = location
263-
licenseInfo.ResourceName = cdxComponent.Name
264-
if cdxComponent.BOMRef != nil {
265-
licenseInfo.BOMRef = *cdxComponent.BOMRef
266-
}
246+
licenseInfo = *schema.NewLicenseInfoFromComponent(cdxComponent, schema.CDXLicenseChoice{}, location)
267247
_, err = bom.HashmapLicenseInfo(policyConfig, LICENSE_NO_ASSERTION, licenseInfo, whereFilters, licenseFlags)
268248

269-
getLogger().Warningf("%s: %s (name:`%s`, version: `%s`, package-url: `%s`)",
270-
"No license found for component. bomRef",
271-
licenseInfo.BOMRef,
272-
licenseInfo.ResourceName,
273-
cdxComponent.Version,
274-
cdxComponent.Purl)
249+
// Issue a warning that the component had no license; use "safe" BOMRef string value
250+
// TODO: flag component for stats. purposes
251+
warnInvalidResourceLicense(schema.RESOURCE_TYPE_COMPONENT, licenseInfo.BOMRef.String(), cdxComponent.Name, cdxComponent.Version)
275252
// No actual licenses to process
276253
return
277254
}
@@ -284,7 +261,6 @@ func hashComponentLicense(bom *schema.BOM, policyConfig *schema.LicensePolicyCon
284261
return
285262
}
286263
}
287-
288264
return
289265
}
290266

@@ -301,35 +277,23 @@ func hashServiceLicense(bom *schema.BOM, policyConfig *schema.LicensePolicyConfi
301277
for _, licenseChoice := range *pLicenses {
302278
getLogger().Debugf("licenseChoice: %s", getLogger().FormatStruct(licenseChoice))
303279
getLogger().Tracef("Hashing license for service=`%s`", cdxService.Name)
304-
licenseInfo.LicenseChoice = licenseChoice
305-
licenseInfo.Service = cdxService
306-
licenseInfo.ResourceName = cdxService.Name
307-
if cdxService.BOMRef != nil {
308-
licenseInfo.BOMRef = *cdxService.BOMRef
309-
}
310-
licenseInfo.BOMLocationValue = location
280+
licenseInfo = *schema.NewLicenseInfoFromService(cdxService, licenseChoice, location)
311281
err = hashLicenseInfoByLicenseType(bom, policyConfig, licenseInfo, whereFilters, licenseFlags)
312-
313282
if err != nil {
283+
// Show intent to not check for error returns as there no intent to recover
284+
_ = getLogger().Errorf("%s. license: %+v", MSG_LICENSE_HASH_ERROR, licenseInfo)
314285
return
315286
}
316287
}
317288
} else {
318289
// Account for service with no license with an "UNDEFINED" entry
319290
// hash any service w/o a license using special key name
320-
licenseInfo.Service = cdxService
321-
licenseInfo.BOMLocationValue = location
322-
licenseInfo.ResourceName = cdxService.Name
323-
if cdxService.BOMRef != nil {
324-
licenseInfo.BOMRef = *cdxService.BOMRef
325-
}
291+
licenseInfo = *schema.NewLicenseInfoFromService(cdxService, schema.CDXLicenseChoice{}, location)
326292
_, err = bom.HashmapLicenseInfo(policyConfig, LICENSE_NO_ASSERTION, licenseInfo, whereFilters, licenseFlags)
327293

328-
getLogger().Warningf("%s: %s (name: `%s`, version: `%s`)",
329-
"No license found for service. bomRef",
330-
cdxService.BOMRef,
331-
cdxService.Name,
332-
cdxService.Version)
294+
// Issue a warning that the service had no license; use "safe" BOMRef string value
295+
// TODO: flag service for stats. purposes
296+
warnInvalidResourceLicense(schema.RESOURCE_TYPE_SERVICE, licenseInfo.BOMRef.String(), cdxService.Name, cdxService.Version)
333297

334298
// No actual licenses to process
335299
return
@@ -340,8 +304,8 @@ func hashServiceLicense(bom *schema.BOM, policyConfig *schema.LicensePolicyConfi
340304
if pServices != nil && len(*pServices) > 0 {
341305
err = hashServicesLicenses(bom, policyConfig, *pServices, location, whereFilters, licenseFlags)
342306
if err != nil {
343-
// Show intent to not check for error returns as there is no recovery
344-
_ = getLogger().Errorf("Unable to hash empty license: %v", licenseInfo)
307+
// Show intent to not check for error returns as there no intent to recover
308+
_ = getLogger().Errorf("%s. license: %+v", MSG_LICENSE_HASH_ERROR, licenseInfo)
345309
return
346310
}
347311
}

cmd/resource.go

+9-1
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,10 @@ const (
5252

5353
var RESOURCE_LIST_ROW_DATA = []ColumnFormatData{
5454
*NewColumnFormatData(RESOURCE_FILTER_KEY_RESOURCE_TYPE, DEFAULT_COLUMN_TRUNCATE_LENGTH, REPORT_SUMMARY_DATA_TRUE, false),
55+
*NewColumnFormatData(RESOURCE_FILTER_KEY_GROUP, DEFAULT_COLUMN_TRUNCATE_LENGTH, REPORT_SUMMARY_DATA_TRUE, REPORT_REPLACE_LINE_FEEDS_TRUE),
5556
*NewColumnFormatData(RESOURCE_FILTER_KEY_NAME, DEFAULT_COLUMN_TRUNCATE_LENGTH, REPORT_SUMMARY_DATA_TRUE, false),
5657
*NewColumnFormatData(RESOURCE_FILTER_KEY_VERSION, DEFAULT_COLUMN_TRUNCATE_LENGTH, REPORT_SUMMARY_DATA_TRUE, false),
58+
*NewColumnFormatData(RESOURCE_FILTER_KEY_DESCRIPTION, DEFAULT_COLUMN_TRUNCATE_LENGTH, REPORT_SUMMARY_DATA_TRUE, REPORT_REPLACE_LINE_FEEDS_TRUE),
5759
*NewColumnFormatData(RESOURCE_FILTER_KEY_BOMREF, DEFAULT_COLUMN_TRUNCATE_LENGTH, REPORT_SUMMARY_DATA_TRUE, REPORT_REPLACE_LINE_FEEDS_TRUE),
5860
}
5961

@@ -260,7 +262,13 @@ func sortResources(entries []multimap.Entry) {
260262
if resource1.ResourceType != resource2.ResourceType {
261263
return resource1.ResourceType < resource2.ResourceType
262264
}
263-
return resource1.Name < resource2.Name
265+
if resource1.Group != resource2.Group {
266+
return resource1.Group < resource2.Group
267+
}
268+
if resource1.Name != resource2.Name {
269+
return resource1.Name < resource2.Name
270+
}
271+
return resource1.Version < resource2.Version
264272
})
265273
}
266274

cmd/resource_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ func TestResourceListTextCdx13WhereClauseAndResultsBomRefContains(t *testing.T)
256256
EXPECTED_OUTPUT_LINE_COUNT,
257257
schema.RESOURCE_TYPE_COMPONENT)
258258
rti.ResultLineContainsValues = TEST_OUTPUT_CONTAINS
259-
rti.ResultLineContainsValuesAtLineNum = 10
259+
rti.ResultLineContainsValuesAtLineNum = 11
260260
innerTestResourceList(t, rti)
261261
}
262262

0 commit comments

Comments
 (0)