github.com/lineaje-labs/syft@v0.98.1-0.20231227153149-9e393f60ff1b/cmd/syft/cli/options/format.go (about)

     1  package options
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/hashicorp/go-multierror"
     7  
     8  	"github.com/anchore/clio"
     9  	"github.com/anchore/syft/syft/format/cyclonedxjson"
    10  	"github.com/anchore/syft/syft/format/cyclonedxxml"
    11  	"github.com/anchore/syft/syft/format/github"
    12  	"github.com/anchore/syft/syft/format/spdxjson"
    13  	"github.com/anchore/syft/syft/format/spdxtagvalue"
    14  	"github.com/anchore/syft/syft/format/syftjson"
    15  	"github.com/anchore/syft/syft/format/table"
    16  	"github.com/anchore/syft/syft/format/template"
    17  	"github.com/anchore/syft/syft/format/text"
    18  	"github.com/anchore/syft/syft/sbom"
    19  )
    20  
    21  var _ clio.PostLoader = (*Format)(nil)
    22  
    23  // Format contains all user configuration for output formatting.
    24  type Format struct {
    25  	Pretty        *bool               `yaml:"pretty" json:"pretty" mapstructure:"pretty"`
    26  	Template      FormatTemplate      `yaml:"template" json:"template" mapstructure:"template"`
    27  	SyftJSON      FormatSyftJSON      `yaml:"json" json:"json" mapstructure:"json"`
    28  	SPDXJSON      FormatSPDXJSON      `yaml:"spdx-json" json:"spdx-json" mapstructure:"spdx-json"`
    29  	CyclonedxJSON FormatCyclonedxJSON `yaml:"cyclonedx-json" json:"cyclonedx-json" mapstructure:"cyclonedx-json"`
    30  	CyclonedxXML  FormatCyclonedxXML  `yaml:"cyclonedx-xml" json:"cyclonedx-xml" mapstructure:"cyclonedx-xml"`
    31  }
    32  
    33  func (o *Format) PostLoad() error {
    34  	o.SyftJSON.Pretty = multiLevelOption[bool](false, o.Pretty, o.SyftJSON.Pretty)
    35  	o.SPDXJSON.Pretty = multiLevelOption[bool](false, o.Pretty, o.SPDXJSON.Pretty)
    36  	o.CyclonedxJSON.Pretty = multiLevelOption[bool](false, o.Pretty, o.CyclonedxJSON.Pretty)
    37  	o.CyclonedxXML.Pretty = multiLevelOption[bool](false, o.Pretty, o.CyclonedxXML.Pretty)
    38  
    39  	return nil
    40  }
    41  
    42  func DefaultFormat() Format {
    43  	return Format{
    44  		Template:      DefaultFormatTemplate(),
    45  		SyftJSON:      DefaultFormatJSON(),
    46  		SPDXJSON:      DefaultFormatSPDXJSON(),
    47  		CyclonedxJSON: DefaultFormatCyclonedxJSON(),
    48  		CyclonedxXML:  DefaultFormatCyclonedxXML(),
    49  	}
    50  }
    51  
    52  func (o *Format) Encoders() ([]sbom.FormatEncoder, error) {
    53  	// setup all encoders based on the configuration
    54  	var list encoderList
    55  
    56  	// in the future there will be application configuration options that can be used to set the default output format
    57  	list.addWithErr(template.ID)(o.Template.formatEncoders())
    58  	list.addWithErr(syftjson.ID)(o.SyftJSON.formatEncoders())
    59  	list.add(table.ID)(table.NewFormatEncoder())
    60  	list.add(text.ID)(text.NewFormatEncoder())
    61  	list.add(github.ID)(github.NewFormatEncoder())
    62  	list.addWithErr(cyclonedxxml.ID)(o.CyclonedxXML.formatEncoders())
    63  	list.addWithErr(cyclonedxjson.ID)(o.CyclonedxJSON.formatEncoders())
    64  	list.addWithErr(spdxjson.ID)(o.SPDXJSON.formatEncoders())
    65  	list.addWithErr(spdxtagvalue.ID)(spdxTagValueEncoders())
    66  
    67  	return list.encoders, list.err
    68  }
    69  
    70  // TODO: when application configuration is made for this format then this should be ported to the options object
    71  // that is created for that configuration (as done with the template output option)
    72  func spdxTagValueEncoders() ([]sbom.FormatEncoder, error) {
    73  	var (
    74  		encs []sbom.FormatEncoder
    75  		errs error
    76  	)
    77  	for _, v := range spdxtagvalue.SupportedVersions() {
    78  		enc, err := spdxtagvalue.NewFormatEncoderWithConfig(spdxtagvalue.EncoderConfig{Version: v})
    79  		if err != nil {
    80  			errs = multierror.Append(errs, err)
    81  		} else {
    82  			encs = append(encs, enc)
    83  		}
    84  	}
    85  	return encs, errs
    86  }
    87  
    88  type encoderList struct {
    89  	encoders []sbom.FormatEncoder
    90  	err      error
    91  }
    92  
    93  func (l *encoderList) addWithErr(name sbom.FormatID) func([]sbom.FormatEncoder, error) {
    94  	return func(encs []sbom.FormatEncoder, err error) {
    95  		if err != nil {
    96  			l.err = multierror.Append(l.err, fmt.Errorf("unable to configure %q format encoder: %w", name, err))
    97  			return
    98  		}
    99  		for _, enc := range encs {
   100  			if enc == nil {
   101  				l.err = multierror.Append(l.err, fmt.Errorf("unable to configure %q format encoder: nil encoder returned", name))
   102  				continue
   103  			}
   104  			l.encoders = append(l.encoders, enc)
   105  		}
   106  	}
   107  }
   108  
   109  func (l *encoderList) add(name sbom.FormatID) func(...sbom.FormatEncoder) {
   110  	return func(encs ...sbom.FormatEncoder) {
   111  		for _, enc := range encs {
   112  			if enc == nil {
   113  				l.err = multierror.Append(l.err, fmt.Errorf("unable to configure %q format encoder: nil encoder returned", name))
   114  				continue
   115  			}
   116  			l.encoders = append(l.encoders, enc)
   117  		}
   118  	}
   119  }
   120  
   121  func multiLevelOption[T any](defaultValue T, option ...*T) *T {
   122  	result := defaultValue
   123  	for _, opt := range option {
   124  		if opt != nil {
   125  			result = *opt
   126  		}
   127  	}
   128  	return &result
   129  }