github.com/vchain-us/vcn@v0.9.11-0.20210921212052-a2484d23c0b3/pkg/bom/spdx.go (about) 1 /* 2 * Copyright (c) 2021 CodeNotary, Inc. All Rights Reserved. 3 * This software is released under GPL3. 4 * The full license information can be found under: 5 * https://www.gnu.org/licenses/gpl-3.0.en.html 6 * 7 */ 8 9 package bom 10 11 import ( 12 "fmt" 13 "os" 14 "path/filepath" 15 "strconv" 16 "time" 17 18 "github.com/google/uuid" 19 20 "github.com/vchain-us/vcn/pkg/bom/artifact" 21 ) 22 23 const ( 24 optional = iota 25 mandatory 26 ) 27 28 type headerLine struct { 29 tag string 30 presense int 31 fn func(artifact.Artifact) (string, error) 32 } 33 34 var headerContent = []headerLine{ 35 {"SPDXVersion", mandatory, func(artifact.Artifact) (string, error) { 36 return "SPDX-2.2", nil 37 }}, 38 {"DataLicense", mandatory, func(artifact.Artifact) (string, error) { 39 return "CC0-1.0", nil 40 }}, 41 {"SPDXID", mandatory, func(artifact.Artifact) (string, error) { 42 return "SPDXRef-DOCUMENT", nil 43 }}, 44 {"DocumentName", mandatory, func(p artifact.Artifact) (string, error) { 45 path, err := filepath.Abs(p.Path()) 46 if err != nil { 47 return "", err 48 } 49 return filepath.Base(path), nil 50 }}, 51 {"DocumentNamespace", mandatory, func(p artifact.Artifact) (string, error) { 52 path, err := filepath.Abs(p.Path()) 53 if err != nil { 54 return "", err 55 } 56 return "http://spdx.org/spdxdocs/" + filepath.Base(path) + "-" + uuid.NewString(), nil 57 }}, 58 {"Creator", mandatory, func(artifact.Artifact) (string, error) { 59 return "Tool: CodeNotary vcn", nil 60 }}, 61 {"Created", mandatory, func(artifact.Artifact) (string, error) { 62 return time.Now().UTC().Format(time.RFC3339), nil 63 }}, 64 } 65 66 type componentLine struct { 67 tag string 68 presense int 69 fn func(artifact.Artifact, artifact.Dependency, int) (string, error) 70 } 71 72 var componentContent = []componentLine{ 73 {"PackageName", mandatory, func(a artifact.Artifact, d artifact.Dependency, seq int) (string, error) { 74 return d.Name, nil 75 }}, 76 {"SPDXID", mandatory, func(a artifact.Artifact, d artifact.Dependency, seq int) (string, error) { 77 return "SPDXRef-Package-" + strconv.Itoa(seq), nil 78 }}, 79 {"PackageVersion", mandatory, func(a artifact.Artifact, d artifact.Dependency, seq int) (string, error) { 80 return d.Version, nil 81 }}, 82 {"PackageDownloadLocation", mandatory, noAssertion}, 83 // FilesAnalysed is optional, but by default it is true, which requires presence of many other fields 84 {"FilesAnalyzed", mandatory, func(a artifact.Artifact, d artifact.Dependency, seq int) (string, error) { 85 return "false", nil 86 }}, 87 {"PackageChecksum", mandatory, func(a artifact.Artifact, d artifact.Dependency, seq int) (string, error) { 88 return artifact.HashTypeName(d.HashType) + ": " + d.Hash, nil 89 }}, 90 {"PackageSourceInfo", optional, func(a artifact.Artifact, d artifact.Dependency, seq int) (string, error) { 91 return "<text>" + Purl(a, d) + "</text>", nil 92 }}, 93 {"PackageLicenseConcluded", mandatory, func(a artifact.Artifact, d artifact.Dependency, seq int) (string, error) { 94 if d.License != "" { 95 return d.License, nil 96 } 97 return "NOASSERTION", nil 98 }}, 99 {"PackageLicenseDeclared", mandatory, noAssertion}, 100 {"PackageCopyrightText", mandatory, noAssertion}, 101 {"PackageComment", optional, func(a artifact.Artifact, d artifact.Dependency, seq int) (string, error) { 102 l := artifact.TrustLevelName(d.TrustLevel) 103 if l != "" { 104 return "<text>" + artifact.TrustLevelName(d.TrustLevel) + ", " + DepLinkType(a, d) + "</text>", nil 105 } 106 return "", nil 107 }}, 108 } 109 110 func noAssertion(a artifact.Artifact, d artifact.Dependency, seq int) (string, error) { 111 return "NOASSERTION", nil 112 } 113 114 // Output info about package and its components in SPDX text (tag:value) format, according to 115 // SPDX spec 2.2: https://spdx.dev/wp-content/uploads/sites/41/2020/08/SPDX-specification-2-2.pdf 116 func OutputSpdxText(a artifact.Artifact, filename string) error { 117 deps := a.Dependencies() 118 f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) 119 if err != nil { 120 return err 121 } 122 123 defer f.Close() 124 125 // SPDX header 126 for _, line := range headerContent { 127 value, err := line.fn(a) 128 if err != nil { 129 if line.presense == mandatory { 130 return fmt.Errorf("cannot get value for tag %s: %w", line.tag, err) 131 } 132 continue // optional tag - ignore error 133 } 134 if value == "" { 135 if line.presense == mandatory { 136 return fmt.Errorf("no value found for mandatory header tag %s", line.tag) 137 } 138 continue // optional 139 } 140 if _, err = fmt.Fprintf(f, "%s: %s\n", line.tag, value); err != nil { 141 return err 142 } 143 } 144 145 if _, err = fmt.Fprintf(f, "\n##### Software components\n\n"); err != nil { 146 return err 147 } 148 149 for i, dep := range deps { 150 for _, line := range componentContent { 151 value, err := line.fn(a, dep, i+1) 152 if err != nil { 153 if line.presense == mandatory { 154 return fmt.Errorf("cannot get value for tag %s for component %s: %w", line.tag, dep.Name, err) 155 } 156 continue // optional tag - ignore error 157 } 158 if value == "" { 159 if line.presense == mandatory { 160 return fmt.Errorf("no value found for mandatory component tag %s for component %s", line.tag, dep.Name) 161 } 162 continue // optional 163 } 164 if _, err = fmt.Fprintf(f, "%s: %s\n", line.tag, value); err != nil { 165 return err 166 } 167 } 168 if _, err = fmt.Fprintln(f); err != nil { 169 return err 170 } 171 } 172 173 return nil 174 }