Skip to content

Commit 67a79e2

Browse files
committed
add bom document todot
1 parent 19bed29 commit 67a79e2

File tree

12 files changed

+311
-0
lines changed

12 files changed

+311
-0
lines changed

cmd/bom/cmd/document.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,6 @@ func AddDocument(parent *cobra.Command) {
3131

3232
AddOutline(documentCmd)
3333
AddQuery(documentCmd)
34+
AddToDot(documentCmd)
3435
parent.AddCommand(documentCmd)
3536
}

cmd/bom/cmd/document_todot.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
Copyright 2023 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package cmd
18+
19+
import (
20+
"fmt"
21+
22+
"github.com/spf13/cobra"
23+
24+
"sigs.k8s.io/bom/pkg/spdx"
25+
)
26+
27+
func AddToDot(parent *cobra.Command) {
28+
toDotOpts := &spdx.ToDotOptions{}
29+
toDotCmd := &cobra.Command{
30+
PersistentPreRunE: initLogging,
31+
Short: "bom document todot -> dump the SPDX document as dotlang.",
32+
Long: `bom document todot -> dump the SPDX document as dotlang.
33+
34+
This Subcommand translates the graph like structure of an spdx document into dotlang,
35+
An abstract grammar used to represent graphs https://graphviz.org/doc/info/lang.html.
36+
37+
This is printed to stdout but can easily be piped to a file like so.
38+
39+
bom document todot file.spdx > file.dot
40+
41+
The output can also be filtered by depth, (--depth), inverse dependencies (--find)
42+
or subgraph (--subgraph) to aid with visualisation by tools like Graphviz
43+
44+
bom will try to add useful information to dotlangs tooltip node attribute.
45+
`,
46+
Use: "todot SPDX_FILE|URL",
47+
SilenceUsage: true,
48+
SilenceErrors: true,
49+
RunE: func(_ *cobra.Command, args []string) error {
50+
if len(args) == 0 {
51+
args = append(args, "")
52+
}
53+
doc, err := spdx.OpenDoc(args[0])
54+
if err != nil {
55+
return fmt.Errorf("opening doc: %w", err)
56+
}
57+
if toDotOpts.Find != "" {
58+
doc.FilterReverseDependencies(toDotOpts.Find, toDotOpts.Depth)
59+
}
60+
fmt.Println(doc.ToDot(toDotOpts))
61+
return nil
62+
},
63+
}
64+
toDotCmd.PersistentFlags().StringVarP(
65+
&toDotOpts.Find,
66+
"find",
67+
"f",
68+
"",
69+
"Find node in DAG",
70+
)
71+
toDotCmd.PersistentFlags().IntVarP(
72+
&toDotOpts.Depth,
73+
"depth",
74+
"d",
75+
-1,
76+
"Depth to traverse",
77+
)
78+
toDotCmd.PersistentFlags().StringVarP(
79+
&toDotOpts.SubGraphRoot,
80+
"subgraph",
81+
"s",
82+
"",
83+
"SPDXID of the root node for the subgraph",
84+
)
85+
parent.AddCommand(toDotCmd)
86+
}

pkg/spdx/document.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,12 @@ type ExternalRef struct {
120120
Locator string // unique string with no spaces
121121
}
122122

