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  }