github.com/google/osv-scalibr@v0.4.1/enricher/enricher.go (about) 1 // Copyright 2025 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package enricher provides the interface for enrichment plugins. 16 package enricher 17 18 import ( 19 "cmp" 20 "context" 21 "errors" 22 "fmt" 23 "path/filepath" 24 "slices" 25 "strings" 26 27 scalibrfs "github.com/google/osv-scalibr/fs" 28 "github.com/google/osv-scalibr/inventory" 29 "github.com/google/osv-scalibr/plugin" 30 ) 31 32 var ( 33 // ErrNoDirectFS is returned when an enricher requires direct filesystem access but the scan root is nil. 34 ErrNoDirectFS = errors.New("enrichment requires direct filesystem access but scan root is nil") 35 36 // EnricherOrder describes the order in which specific enrichers need to run in. 37 // TODO(b/416106602): Use required enrichers instead of a global ordering list. 38 EnricherOrder = []string{ 39 "reachability/java", 40 "vulnmatch/osvdev", 41 // Reachability enrichers need to run after vulnmatch enrichers (in certain configurations). 42 "reachability/go/source", 43 "vex/filter", 44 } 45 ) 46 47 // Enricher is the interface for an enrichment plugin, used to enrich scan results with additional 48 // information through APIs or other sources. 49 type Enricher interface { 50 plugin.Plugin 51 // RequiredPlugins returns a list of Plugins that need to be enabled for this Enricher to run. 52 RequiredPlugins() []string 53 // Enrich enriches the scan results with additional information. 54 Enrich(ctx context.Context, input *ScanInput, inv *inventory.Inventory) error 55 } 56 57 // Config for running enrichers. 58 type Config struct { 59 Enrichers []Enricher 60 ScanRoot *scalibrfs.ScanRoot 61 } 62 63 // ScanInput provides information for the enricher about the scan. 64 type ScanInput struct { 65 // The root of the artifact being scanned. 66 ScanRoot *scalibrfs.ScanRoot 67 } 68 69 // Run runs the specified enrichers and returns their statuses. 70 func Run(ctx context.Context, config *Config, inventory *inventory.Inventory) ([]*plugin.Status, error) { 71 var statuses []*plugin.Status 72 if len(config.Enrichers) == 0 { 73 return statuses, nil 74 } 75 76 orderEnrichers(config.Enrichers) 77 78 for _, e := range config.Enrichers { 79 capabilities := e.Requirements() 80 if capabilities != nil && capabilities.DirectFS && config.ScanRoot == nil { 81 return nil, fmt.Errorf("%w: for enricher %v", ErrNoDirectFS, e.Name()) 82 } 83 } 84 85 input := &ScanInput{} 86 if config.ScanRoot != nil { 87 if !config.ScanRoot.IsVirtual() { 88 p, err := filepath.Abs(config.ScanRoot.Path) 89 if err != nil { 90 return nil, err 91 } 92 config.ScanRoot.Path = p 93 } 94 input = &ScanInput{ 95 ScanRoot: config.ScanRoot, 96 } 97 } 98 99 for _, e := range config.Enrichers { 100 err := e.Enrich(ctx, input, inventory) 101 // TODO - b/410630503: Support partial success. 102 statuses = append(statuses, plugin.StatusFromErr(e, false, err, nil)) 103 } 104 return statuses, nil 105 } 106 107 // Orders the enrichers to make sure they're run in the order specified by EnricherOrder. 108 func orderEnrichers(enrichers []Enricher) { 109 nameToPlace := make(map[string]int) 110 for i, name := range EnricherOrder { 111 nameToPlace[name] = i 112 } 113 getPlace := func(name string) int { 114 if place, ok := nameToPlace[name]; ok { 115 return place 116 } 117 // Enrichers not in the explicit list can run in any order. 118 return len(nameToPlace) 119 } 120 121 slices.SortFunc(enrichers, func(a Enricher, b Enricher) int { 122 return cmp.Or( 123 cmp.Compare(getPlace(a.Name()), getPlace(b.Name())), 124 // Use the name as a tie-breaker to keep ordering deterministic. 125 strings.Compare(a.Name(), b.Name()), 126 ) 127 }) 128 }