github.com/google/osv-scalibr@v0.4.1/extractor/filesystem/runtime/asdf/asdf.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 asdf extracts the installed language runtime names and versions from asdf .tool-version files. 16 package asdf 17 18 import ( 19 "bufio" 20 "context" 21 "fmt" 22 "path" 23 "strings" 24 25 "github.com/google/osv-scalibr/extractor" 26 "github.com/google/osv-scalibr/extractor/filesystem" 27 asdfmeta "github.com/google/osv-scalibr/extractor/filesystem/runtime/asdf/metadata" 28 "github.com/google/osv-scalibr/inventory" 29 "github.com/google/osv-scalibr/plugin" 30 "github.com/google/osv-scalibr/purl" 31 ) 32 33 const ( 34 // Name is the unique name of this extractor. 35 Name = "os/asdf" 36 ) 37 38 // Extractor extracts asdf tools. 39 type Extractor struct{} 40 41 // New returns a new instance of the extractor. 42 func New() filesystem.Extractor { return &Extractor{} } 43 44 // Name of the extractor. 45 func (e Extractor) Name() string { return Name } 46 47 // Version of the extractor. 48 func (e Extractor) Version() int { return 0 } 49 50 // Requirements of the extractor. 51 func (e Extractor) Requirements() *plugin.Capabilities { 52 return &plugin.Capabilities{} 53 } 54 55 // FileRequired returns true if the file name is '.tool-versions'. 56 func (e Extractor) FileRequired(api filesystem.FileAPI) bool { 57 return path.Base(api.Path()) == ".tool-versions" 58 } 59 60 func parseToolLine(line string) (tool string, versions []string, ok bool) { 61 line = strings.TrimSpace(line) 62 if line == "" || strings.HasPrefix(line, "#") { 63 return "", nil, false 64 } 65 fields := strings.Fields(line) 66 if len(fields) < 2 { 67 return "", nil, false 68 } 69 return fields[0], fields[1:], true 70 } 71 72 // Extract extracts packages from the asdf .tool-versions file. 73 // 74 // Reference: https://asdf-vm.com/manage/configuration.html#tool-versions 75 func (e Extractor) Extract(ctx context.Context, input *filesystem.ScanInput) (inventory.Inventory, error) { 76 scanner := bufio.NewScanner(input.Reader) 77 var pkgs []*extractor.Package 78 79 for scanner.Scan() { 80 if err := ctx.Err(); err != nil { 81 return inventory.Inventory{}, fmt.Errorf("%s halted due to context error: %w", e.Name(), err) 82 } 83 84 tool, versions, ok := parseToolLine(scanner.Text()) 85 if !ok { 86 continue 87 } 88 89 for _, v := range versions { 90 // Skip entries that don't store version strings. 91 if v == "system" || strings.HasPrefix(v, "file:") { 92 continue 93 } 94 pkgs = append(pkgs, &extractor.Package{ 95 Name: tool, 96 Version: v, 97 PURLType: purl.TypeAsdf, 98 Locations: []string{input.Path}, 99 Metadata: &asdfmeta.Metadata{ 100 ToolName: tool, 101 ToolVersion: v, 102 }, 103 }) 104 } 105 } 106 107 if err := scanner.Err(); err != nil { 108 return inventory.Inventory{}, err 109 } 110 return inventory.Inventory{Packages: pkgs}, nil 111 }