github.com/google/osv-scalibr@v0.4.1/extractor/filesystem/language/python/pylock/pylock.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 pylock extracts pylock.toml files
    16  package pylock
    17  
    18  import (
    19  	"context"
    20  	"fmt"
    21  	"path/filepath"
    22  
    23  	"github.com/BurntSushi/toml"
    24  	"github.com/google/osv-scalibr/extractor"
    25  	"github.com/google/osv-scalibr/extractor/filesystem"
    26  	"github.com/google/osv-scalibr/inventory"
    27  	"github.com/google/osv-scalibr/plugin"
    28  	"github.com/google/osv-scalibr/purl"
    29  )
    30  
    31  const (
    32  	// Name is the unique name of this extractor.
    33  	Name = "python/pylock"
    34  )
    35  
    36  type pylockVCS struct {
    37  	Type   string `toml:"type"`
    38  	Commit string `toml:"commit-id"`
    39  }
    40  
    41  type pylockDirectory struct {
    42  	Path string `toml:"path"`
    43  }
    44  
    45  type pylockPackage struct {
    46  	Name      string          `toml:"name"`
    47  	Version   string          `toml:"version"`
    48  	VCS       pylockVCS       `toml:"vcs"`
    49  	Directory pylockDirectory `toml:"directory"`
    50  }
    51  
    52  type pylockLockfile struct {
    53  	Version  string          `toml:"lock-version"`
    54  	Packages []pylockPackage `toml:"packages"`
    55  }
    56  
    57  // Extractor extracts python packages from pylock.toml files.
    58  type Extractor struct{}
    59  
    60  var _ filesystem.Extractor = Extractor{}
    61  
    62  // New returns a new instance of the extractor.
    63  func New() filesystem.Extractor { return &Extractor{} }
    64  
    65  // Name of the extractor
    66  func (e Extractor) Name() string { return Name }
    67  
    68  // Version of the extractor
    69  func (e Extractor) Version() int { return 0 }
    70  
    71  // Requirements of the extractor
    72  func (e Extractor) Requirements() *plugin.Capabilities {
    73  	return &plugin.Capabilities{}
    74  }
    75  
    76  // FileRequired returns true if the specified file matches pylock lockfile patterns
    77  func (e Extractor) FileRequired(api filesystem.FileAPI) bool {
    78  	base := filepath.Base(api.Path())
    79  
    80  	if base == "pylock.toml" {
    81  		return true
    82  	}
    83  
    84  	m, _ := filepath.Match("pylock.*.toml", base)
    85  
    86  	return m
    87  }
    88  
    89  // Extract extracts packages from pylock.toml files passed through the scan input.
    90  func (e Extractor) Extract(_ context.Context, input *filesystem.ScanInput) (inventory.Inventory, error) {
    91  	var parsedLockfile *pylockLockfile
    92  
    93  	_, err := toml.NewDecoder(input.Reader).Decode(&parsedLockfile)
    94  
    95  	if err != nil {
    96  		return inventory.Inventory{}, fmt.Errorf("could not extract: %w", err)
    97  	}
    98  
    99  	packages := make([]*extractor.Package, 0, len(parsedLockfile.Packages))
   100  
   101  	for _, lockPackage := range parsedLockfile.Packages {
   102  		// this is likely the root package, which is sometimes included in the lockfile
   103  		if lockPackage.Version == "" && lockPackage.Directory.Path == "." {
   104  			continue
   105  		}
   106  
   107  		pkgDetails := &extractor.Package{
   108  			Name:      lockPackage.Name,
   109  			Version:   lockPackage.Version,
   110  			PURLType:  purl.TypePyPi,
   111  			Locations: []string{input.Path},
   112  		}
   113  		if lockPackage.VCS.Commit != "" {
   114  			pkgDetails.SourceCode = &extractor.SourceCodeIdentifier{
   115  				Commit: lockPackage.VCS.Commit,
   116  			}
   117  		}
   118  		packages = append(packages, pkgDetails)
   119  	}
   120  
   121  	return inventory.Inventory{Packages: packages}, nil
   122  }
   123  
   124  var _ filesystem.Extractor = Extractor{}