Skip to content

Commit 19bed29

Browse files
committed
added find flag
1 parent 426f4b7 commit 19bed29

File tree

6 files changed

+162
-1
lines changed

6 files changed

+162
-1
lines changed

cmd/bom/cmd/document_outline.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ set the --spdx-ids to only output the IDs of the entities.
5454
if err != nil {
5555
return fmt.Errorf("opening doc: %w", err)
5656
}
57+
if outlineOpts.Find != "" {
58+
doc.FilterReverseDependencies(outlineOpts.Find, outlineOpts.Recursion)
59+
}
5760
output, err := doc.Outline(outlineOpts)
5861
if err != nil {
5962
return fmt.Errorf("generating document outline: %w", err)
@@ -89,6 +92,13 @@ set the --spdx-ids to only output the IDs of the entities.
8992
false,
9093
"show package urls instead of name@version",
9194
)
95+
outlineCmd.PersistentFlags().StringVarP(
96+
&outlineOpts.Find,
97+
"find",
98+
"f",
99+
"",
100+
"Find node in DAG",
101+
)
92102

93103
parent.AddCommand(outlineCmd)
94104
}

pkg/spdx/document.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ type DrawingOptions struct {
131131
ASCIIOnly bool
132132
Purls bool
133133
Version bool
134+
Find string
134135
}
135136

136137
// String returns the SPDX string of the external document ref.
@@ -313,6 +314,19 @@ func treeLines(o *DrawingOptions, depth int, connector string) string {
313314
return res
314315
}
315316

