github.com/lineaje-labs/syft@v0.98.1-0.20231227153149-9e393f60ff1b/cmd/syft/cli/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.File, 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 }