github.com/anchore/syft@v1.4.2-0.20240516191711-1bec1fc5d397/syft/create_sbom.go (about)

     1  package syft
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"sort"
     7  
     8  	"github.com/dustin/go-humanize"
     9  	"github.com/scylladb/go-set/strset"
    10  
    11  	"github.com/anchore/syft/internal/bus"
    12  	"github.com/anchore/syft/internal/sbomsync"
    13  	"github.com/anchore/syft/internal/task"
    14  	"github.com/anchore/syft/syft/artifact"
    15  	"github.com/anchore/syft/syft/event/monitor"
    16  	"github.com/anchore/syft/syft/pkg"
    17  	"github.com/anchore/syft/syft/sbom"
    18  	"github.com/anchore/syft/syft/source"
    19  )
    20  
    21  // CreateSBOM creates a software bill-of-materials from the given source. If the CreateSBOMConfig is nil, then
    22  // default options will be used.
    23  func CreateSBOM(ctx context.Context, src source.Source, cfg *CreateSBOMConfig) (*sbom.SBOM, error) {
    24  	if cfg == nil {
    25  		cfg = DefaultCreateSBOMConfig()
    26  	}
    27  	if err := cfg.validate(); err != nil {
    28  		return nil, fmt.Errorf("invalid configuration: %w", err)
    29  	}
    30  
    31  	srcMetadata := src.Describe()
    32  
    33  	taskGroups, audit, err := cfg.makeTaskGroups(srcMetadata)
    34  	if err != nil {
    35  		return nil, err
    36  	}
    37  
    38  	resolver, err := src.FileResolver(cfg.Search.Scope)
    39  	if err != nil {
    40  		return nil, fmt.Errorf("unable to get file resolver: %w", err)
    41  	}
    42  
    43  	s := sbom.SBOM{
    44  		Source: srcMetadata,
    45  		Descriptor: sbom.Descriptor{
    46  			Name:    cfg.ToolName,
    47  			Version: cfg.ToolVersion,
    48  			Configuration: configurationAuditTrail{
    49  				Search:         cfg.Search,
    50  				Relationships:  cfg.Relationships,
    51  				DataGeneration: cfg.DataGeneration,
    52  				Packages:       cfg.Packages,
    53  				Files:          cfg.Files,
    54  				Catalogers:     *audit,
    55  				ExtraConfigs:   cfg.ToolConfiguration,
    56  			},
    57  		},
    58  		Artifacts: sbom.Artifacts{
    59  			Packages: pkg.NewCollection(),
    60  		},
    61  	}
    62  
    63  	catalogingProgress := monitorCatalogingTask(src.ID(), taskGroups)
    64  	packageCatalogingProgress := monitorPackageCatalogingTask()
    65  
    66  	builder := sbomsync.NewBuilder(&s, monitorPackageCount(packageCatalogingProgress))
    67  	for i := range taskGroups {
    68  		err := task.NewTaskExecutor(taskGroups[i], cfg.Parallelism).Execute(ctx, resolver, builder, catalogingProgress)
    69  		if err != nil {
    70  			// TODO: tie this to the open progress monitors...
    71  			return nil, fmt.Errorf("failed to run tasks: %w", err)
    72  		}
    73  	}
    74  
    75  	packageCatalogingProgress.SetCompleted()
    76  	catalogingProgress.SetCompleted()
    77  
    78  	return &s, nil
    79  }
    80  
    81  func monitorPackageCount(prog *monitor.CatalogerTaskProgress) func(s *sbom.SBOM) {
    82  	return func(s *sbom.SBOM) {
    83  		count := humanize.Comma(int64(s.Artifacts.Packages.PackageCount()))
    84  		prog.AtomicStage.Set(fmt.Sprintf("%s packages", count))
    85  	}
    86  }
    87  
    88  func monitorPackageCatalogingTask() *monitor.CatalogerTaskProgress {
    89  	info := monitor.GenericTask{
    90  		Title: monitor.Title{
    91  			Default: "Packages",
    92  		},
    93  		ID:            monitor.PackageCatalogingTaskID,
    94  		HideOnSuccess: false,
    95  		ParentID:      monitor.TopLevelCatalogingTaskID,
    96  	}
    97  
    98  	return bus.StartCatalogerTask(info, -1, "")
    99  }
   100  
   101  func monitorCatalogingTask(srcID artifact.ID, tasks [][]task.Task) *monitor.CatalogerTaskProgress {
   102  	info := monitor.GenericTask{
   103  		Title: monitor.Title{
   104  			Default:      "Catalog contents",
   105  			WhileRunning: "Cataloging contents",
   106  			OnSuccess:    "Cataloged contents",
   107  		},
   108  		ID:            monitor.TopLevelCatalogingTaskID,
   109  		Context:       string(srcID),
   110  		HideOnSuccess: false,
   111  	}
   112  
   113  	var length int64
   114  	for _, tg := range tasks {
   115  		length += int64(len(tg))
   116  	}
   117  
   118  	return bus.StartCatalogerTask(info, length, "")
   119  }
   120  
   121  func formatTaskNames(tasks []task.Task) []string {
   122  	set := strset.New()
   123  	for _, td := range tasks {
   124  		set.Add(td.Name())
   125  	}
   126  	list := set.List()
   127  	sort.Strings(list)
   128  	return list
   129  }