github.com/anchore/syft@v1.4.2-0.20240516191711-1bec1fc5d397/syft/pkg/cataloger/golang/parse_go_mod.go (about)

     1  package golang
     2  
     3  import (
     4  	"bufio"
     5  	"context"
     6  	"fmt"
     7  	"io"
     8  	"sort"
     9  	"strings"
    10  
    11  	"golang.org/x/mod/modfile"
    12  
    13  	"github.com/anchore/syft/internal"
    14  	"github.com/anchore/syft/internal/log"
    15  	"github.com/anchore/syft/syft/artifact"
    16  	"github.com/anchore/syft/syft/file"
    17  	"github.com/anchore/syft/syft/pkg"
    18  	"github.com/anchore/syft/syft/pkg/cataloger/generic"
    19  )
    20  
    21  type goModCataloger struct {
    22  	licenses goLicenses
    23  }
    24  
    25  // parseGoModFile takes a go.mod and lists all packages discovered.
    26  //
    27  //nolint:funlen
    28  func (c *goModCataloger) parseGoModFile(_ context.Context, resolver file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
    29  	packages := make(map[string]pkg.Package)
    30  
    31  	contents, err := io.ReadAll(reader)
    32  	if err != nil {
    33  		return nil, nil, fmt.Errorf("failed to read go module: %w", err)
    34  	}
    35  
    36  	f, err := modfile.Parse(reader.RealPath, contents, nil)
    37  	if err != nil {
    38  		return nil, nil, fmt.Errorf("failed to parse go module: %w", err)
    39  	}
    40  
    41  	digests, err := parseGoSumFile(resolver, reader)
    42  	if err != nil {
    43  		log.Debugf("unable to get go.sum: %v", err)
    44  	}
    45  
    46  	for _, m := range f.Require {
    47  		licenses, err := c.licenses.getLicenses(resolver, m.Mod.Path, m.Mod.Version)
    48  		if err != nil {
    49  			log.Tracef("error getting licenses for package: %s %v", m.Mod.Path, err)
    50  		}
    51  
    52  		packages[m.Mod.Path] = pkg.Package{
    53  			Name:      m.Mod.Path,
    54  			Version:   m.Mod.Version,
    55  			Licenses:  pkg.NewLicenseSet(licenses...),
    56  			Locations: file.NewLocationSet(reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
    57  			PURL:      packageURL(m.Mod.Path, m.Mod.Version),
    58  			Language:  pkg.Go,
    59  			Type:      pkg.GoModulePkg,
    60  			Metadata: pkg.GolangModuleEntry{
    61  				H1Digest: digests[fmt.Sprintf("%s %s", m.Mod.Path, m.Mod.Version)],
    62  			},
    63  		}
    64  	}
    65  
    66  	// remove any old packages and replace with new ones...
    67  	for _, m := range f.Replace {
    68  		licenses, err := c.licenses.getLicenses(resolver, m.New.Path, m.New.Version)
    69  		if err != nil {
    70  			log.Tracef("error getting licenses for package: %s %v", m.New.Path, err)
    71  		}
    72  
    73  		packages[m.New.Path] = pkg.Package{
    74  			Name:      m.New.Path,
    75  			Version:   m.New.Version,
    76  			Licenses:  pkg.NewLicenseSet(licenses...),
    77  			Locations: file.NewLocationSet(reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
    78  			PURL:      packageURL(m.New.Path, m.New.Version),
    79  			Language:  pkg.Go,
    80  			Type:      pkg.GoModulePkg,
    81  			Metadata: pkg.GolangModuleEntry{
    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  	defer internal.CloseAndLogError(contents, goSumLocation.AccessPath)
   124  
   125  	// go.sum has the format like:
   126  	// github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
   127  	// github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw=
   128  	// github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
   129  	scanner := bufio.NewScanner(contents)
   130  	// optionally, resize scanner's capacity for lines over 64K, see next example
   131  	for scanner.Scan() {
   132  		line := scanner.Text()
   133  		parts := strings.Split(line, " ")
   134  		if len(parts) < 3 {
   135  			continue
   136  		}
   137  		nameVersion := fmt.Sprintf("%s %s", parts[0], parts[1])
   138  		hash := parts[2]
   139  		out[nameVersion] = hash
   140  	}
   141  
   142  	return out, nil
   143  }