@@ -18,15 +18,16 @@ package cdx
1818
1919import (
2020 "context"
21+ "encoding/json"
2122 "fmt"
2223 "os"
24+ "reflect"
2325 "time"
2426
2527 cydx "github.com/CycloneDX/cyclonedx-go"
2628 "github.com/google/uuid"
2729 "github.com/interlynk-io/sbomasm/pkg/detect"
2830 "github.com/interlynk-io/sbomasm/pkg/logger"
29- "github.com/mitchellh/copystructure"
3031 "github.com/samber/lo"
3132 "sigs.k8s.io/release-utils/version"
3233)
@@ -55,21 +56,88 @@ func newBomRef() string {
5556}
5657
5758func cloneComp (c * cydx.Component ) (* cydx.Component , error ) {
58- compCopy , err := copystructure .Copy (c )
59+ var newComp cydx.Component
60+
61+ // Marshal the original component to JSON
62+ b , err := json .Marshal (c )
5963 if err != nil {
6064 return nil , err
6165 }
6266
63- return compCopy .(* cydx.Component ), nil
67+ // Unmarshal into a map[string]interface{} to perform cleanup
68+ var tempMap map [string ]interface {}
69+ if err := json .Unmarshal (b , & tempMap ); err != nil {
70+ return nil , err
71+ }
72+
73+ // Remove empty fields recursively
74+ cleanedUpMap := removeEmptyFields (tempMap )
75+
76+ // Marshal the cleaned-up map back to JSON
77+ cleanedUpBytes , err := json .Marshal (cleanedUpMap )
78+ if err != nil {
79+ return nil , err
80+ }
81+
82+ // Unmarshal the cleaned-up JSON back into a cydx.Component struct
83+ if err := json .Unmarshal (cleanedUpBytes , & newComp ); err != nil {
84+ return nil , err
85+ }
86+
87+ return & newComp , nil
6488}
6589
6690func cloneService (s * cydx.Service ) (* cydx.Service , error ) {
67- serviceCopy , err := copystructure .Copy (s )
91+ var newService cydx.Service
92+ b , err := json .Marshal (s )
6893 if err != nil {
6994 return nil , err
7095 }
96+ json .Unmarshal (b , & newService )
97+ return & newService , nil
98+ }
7199
72- return serviceCopy .(* cydx.Service ), nil
100+ // Recursive function to remove empty fields, including empty objects and arrays
101+ func removeEmptyFields (data interface {}) interface {} {
102+ switch v := data .(type ) {
103+ case map [string ]interface {}:
104+ // Loop through map and remove empty fields
105+ for key , value := range v {
106+ v [key ] = removeEmptyFields (value )
107+ // Remove empty maps and slices
108+ if isEmptyValue (v [key ]) {
109+ delete (v , key )
110+ }
111+ }
112+ case []interface {}:
113+ // Process arrays
114+ var newArray []interface {}
115+ for _ , item := range v {
116+ item = removeEmptyFields (item )
117+ if ! isEmptyValue (item ) {
118+ newArray = append (newArray , item )
119+ }
120+ }
121+ return newArray
122+ }
123+ return data
124+ }
125+
126+ // Helper function to determine if a value is considered "empty"
127+ func isEmptyValue (v interface {}) bool {
128+ if v == nil {
129+ return true
130+ }
131+ val := reflect .ValueOf (v )
132+ switch val .Kind () {
133+ case reflect .Array , reflect .Slice , reflect .Map :
134+ return val .Len () == 0
135+ case reflect .Struct :
136+ return reflect .DeepEqual (v , reflect .Zero (val .Type ()).Interface ())
137+ case reflect .String :
138+ return v == ""
139+ }
140+ return false
73141}
74142
75143func loadBom (ctx context.Context , path string ) (* cydx.BOM , error ) {
@@ -117,39 +185,42 @@ func utcNowTime() string {
117185 return locationTime .Format (time .RFC3339 )
118186}
119187
120- func toolInfo (name , version , desc , sName , sUrl , sEmail , sLicense string ) * cydx.Component {
121- return & cydx.Component {
188+ func buildToolList (in []* cydx.BOM ) * cydx.ToolsChoice {
189+ tools := cydx.ToolsChoice {}
190+
191+ tools .Services = & []cydx.Service {}
192+ tools .Components = & []cydx.Component {}
193+
194+ * tools .Components = append (* tools .Components , cydx.Component {
122195 Type : cydx .ComponentTypeApplication ,
123- Name : name ,
124- Version : version ,
125- Description : desc ,
196+ Name : "sbomasm" ,
197+ Version : version . GetVersionInfo (). GitVersion ,
198+ Description : "Assembler & Editor for your sboms" ,
126199 Supplier : & cydx.OrganizationalEntity {
127- Name : sName ,
128- URL : & []string {sUrl },
129- Contact : & []cydx.OrganizationalContact {{Email : sEmail }},
200+ Name : "Interlynk" ,
201+ URL : & []string {"https://interlynk.io" },
202+ Contact : & []cydx.OrganizationalContact {{Email : "support@interlynk.io" }},
130203 },
131204 Licenses : & cydx.Licenses {
132205 {
133206 License : & cydx.License {
134- ID : sLicense ,
207+ ID : "Apache-2.0" ,
135208 },
136209 },
137210 },
138- }
139- }
140-
141- func buildToolList (in []* cydx.BOM ) * cydx.ToolsChoice {
142- tools := cydx.ToolsChoice {}
143-
144- tools .Services = & []cydx.Service {}
145- tools .Components = & []cydx.Component {}
146-
147- * tools .Components = append (* tools .Components , * toolInfo ("sbomasm" , version .GetVersionInfo ().GitVersion , "Assembler & Editor for your sboms" , "Interlynk" , "https://interlynk.io" , "support@interlynk.io" , "Apache-2.0" ))
211+ })
148212
149213 for _ , bom := range in {
150214 if bom .Metadata != nil && bom .Metadata .Tools != nil {
151215 for _ , tool := range * bom .Metadata .Tools .Tools {
152- * tools .Components = append (* tools .Components , * toolInfo (tool .Name , tool .Version , "" , tool .Vendor , "" , "" , "" ))
216+ * tools .Components = append (* tools .Components , cydx.Component {
217+ Type : cydx .ComponentTypeApplication ,
218+ Name : tool .Name ,
219+ Version : tool .Version ,
220+ Supplier : & cydx.OrganizationalEntity {
221+ Name : tool .Vendor ,
222+ },
223+ })
153224 }
154225 }
155226
@@ -168,29 +239,47 @@ func buildToolList(in []*cydx.BOM) *cydx.ToolsChoice {
168239 }
169240 }
170241
242+ uniqTools := lo .UniqBy (* tools .Components , func (c cydx.Component ) string {
243+ return fmt .Sprintf ("%s-%s" , c .Name , c .Version )
244+ })
245+
246+ uniqServices := lo .UniqBy (* tools .Services , func (s cydx.Service ) string {
247+ return fmt .Sprintf ("%s-%s" , s .Name , s .Version )
248+ })
249+
250+ tools .Components = & uniqTools
251+ tools .Services = & uniqServices
252+
171253 return & tools
172254}
173255
174- func buildComponentList (in []* cydx.BOM , cs * ComponentService ) []cydx.Component {
175- return lo .Flatten (lo .Map (in , func (bom * cydx.BOM , _ int ) []cydx.Component {
176- newComps := []cydx.Component {}
256+ func buildComponentList (in []* cydx.BOM , cs * uniqueComponentService ) []cydx.Component {
257+ finalList := []cydx.Component {}
258+
259+ for _ , bom := range in {
177260 for _ , comp := range lo .FromPtr (bom .Components ) {
178- newComps = append (newComps , * cs .StoreAndCloneWithNewID (& comp ))
261+ newComp , duplicate := cs .StoreAndCloneWithNewID (& comp )
262+ if ! duplicate {
263+ finalList = append (finalList , * newComp )
264+ }
179265 }
180- return newComps
181- }))
266+ }
267+ return finalList
182268}
183269
184- func buildPrimaryComponentList (in []* cydx.BOM , cs * ComponentService ) []cydx.Component {
270+ func buildPrimaryComponentList (in []* cydx.BOM , cs * uniqueComponentService ) []cydx.Component {
185271 return lo .Map (in , func (bom * cydx.BOM , _ int ) cydx.Component {
186272 if bom .Metadata != nil && bom .Metadata .Component != nil {
187- return * cs .StoreAndCloneWithNewID (bom .Metadata .Component )
273+ newComp , duplicate := cs .StoreAndCloneWithNewID (bom .Metadata .Component )
274+ if ! duplicate {
275+ return * newComp
276+ }
188277 }
189278 return cydx.Component {}
190279 })
191280}
192281
193- func buildDependencyList (in []* cydx.BOM , cs * ComponentService ) []cydx.Dependency {
282+ func buildDependencyList (in []* cydx.BOM , cs * uniqueComponentService ) []cydx.Dependency {
194283 return lo .Flatten (lo .Map (in , func (bom * cydx.BOM , _ int ) []cydx.Dependency {
195284 newDeps := []cydx.Dependency {}
196285 for _ , dep := range lo .FromPtr (bom .Dependencies ) {
0 commit comments