github.com/anchore/syft@v1.4.2-0.20240516191711-1bec1fc5d397/cmd/syft/internal/options/output.go (about)

     1  package options
     2  
     3  import (
     4  	"fmt"
     5  	"sort"
     6  	"strings"
     7  
     8  	"github.com/hashicorp/go-multierror"
     9  	"github.com/scylladb/go-set/strset"
    10  
    11  	"github.com/anchore/clio"
    12  	"github.com/anchore/syft/syft/format/cyclonedxjson"
    13  	"github.com/anchore/syft/syft/format/cyclonedxxml"
    14  	"github.com/anchore/syft/syft/format/github"
    15  	"github.com/anchore/syft/syft/format/spdxjson"
    16  	"github.com/anchore/syft/syft/format/spdxtagvalue"
    17  	"github.com/anchore/syft/syft/format/syftjson"
    18  	"github.com/anchore/syft/syft/format/table"
    19  	"github.com/anchore/syft/syft/format/template"
    20  	"github.com/anchore/syft/syft/format/text"
    21  	"github.com/anchore/syft/syft/sbom"
    22  )
    23  
    24  var _ interface {
    25  	clio.FlagAdder
    26  	clio.PostLoader
    27  } = (*Output)(nil)
    28  
    29  // Output has the standard output options syft accepts: multiple -o, --file, --template
    30  type Output struct {
    31  	AllowableOptions     []string `yaml:"-" json:"-" mapstructure:"-"`
    32  	AllowMultipleOutputs bool     `yaml:"-" json:"-" mapstructure:"-"`
    33  	AllowToFile          bool     `yaml:"-" json:"-" mapstructure:"-"`
    34  	Outputs              []string `yaml:"output" json:"output" mapstructure:"output"` // -o, the format to use for output
    35  	OutputFile           `yaml:",inline" json:"" mapstructure:",squash"`
    36  	Format               `yaml:"format" json:"format" mapstructure:"format"`
    37  }
    38  
    39  func DefaultOutput() Output {
    40  	return Output{
    41  		AllowMultipleOutputs: true,
    42  		AllowToFile:          true,
    43  		Outputs:              []string{string(table.ID)},
    44  		OutputFile: OutputFile{
    45  			Enabled: true,
    46  		},
    47  		Format: DefaultFormat(),
    48  	}
    49  }
    50  
    51  func (o *Output) PostLoad() error {
    52  	var errs error
    53  	for _, loader := range []clio.PostLoader{&o.OutputFile, &o.Format} {
    54  		if err := loader.PostLoad(); err != nil {
    55  			errs = multierror.Append(errs, err)
    56  		}
    57  	}
    58  
    59  	return errs
    60  }
    61  
    62  func (o *Output) AddFlags(flags clio.FlagSet) {
    63  	var names []string
    64  	for _, id := range supportedIDs() {
    65  		names = append(names, id.String())
    66  	}
    67  	sort.Strings(names)
    68  
    69  	flags.StringArrayVarP(&o.Outputs, "output", "o",
    70  		fmt.Sprintf("report output format (<format>=<file> to output to a file), formats=%v", names))
    71  }
    72  
    73  func (o Output) SBOMWriter() (sbom.Writer, error) {
    74  	names := o.OutputNameSet()
    75  
    76  	if len(o.Outputs) > 1 && !o.AllowMultipleOutputs {
    77  		return nil, fmt.Errorf("only one output format is allowed (given %d: %s)", len(o.Outputs), names)
    78  	}
    79  
    80  	usesTemplateOutput := names.Has(string(template.ID))
    81  
    82  	if usesTemplateOutput && o.Format.Template.Path == "" {
    83  		return nil, fmt.Errorf(`must specify path to template file when using "template" output format`)
    84  	}
    85  
    86  	encoders, err := o.Encoders()
    87  	if err != nil {
    88  		return nil, err
    89  	}
    90  
    91  	if !o.AllowToFile {
    92  		for _, opt := range o.Outputs {
    93  			if strings.Contains(opt, "=") {
    94  				return nil, fmt.Errorf("file output is not allowed ('-o format=path' should be '-o format')")
    95  			}
    96  		}
    97  	}
    98  
    99  	return makeSBOMWriter(o.Outputs, o.LegacyFile, encoders)
   100  }
   101  
   102  func (o Output) OutputNameSet() *strset.Set {
   103  	names := strset.New()
   104  	for _, output := range o.Outputs {
   105  		fields := strings.Split(output, "=")
   106  		names.Add(fields[0])
   107  	}
   108  
   109  	return names
   110  }
   111  
   112  func supportedIDs() []sbom.FormatID {
   113  	encs := []sbom.FormatID{
   114  		// encoders that support a single version
   115  		syftjson.ID,
   116  		github.ID,
   117  		table.ID,
   118  		text.ID,
   119  		template.ID,
   120  
   121  		// encoders that support multiple versions
   122  		cyclonedxxml.ID,
   123  		cyclonedxjson.ID,
   124  		spdxtagvalue.ID,
   125  		spdxjson.ID,
   126  	}
   127  
   128  	return encs
   129  }