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