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 }