github.com/noqcks/syft@v0.0.0-20230920222752-a9e2c4e288e5/syft/pkg/cataloger/python/parse_pipfile_lock.go (about)

     1  package python
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"strings"
     9  
    10  	"github.com/anchore/syft/syft/artifact"
    11  	"github.com/anchore/syft/syft/file"
    12  	"github.com/anchore/syft/syft/pkg"
    13  	"github.com/anchore/syft/syft/pkg/cataloger/generic"
    14  )
    15  
    16  type pipfileLock struct {
    17  	Meta struct {
    18  		Hash struct {
    19  			Sha256 string `json:"sha256"`
    20  		} `json:"hash"`
    21  		PipfileSpec int `json:"pipfile-spec"`
    22  		Requires    struct {
    23  			PythonVersion string `json:"python_version"`
    24  		} `json:"requires"`
    25  		Sources []struct {
    26  			Name      string `json:"name"`
    27  			URL       string `json:"url"`
    28  			VerifySsl bool   `json:"verify_ssl"`
    29  		} `json:"sources"`
    30  	} `json:"_meta"`
    31  	Default map[string]Dependency `json:"default"`
    32  	Develop map[string]Dependency `json:"develop"`
    33  }
    34  
    35  type Dependency struct {
    36  	Hashes  []string `json:"hashes"`
    37  	Version string   `json:"version"`
    38  	Index   string   `json:"index"`
    39  }
    40  
    41  var _ generic.Parser = parsePipfileLock
    42  
    43  // parsePipfileLock is a parser function for Pipfile.lock contents, returning "Default" python packages discovered.
    44  func parsePipfileLock(_ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
    45  	pkgs := make([]pkg.Package, 0)
    46  	dec := json.NewDecoder(reader)
    47  
    48  	for {
    49  		var lock pipfileLock
    50  		if err := dec.Decode(&lock); errors.Is(err, io.EOF) {
    51  			break
    52  		} else if err != nil {
    53  			return nil, nil, fmt.Errorf("failed to parse Pipfile.lock file: %w", err)
    54  		}
    55  		sourcesMap := map[string]string{}
    56  		for _, source := range lock.Meta.Sources {
    57  			sourcesMap[source.Name] = source.URL
    58  		}
    59  		for name, pkgMeta := range lock.Default {
    60  			var index string
    61  			if pkgMeta.Index != "" {
    62  				index = sourcesMap[pkgMeta.Index]
    63  			} else {
    64  				// https://pipenv.pypa.io/en/latest/advanced/#specifying-package-indexes
    65  				index = "https://pypi.org/simple"
    66  			}
    67  			version := strings.TrimPrefix(pkgMeta.Version, "==")
    68  			pkgs = append(pkgs, newPackageForIndexWithMetadata(name, version, pkg.PythonPipfileLockMetadata{Index: index, Hashes: pkgMeta.Hashes}, reader.Location))
    69  		}
    70  	}
    71  
    72  	pkg.Sort(pkgs)
    73  
    74  	return pkgs, nil, nil
    75  }