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  }