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  }