github.com/nextlinux/gosbom@v0.81.1-0.20230627115839-1ff50c281391/cmd/gosbom/cli/packages/packages.go (about) 1 package packages 2 3 import ( 4 "context" 5 "fmt" 6 7 "github.com/nextlinux/gosbom/cmd/gosbom/cli/eventloop" 8 "github.com/nextlinux/gosbom/cmd/gosbom/cli/options" 9 "github.com/nextlinux/gosbom/gosbom" 10 "github.com/nextlinux/gosbom/gosbom/artifact" 11 "github.com/nextlinux/gosbom/gosbom/event" 12 "github.com/nextlinux/gosbom/gosbom/formats/template" 13 "github.com/nextlinux/gosbom/gosbom/sbom" 14 "github.com/nextlinux/gosbom/gosbom/source" 15 "github.com/nextlinux/gosbom/internal" 16 "github.com/nextlinux/gosbom/internal/bus" 17 "github.com/nextlinux/gosbom/internal/config" 18 "github.com/nextlinux/gosbom/internal/ui" 19 "github.com/nextlinux/gosbom/internal/version" 20 "github.com/wagoodman/go-partybus" 21 22 "github.com/anchore/stereoscope" 23 ) 24 25 func Run(_ context.Context, app *config.Application, args []string) error { 26 err := ValidateOutputOptions(app) 27 if err != nil { 28 return err 29 } 30 31 writer, err := options.MakeSBOMWriter(app.Outputs, app.File, app.OutputTemplatePath) 32 if err != nil { 33 return err 34 } 35 36 // could be an image or a directory, with or without a scheme 37 userInput := args[0] 38 si, err := source.ParseInputWithNameVersion(userInput, app.Platform, app.SourceName, app.SourceVersion, app.DefaultImagePullSource) 39 if err != nil { 40 return fmt.Errorf("could not generate source input for packages command: %w", err) 41 } 42 43 eventBus := partybus.NewBus() 44 stereoscope.SetBus(eventBus) 45 gosbom.SetBus(eventBus) 46 subscription := eventBus.Subscribe() 47 48 return eventloop.EventLoop( 49 execWorker(app, *si, writer), 50 eventloop.SetupSignals(), 51 subscription, 52 stereoscope.Cleanup, 53 ui.Select(options.IsVerbose(app), app.Quiet)..., 54 ) 55 } 56 57 func execWorker(app *config.Application, si source.Input, writer sbom.Writer) <-chan error { 58 errs := make(chan error) 59 go func() { 60 defer close(errs) 61 62 src, cleanup, err := source.New(si, app.Registry.ToOptions(), app.Exclusions) 63 if cleanup != nil { 64 defer cleanup() 65 } 66 if err != nil { 67 errs <- fmt.Errorf("failed to construct source from user input %q: %w", si.UserInput, err) 68 return 69 } 70 71 s, err := GenerateSBOM(src, errs, app) 72 if err != nil { 73 errs <- err 74 return 75 } 76 77 if s == nil { 78 errs <- fmt.Errorf("no SBOM produced for %q", si.UserInput) 79 } 80 81 bus.Publish(partybus.Event{ 82 Type: event.Exit, 83 Value: func() error { return writer.Write(*s) }, 84 }) 85 }() 86 return errs 87 } 88 89 func GenerateSBOM(src *source.Source, errs chan error, app *config.Application) (*sbom.SBOM, error) { 90 tasks, err := eventloop.Tasks(app) 91 if err != nil { 92 return nil, err 93 } 94 95 s := sbom.SBOM{ 96 Source: src.Metadata, 97 Descriptor: sbom.Descriptor{ 98 Name: internal.ApplicationName, 99 Version: version.FromBuild().Version, 100 Configuration: app, 101 }, 102 } 103 104 buildRelationships(&s, src, tasks, errs) 105 106 return &s, nil 107 } 108 109 func buildRelationships(s *sbom.SBOM, src *source.Source, tasks []eventloop.Task, errs chan error) { 110 var relationships []<-chan artifact.Relationship 111 for _, task := range tasks { 112 c := make(chan artifact.Relationship) 113 relationships = append(relationships, c) 114 go eventloop.RunTask(task, &s.Artifacts, src, c, errs) 115 } 116 117 s.Relationships = append(s.Relationships, MergeRelationships(relationships...)...) 118 } 119 120 func MergeRelationships(cs ...<-chan artifact.Relationship) (relationships []artifact.Relationship) { 121 for _, c := range cs { 122 for n := range c { 123 relationships = append(relationships, n) 124 } 125 } 126 127 return relationships 128 } 129 130 func ValidateOutputOptions(app *config.Application) error { 131 var usesTemplateOutput bool 132 for _, o := range app.Outputs { 133 if o == template.ID.String() { 134 usesTemplateOutput = true 135 break 136 } 137 } 138 139 if usesTemplateOutput && app.OutputTemplatePath == "" { 140 return fmt.Errorf(`must specify path to template file when using "template" output format`) 141 } 142 143 return nil 144 }