github.com/anchore/syft@v1.4.2-0.20240516191711-1bec1fc5d397/syft/format/encoders_collection.go (about) 1 package format 2 3 import ( 4 "bytes" 5 "fmt" 6 "regexp" 7 "sort" 8 "strings" 9 10 "github.com/scylladb/go-set/strset" 11 12 "github.com/anchore/syft/internal/log" 13 "github.com/anchore/syft/syft/sbom" 14 ) 15 16 type EncoderCollection struct { 17 encoders []sbom.FormatEncoder 18 } 19 20 func NewEncoderCollection(encoders ...sbom.FormatEncoder) *EncoderCollection { 21 return &EncoderCollection{ 22 encoders: encoders, 23 } 24 } 25 26 // IDs returns all format IDs represented in the collection. 27 func (e EncoderCollection) IDs() []sbom.FormatID { 28 idSet := strset.New() 29 for _, f := range e.encoders { 30 idSet.Add(string(f.ID())) 31 } 32 33 idList := idSet.List() 34 sort.Strings(idList) 35 36 var ids []sbom.FormatID 37 for _, id := range idList { 38 ids = append(ids, sbom.FormatID(id)) 39 } 40 41 return ids 42 } 43 44 // NameVersions returns all formats that are supported by the collection as a list of "name@version" strings. 45 func (e EncoderCollection) NameVersions() []string { 46 set := strset.New() 47 for _, f := range e.encoders { 48 if f.Version() == sbom.AnyVersion { 49 set.Add(string(f.ID())) 50 } else { 51 set.Add(fmt.Sprintf("%s@%s", f.ID(), f.Version())) 52 } 53 } 54 55 list := set.List() 56 sort.Strings(list) 57 58 return list 59 } 60 61 // Aliases returns all format aliases represented in the collection (where an ID would be "spdx-tag-value" the alias would be "spdx"). 62 func (e EncoderCollection) Aliases() []string { 63 aliases := strset.New() 64 for _, f := range e.encoders { 65 aliases.Add(f.Aliases()...) 66 } 67 lst := aliases.List() 68 sort.Strings(lst) 69 return lst 70 } 71 72 // Get returns the contained encoder for a given format name and version. 73 func (e EncoderCollection) Get(name string, version string) sbom.FormatEncoder { 74 log.WithFields("name", name, "version", version).Trace("looking for matching encoder") 75 76 name = cleanFormatName(name) 77 var mostRecentFormat sbom.FormatEncoder 78 79 for _, f := range e.encoders { 80 log.WithFields("name", f.ID(), "version", f.Version(), "aliases", f.Aliases()).Trace("considering format") 81 names := []string{string(f.ID())} 82 names = append(names, f.Aliases()...) 83 for _, n := range names { 84 if cleanFormatName(n) == name && versionMatches(f.Version(), version) { 85 if mostRecentFormat == nil || f.Version() > mostRecentFormat.Version() { 86 mostRecentFormat = f 87 } 88 } 89 } 90 } 91 92 if mostRecentFormat != nil { 93 log.WithFields("name", mostRecentFormat.ID(), "version", mostRecentFormat.Version()).Trace("found matching encoder") 94 } else { 95 log.WithFields("search-name", name, "search-version", version).Trace("no matching encoder found") 96 } 97 98 return mostRecentFormat 99 } 100 101 // GetByString accepts a name@version string, such as: 102 // - json 103 // - spdx-json@2.1 104 // - cdx@1.5 105 func (e EncoderCollection) GetByString(s string) sbom.FormatEncoder { 106 parts := strings.SplitN(s, "@", 2) 107 version := sbom.AnyVersion 108 if len(parts) > 1 { 109 version = parts[1] 110 } 111 return e.Get(parts[0], version) 112 } 113 114 func versionMatches(version string, match string) bool { 115 if version == sbom.AnyVersion || match == sbom.AnyVersion { 116 return true 117 } 118 119 match = strings.ReplaceAll(match, ".", "\\.") 120 match = strings.ReplaceAll(match, "*", ".*") 121 match = fmt.Sprintf("^%s(\\..*)*$", match) 122 matcher, err := regexp.Compile(match) 123 if err != nil { 124 return false 125 } 126 return matcher.MatchString(version) 127 } 128 129 func cleanFormatName(name string) string { 130 r := strings.NewReplacer("-", "", "_", "") 131 return strings.ToLower(r.Replace(name)) 132 } 133 134 // Encode takes all SBOM elements and a format option and encodes an SBOM document. 135 func Encode(s sbom.SBOM, f sbom.FormatEncoder) ([]byte, error) { 136 buff := bytes.Buffer{} 137 138 if err := f.Encode(&buff, s); err != nil { 139 return nil, fmt.Errorf("unable to encode sbom: %w", err) 140 } 141 142 return buff.Bytes(), nil 143 }