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 }