317+
// FilterReverseDependencies filters the document to only include direct paths to
318+
// Objects with the name name. Hence finding that Object's reverse dependencies.
319+
func (d *Document) FilterReverseDependencies(name string, depth int) {
320+
keepPackages := map[string]*Package{}
321+
for packageName, p := range d.Packages {
322+
if !recursiveNameFilter(name, p, depth, &map[string]bool{}) {
323+
continue
324+
}
325+
keepPackages[packageName] = p
326+
}
327+
d.Packages = keepPackages
328+
}
329+
316330
// Outline draws an outline of the relationships inside the doc.
317331
func (d *Document) Outline(o *DrawingOptions) (outline string, err error) {
318332
seen := map[string]struct{}{}

pkg/spdx/object.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,11 @@ type Object interface {
4646
SetEntity(*Entity)
4747
AddRelationship(*Relationship)
4848
GetRelationships() *[]*Relationship
49+
FilterRelationships(filter func(r *Relationship) bool)
4950
ToProvenanceSubject() *intoto.Subject
5051
getProvenanceSubjects(opts *ProvenanceOptions, seen *map[string]struct{}) []intoto.Subject
5152
GetElementByID(string) Object
53+
GetName() string
5254
}
5355

5456
type Entity struct {
@@ -79,6 +81,11 @@ func (e *Entity) SPDXID() string {
7981
return e.ID
8082
}
8183

84+
// GetName returns the Objects name as a string.
85+
func (e *Entity) GetName() string {
86+
return e.Name
87+
}
88+
8289
// SPDXID returns the SPDX reference string for the object.
8390
func (e *Entity) SetSPDXID(id string) {
8491
e.ID = id
@@ -155,6 +162,21 @@ func (e *Entity) GetRelationships() *[]*Relationship {
155162
return &e.Relationships
156163
}
157164

165+
// FilterRelationships filters relationships according to a filter function.
166+
// On a true the relationship is kept, on a false the relationship is dropped.
167+
func (e *Entity) FilterRelationships(filter func(r *Relationship) bool) {
168+
keepRelationships := []*Relationship{}
169+
for _, rel := range e.Relationships {
170+
if filter(rel) {
171+
keepRelationships = append(keepRelationships, rel)
172+
}
173+
}
174+
if len(keepRelationships) == 0 {
175+
keepRelationships = nil
176+
}
177+
e.Relationships = keepRelationships
178+
}
179+
158180
// ToProvenanceSubject converts the element to an intoto subject, suitable
159181
// to use inprovenance attestaions.
160182
func (e *Entity) ToProvenanceSubject() *intoto.Subject {

pkg/spdx/package.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -401,7 +401,11 @@ func (p *Package) Draw(builder *strings.Builder, o *DrawingOptions, depth int, s
401401
connector = connectorL
402402
}
403403

404-
fmt.Fprintf(builder, treeLines(o, depth, connector)+"🔗 %d Relationships\n", len(p.Relationships))
404+
// not printed when find is set since relationships will be filtered out from the graph.
405+
if o.Find == "" {
406+
fmt.Fprintf(builder, treeLines(o, depth, connector)+"🔗 %d Relationships\n", len(p.Relationships))
407+
}
408+
405409
if depth >= o.Recursion && o.Recursion > 0 {
406410
fmt.Fprintln(builder, treeLines(o, depth-1, ""))
407411
return

pkg/spdx/spdx.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,32 @@ func Banner() string {
264264
return string(d)
265265
}
266266

267+
// recursiveNameFilter is a function that recursivley filters an objects peers inplace
268+
// to include only those that are on a direct path to another object with the queried name.
269+
// If one or more path is found it returns true.
270+
//
271+
//nolint:gocritic // seen is a pointer recursively populated
272+
func recursiveNameFilter(name string, o Object, depth int, seen *map[string]bool) bool {
273+
if o.GetName() == name {
274+
o.FilterRelationships(func(_ *Relationship) bool { return false })
275+
return true
276+
}
277+
// searched to the max depth and name not found
278+
if depth == 1 {
279+
return false
280+
}
281+
if out, ok := (*seen)[o.SPDXID()]; ok {
282+
return out
283+
}
284+
filter := func(r *Relationship) bool {
285+
return recursiveNameFilter(name, r.Peer, depth-1, seen)
286+
}
287+
o.FilterRelationships(filter)
288+
out := len(*o.GetRelationships()) != 0
289+
(*seen)[o.SPDXID()] = out
290+
return out
291+
}
292+
267293
// recursiveIDSearch is a function that recursively searches an object's peers
268294
// to find the specified SPDX ID. If found, returns a copy of the object.
269295
//

pkg/spdx/spdx_unit_test.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"io"
2626
"os"
2727
"path/filepath"
28+
"strings"
2829
"testing"
2930

3031
"github.com/stretchr/testify/require"
@@ -416,6 +417,90 @@ func TestIgnorePatterns(t *testing.T) {
416417
require.Len(t, p, 4)
417418
}
418419

420+
func TestRecursiveNameFilter(t *testing.T) {
421+
/*
422+
create the starting package structure
423+
424+
root-p
425+
|
426+
|-target-p-0
427+
| |-sub-p-0
428+
|
429+
|-sub-p-1
430+
| |-target-p-1
431+
| |-sub-p-2
432+
|
433+
|-sub-p-3
434+
*/
435+
packageIDs := []string{"root-p"}
436+
for i := range 3 {
437+
packageIDs = append(packageIDs, fmt.Sprintf("target-p-%d", i))
438+
}
439+
for i := range 7 {
440+
packageIDs = append(packageIDs, fmt.Sprintf("sub-p-%d", i))
441+
}
442+
edges := []struct {
443+
p string
444+
c string
445+
}{
446+
{"root-p", "target-p-0"},
447+
{"root-p", "sub-p-1"},
448+
{"root-p", "sub-p-3"},
449+
{"target-p-0", "sub-p-0"},
450+
{"sub-p-1", "target-p-1"},
451+
{"sub-p-1", "sub-p-2"},
452+
}
453+
packages := map[string]*Package{}
454+
for _, id := range packageIDs {
455+
p := NewPackage()
456+
p.SetSPDXID(id)
457+
p.Name = strings.Join(strings.Split(id, "-")[:2], "-")
458+
packages[id] = p
459+
}
460+
461+
for _, edge := range edges {
462+
require.NoError(t, packages[edge.p].AddPackage(packages[edge.c]))
463+
}
464+
465+
/*
466+
create the expected package structure
467+
468+
root-p
469+
|
470+
|-target-p-0
471+
*/
472+
ePackageIDs := []string{"root-p", "target-p-0"}
473+
eEdges := []struct {
474+
p string
475+
c string
476+
}{
477+
{"root-p", "target-p-0"},
478+
}
479+
ePackages := map[string]*Package{}
480+
for _, id := range ePackageIDs {
481+
p := NewPackage()
482+
p.SetSPDXID(id)
483+
p.Name = strings.Join(strings.Split(id, "-")[:2], "-")
484+
ePackages[id] = p
485+
}
486+
487+
for _, edge := range eEdges {
488+
require.NoError(t, ePackages[edge.p].AddPackage(ePackages[edge.c]))
489+
}
490+
491+
// filter the starting packages
492+
ok := recursiveNameFilter(
493+
"target-p",
494+
packages["root-p"],
495+
2,
496+
&map[string]bool{},
497+
)
498+
require.True(t, ok)
499+
500+
// check filtered == expected
501+
require.Equal(t, ePackages["root-p"], packages["root-p"])
502+
}
503+
419504
func TestRecursiveSearch(t *testing.T) {
420505
p := NewPackage()
421506
p.SetSPDXID("p-top")

0 commit comments

Comments
 (0)