123+
type ToDotOptions struct {
124+
Find string
125+
Depth int
126+
SubGraphRoot string
127+
}
128+
123129
type DrawingOptions struct {
124130
Width int
125131
Height int
@@ -276,6 +282,30 @@ func (d *Document) Render() (doc string, err error) {
276282
return doc, err
277283
}
278284

285+
func (d *Document) ToDot(o *ToDotOptions) string {
286+
out := ""
287+
if o.SubGraphRoot == "" {
288+
out = escape(d.Name) + ";\n"
289+
}
290+
seenFilter := &map[string]struct{}{}
291+
var ok bool
292+
for _, p := range d.Packages {
293+
if o.SubGraphRoot != "" {
294+
p, ok = recursiveIDSearch(o.SubGraphRoot, p, &map[string]struct{}{}).(*Package)
295+
if p == nil {
296+
continue
297+
}
298+
if !ok {
299+
log.Fatal("Interface object is not of expected type Package")
300+
}
301+
} else {
302+
out += escape(d.Name) + " -> " + escape(p.SPDXID()) + ";\n"
303+
}
304+
out += toDot(p, o.Depth, seenFilter)
305+
}
306+
return fmt.Sprintf("digraph {\n%s\n}\n", out)
307+
}
308+
279309
// AddFile adds a file contained in the package.
280310
func (d *Document) AddFile(file *File) error {
281311
if d.Files == nil {

pkg/spdx/file.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ type File struct {
6161
LicenseInfoInFile string // GPL-3.0-or-later
6262
}
6363

64+
func (f *File) ToDot() string {
65+
return fmt.Sprintf("%q", f.SPDXID())
66+
}
67+
6468
func NewFile() (f *File) {
6569
f = &File{}
6670
f.Opts = &ObjectOptions{}

pkg/spdx/json/document/document.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ type Package interface {
6464
GetCopyrightText() string
6565
GetLicenseConcluded() string
6666
GetFilesAnalyzed() bool
67+
GetVendorInfo() string
68+
GetSourceInfo() string
6769
GetLicenseDeclared() string
6870
GetVersion() string
6971
GetVerificationCode() PackageVerificationCode

pkg/spdx/json/v2.2/types.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ type Package struct {
9999
Originator string `json:"originator,omitempty"`
100100
Supplier string `json:"supplier,omitempty"`
101101
SourceInfo string `json:"sourceInfo,omitempty"`
102+
VendorInfo string `json:"vendorInfo,omitempty"`
102103
CopyrightText string `json:"copyrightText"`
103104
HasFiles []string `json:"hasFiles,omitempty"`
104105
LicenseInfoFromFiles []string `json:"licenseInfoFromFiles,omitempty"`
@@ -117,6 +118,8 @@ func (p *Package) GetLicenseDeclared() string { return p.LicenseDeclared }
117118
func (p *Package) GetVersion() string { return p.Version }
118119
func (p *Package) GetPrimaryPurpose() string { return "" }
119120
func (p *Package) GetSupplier() string { return p.Supplier }
121+
func (p *Package) GetVendorInfo() string { return p.VendorInfo }
122+
func (p *Package) GetSourceInfo() string { return p.SourceInfo }
120123
func (p *Package) GetOriginator() string { return p.Originator }
121124

122125
func (p *Package) GetVerificationCode() document.PackageVerificationCode {

pkg/spdx/json/v2.3/types.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ type Package struct {
9999
Originator string `json:"originator,omitempty"`
100100
Supplier string `json:"supplier,omitempty"`
101101
SourceInfo string `json:"sourceInfo,omitempty"`
102+
VendorInfo string `json:"vendorInfo,omitempty"`
102103
CopyrightText string `json:"copyrightText"`
103104
PrimaryPurpose string `json:"primaryPackagePurpose,omitempty"`
104105
HasFiles []string `json:"hasFiles,omitempty"`
@@ -118,6 +119,8 @@ func (p *Package) GetLicenseDeclared() string { return p.LicenseDeclared }
118119
func (p *Package) GetVersion() string { return p.Version }
119120
func (p *Package) GetPrimaryPurpose() string { return p.PrimaryPurpose }
120121
func (p *Package) GetSupplier() string { return p.Supplier }
122+
func (p *Package) GetVendorInfo() string { return p.VendorInfo }
123+
func (p *Package) GetSourceInfo() string { return p.SourceInfo }
121124
func (p *Package) GetOriginator() string { return p.Originator }
122125

123126
func (p *Package) GetVerificationCode() document.PackageVerificationCode {

pkg/spdx/object.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ type Object interface {
5151
getProvenanceSubjects(opts *ProvenanceOptions, seen *map[string]struct{}) []intoto.Subject
5252
GetElementByID(string) Object
5353
GetName() string
54+
ToDot() string
5455
}
5556

5657
type Entity struct {

pkg/spdx/package.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ type Package struct {
9898
Comment string // a place for the SPDX document creator to record any general comments
9999
HomePage string // A web site that serves as the package home page
100100
PrimaryPurpose string // Estimate of the most likely package usage
101+
VendorInfo string
102+
SourceInfo string
101103

102104
// Supplier: the actual distribution source for the package/directory
103105
Supplier struct {
@@ -136,6 +138,29 @@ func NewPackage() (p *Package) {
136138
return p
137139
}
138140

141+
// ToDot returns a representation of the package as a dotlang node.
142+
func (p *Package) ToDot() string {
143+
packageData := structToString(p, true, "RWMutex", "Opts", "Relationships")
144+
145+
sURL := ""
146+
if url := p.Purl(); url != nil {
147+
sURL = fmt.Sprintf(`URL: %s\n`, url.ToString())
148+
}
149+
150+
name := p.Name
151+
if p.Version != "" {
152+
name = name + "@" + p.Version
153+
}
154+
155+
return fmt.Sprintf(
156+
`%q [label=%q tooltip="%s%s" fontname="monospace"]`,
157+
p.SPDXID(),
158+
name,
159+
packageData,
160+
sURL,
161+
)
162+
}
163+
139164
// AddFile adds a file contained in the package.
140165
func (p *Package) AddFile(file *File) error {
141166
p.Lock()

pkg/spdx/parser.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,8 @@ func parseJSON(file *os.File) (doc *Document, err error) {
251251
LicenseInfoFromFiles: []string{},
252252
LicenseDeclared: pData.GetLicenseDeclared(),
253253
Version: pData.GetVersion(),
254+
VendorInfo: pData.GetVendorInfo(),
255+
SourceInfo: pData.GetSourceInfo(),
254256
VerificationCode: pData.GetVerificationCode().GetValue(),
255257
// Comment: pData.Comment,
256258
// HomePage: pData.HomePage,

0 commit comments

Comments
 (0)