github.com/anchore/syft@v1.38.2/syft/configuration_audit_trail.go (about) 1 package syft 2 3 import ( 4 "encoding/json" 5 "reflect" 6 7 "github.com/anchore/syft/syft/cataloging" 8 "github.com/anchore/syft/syft/cataloging/filecataloging" 9 "github.com/anchore/syft/syft/cataloging/pkgcataloging" 10 ) 11 12 // configurationAuditTrail is all input configuration was used to generate the SBOM 13 type configurationAuditTrail struct { 14 Search cataloging.SearchConfig `json:"search" yaml:"search" mapstructure:"search"` 15 Relationships cataloging.RelationshipsConfig `json:"relationships" yaml:"relationships" mapstructure:"relationships"` 16 DataGeneration cataloging.DataGenerationConfig `json:"data-generation" yaml:"data-generation" mapstructure:"data-generation"` 17 Packages pkgcataloging.Config `json:"packages" yaml:"packages" mapstructure:"packages"` 18 Files filecataloging.Config `json:"files" yaml:"files" mapstructure:"files"` 19 Licenses cataloging.LicenseConfig `json:"licenses" yaml:"licenses" mapstructure:"licenses"` 20 Catalogers catalogerManifest `json:"catalogers" yaml:"catalogers" mapstructure:"catalogers"` 21 ExtraConfigs any `json:"extra,omitempty" yaml:"extra" mapstructure:"extra"` 22 } 23 24 type catalogerManifest struct { 25 Requested cataloging.SelectionRequest `json:"requested" yaml:"requested" mapstructure:"requested"` 26 Used []string `json:"used" yaml:"used" mapstructure:"used"` 27 } 28 29 type marshalAPIConfiguration configurationAuditTrail 30 31 func (cfg configurationAuditTrail) MarshalJSON() ([]byte, error) { 32 // since the api configuration is placed into the SBOM in an empty interface, and we want a stable ordering of 33 // keys (not guided by the struct ordering) we need to convert the struct to a map. This is best done with 34 // simply marshalling and unmarshalling. Mapstructure is used to ensure we are honoring all json struct 35 // tags. Once we have a map, we can lean on the stable ordering of json map keys in the stdlib. This is an 36 // implementation detail that can be at least relied on until Go 2 (at which point it can change). 37 // This dance allows us to guarantee ordering of keys in the configuration section of the SBOM. 38 39 initialJSON, err := json.Marshal(marshalAPIConfiguration(cfg)) 40 if err != nil { 41 return nil, err 42 } 43 44 var dataMap map[string]interface{} 45 if err := json.Unmarshal(initialJSON, &dataMap); err != nil { 46 return nil, err 47 } 48 49 if v, exists := dataMap["extra"]; exists && v == nil { 50 // remove the extra key if it renders as nil 51 delete(dataMap, "extra") 52 } 53 54 return marshalSorted(dataMap) 55 } 56 57 // marshalSorted recursively marshals a map with sorted keys 58 func marshalSorted(m interface{}) ([]byte, error) { 59 if reflect.TypeOf(m).Kind() != reflect.Map { 60 return json.Marshal(m) 61 } 62 63 val := reflect.ValueOf(m) 64 sortedMap := make(map[string]interface{}) 65 66 for _, key := range val.MapKeys() { 67 value := val.MapIndex(key).Interface() 68 69 if value != nil && reflect.TypeOf(value).Kind() == reflect.Map { 70 sortedValue, err := marshalSorted(value) 71 if err != nil { 72 return nil, err 73 } 74 sortedMap[key.String()] = json.RawMessage(sortedValue) 75 } else { 76 sortedMap[key.String()] = value 77 } 78 } 79 80 return json.Marshal(sortedMap) 81 }