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

     1  package golang
     2  
     3  import (
     4  	"bufio"
     5  	"fmt"
     6  	"io"
     7  	"sort"
     8  	"strings"
     9  
    10  	"golang.org/x/mod/modfile"
    11  
    12  	"github.com/anchore/syft/internal/log"
    13  	"github.com/anchore/syft/syft/artifact"
    14  	"github.com/anchore/syft/syft/file"
    15  	"github.com/anchore/syft/syft/pkg"
    16  	"github.com/anchore/syft/syft/pkg/cataloger/generic"
    17  )
    18  
    19  type goModCataloger struct {
    20  	licenses goLicenses
    21  }
    22  
    23  // parseGoModFile takes a go.mod and lists all packages discovered.
    24  //
    25  //nolint:funlen
    26  func (c *goModCataloger) parseGoModFile(resolver file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
    27  	packages := make(map[string]pkg.Package)
    28  
    29  	contents, err := io.ReadAll(reader)
    30  	if err != nil {
    31  		return nil, nil, fmt.Errorf("failed to read go module: %w", err)
    32  	}
    33  
    34  	f, err := modfile.Parse(reader.RealPath, contents, nil)
    35  	if err != nil {
    36  		return nil, nil, fmt.Errorf("failed to parse go module: %w", err)
    37  	}
    38  
    39  	digests, err := parseGoSumFile(resolver, reader)
    40  	if err != nil {
    41  		log.Debugf("unable to get go.sum: %v", err)
    42  	}
    43  
    44  	for _, m := range f.Require {
    45  		licenses, err := c.licenses.getLicenses(resolver, m.Mod.Path, m.Mod.Version)
    46  		if err != nil {
    47  			log.Tracef("error getting licenses for package: %s %v", m.Mod.Path, err)
    48  		}
    49  
    50  		packages[m.Mod.Path] = pkg.Package{
    51  			Name:         m.Mod.Path,
    52  			Version:      m.Mod.Version,
    53  			Licenses:     pkg.NewLicenseSet(licenses...),
    54  			Locations:    file.NewLocationSet(reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
    55  			PURL:         packageURL(m.Mod.Path, m.Mod.Version),
    56  			Language:     pkg.Go,
    57  			Type:         pkg.GoModulePkg,
    58  			MetadataType: pkg.GolangModMetadataType,
    59  			Metadata: pkg.GolangModMetadata{
    60  				H1Digest: digests[fmt.Sprintf("%s %s", m.Mod.Path, m.Mod.Version)],
    61  			},
    62  		}
    63  	}
    64  
    65  	// remove any old packages and replace with new ones...
    66  	for _, m := range f.Replace {
    67  		licenses, err := c.licenses.getLicenses(resolver, m.New.Path, m.New.Version)
    68  		if err != nil {
    69  			log.Tracef("error getting licenses for package: %s %v", m.New.Path, err)
    70  		}
    71  
    72  		packages[m.New.Path] = pkg.Package{
    73  			Name:         m.New.Path,
    74  			Version:      m.New.Version,
    75  			Licenses:     pkg.NewLicenseSet(licenses...),
    76  			Locations:    file.NewLocationSet(reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
    77  			PURL:         packageURL(m.New.Path, m.New.Version),
    78  			Language:     pkg.Go,
    79  			Type:         pkg.GoModulePkg,
    80  			MetadataType: pkg.GolangModMetadataType,
    81  			Metadata: pkg.GolangModMetadata{
    82  				H1Digest: digests[fmt.Sprintf("%s %s", m.New.Path, m.New.Version)],
    83  			},
    84  		}
    85  	}
    86  
    87  	// remove any packages from the exclude fields
    88  	for _, m := range f.Exclude {
    89  		delete(packages, m.Mod.Path)
    90  	}
    91  
    92  	pkgsSlice := make([]pkg.Package, len(packages))
    93  	idx := 0
    94  	for _, p := range packages {
    95  		p.SetID()
    96  		pkgsSlice[idx] = p
    97  		idx++
    98  	}
    99  
   100  	sort.SliceStable(pkgsSlice, func(i, j int) bool {
   101  		return pkgsSlice[i].Name < pkgsSlice[j].Name
   102  	})
   103  
   104  	return pkgsSlice, nil, nil
   105  }
   106  
   107  func parseGoSumFile(resolver file.Resolver, reader file.LocationReadCloser) (map[string]string, error) {
   108  	out := map[string]string{}
   109  
   110  	if resolver == nil {
   111  		return out, fmt.Errorf("no resolver provided")
   112  	}
   113  
   114  	goSumPath := strings.TrimSuffix(reader.Location.RealPath, ".mod") + ".sum"
   115  	goSumLocation := resolver.RelativeFileByPath(reader.Location, goSumPath)
   116  	if goSumLocation == nil {
   117  		return nil, fmt.Errorf("unable to resolve: %s", goSumPath)
   118  	}
   119  	contents, err := resolver.FileContentsByLocation(*goSumLocation)
   120  	if err != nil {
   121  		return nil, err
   122  	}
   123  
   124  	// go.sum has the format like:
   125  	// github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
   126  	// github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw=
   127  	// github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
   128  	scanner := bufio.NewScanner(contents)
   129  	// optionally, resize scanner's capacity for lines over 64K, see next example
   130  	for scanner.Scan() {
   131  		line := scanner.Text()
   132  		parts := strings.Split(line, " ")
   133  		if len(parts) < 3 {
   134  			continue
   135  		}
   136  		nameVersion := fmt.Sprintf("%s %s", parts[0], parts[1])
   137  		hash := parts[2]
   138  		out[nameVersion] = hash
   139  	}
   140  
   141  	return out, nil
   142  }