github.com/vchain-us/vcn@v0.9.11-0.20210921212052-a2484d23c0b3/pkg/bom/golang/from_go_list.go (about)

     1  /*
     2   * Copyright (c) 2021 CodeNotary, Inc. All Rights Reserved.
     3   * This software is released under GPL3.
     4   * The full license information can be found under:
     5   * https://www.gnu.org/licenses/gpl-3.0.en.html
     6   *
     7   */
     8  
     9  package golang
    10  
    11  import (
    12  	"bufio"
    13  	"bytes"
    14  	"fmt"
    15  	"io"
    16  	"io/ioutil"
    17  	"log"
    18  	"net/http"
    19  	"os"
    20  	"os/exec"
    21  	"path/filepath"
    22  	"strings"
    23  	"sync"
    24  	"time"
    25  
    26  	"github.com/schollz/progressbar/v3"
    27  	"github.com/vchain-us/vcn/pkg/bom/artifact"
    28  	"golang.org/x/mod/sumdb"
    29  )
    30  
    31  // GoArtifactFromExe implements Artifact interface
    32  type goArtifactFromGoList struct {
    33  	goArtifact
    34  }
    35  
    36  type mapKey struct {
    37  	name    string
    38  	version string
    39  }
    40  
    41  type clientOps struct{}
    42  
    43  var goListArgs = []string{"list", "--deps", "-f", "{{if not .Standard}}{{.Module.Path}} {{.Module.Version}}{{end}}"}
    44  var sumDb = "sum.golang.org+033de0ae+Ac4zctda0e5eza+HJyk9SxEdh+s3Ux18htTTAD8OuAn8" // default sumdb server and it's public key
    45  
    46  // Dependencies returns list of Go dependencies used during the build
    47  // run 'go list' to get the list of used modules, and then get hashes from sumdb
    48  func (a *goArtifactFromGoList) ResolveDependencies(output artifact.OutputOptions) ([]artifact.Dependency, error) {
    49  	if a.Deps != nil {
    50  		return a.Deps, nil
    51  	}
    52  	absPath, err := filepath.Abs(a.path)
    53  	if err != nil {
    54  		return nil, err
    55  	}
    56  	// go won't work if cur directory is outside Go module root
    57  	cmd := exec.Command("go", append(goListArgs, absPath)...)
    58  	cmd.Dir = absPath
    59  	buf, err := cmd.Output()
    60  	if err != nil {
    61  		exit, ok := err.(*exec.ExitError)
    62  		if ok {
    63  			return nil, fmt.Errorf("cannot get Go deps:\n%s", string(exit.Stderr))
    64  		}
    65  		return nil, fmt.Errorf("cannot get Go deps: %w", err)
    66  	}
    67  
    68  	// override default sumdb path with the value from env, if set
    69  	tmp, ok := os.LookupEnv("GOSUMDB")
    70  	if ok {
    71  		sumDb = tmp
    72  	}
    73  
    74  	var bar *progressbar.ProgressBar
    75  
    76  	res := make([]artifact.Dependency, 0)
    77  	client := sumdb.NewClient(new(clientOps))
    78  
    79  	// start workers and response processor
    80  	tasks := make(chan mapKey)
    81  	results := make(chan string)
    82  	var wg sync.WaitGroup
    83  	for i := 0; i < artifact.MaxGoroutines; i++ {
    84  		go func() {
    85  			for tasks := range tasks {
    86  				lines, err := client.Lookup(tasks.name, tasks.version)
    87  				if err != nil {
    88  					log.Printf("Cannot lookup package %s/%s: %v", tasks.name, tasks.version, err)
    89  					results <- ""
    90  				} else {
    91  					results <- lines[0]
    92  				}
    93  			}
    94  		}()
    95  	}
    96  	go func() {
    97  		for details := range results {
    98  			fields := strings.Fields(details)
    99  			if len(fields) != 3 {
   100  				continue
   101  			}
   102  			hash, hashType, err := ModHash(fields[2])
   103  			if err == nil {
   104  				res = append(res, artifact.Dependency{
   105  					Name:     fields[0],
   106  					Version:  fields[1],
   107  					Hash:     hash,
   108  					HashType: hashType})
   109  			}
   110  			switch output {
   111  			case artifact.Progress:
   112  				bar.Add(1)
   113  			case artifact.Debug:
   114  				fmt.Printf("%s@%s (%s)\n", fields[0], fields[1], hash)
   115  			}
   116  			wg.Done()
   117  		}
   118  	}()
   119  
   120  	seen := make(map[mapKey]struct{})
   121  	scanner := bufio.NewScanner(bytes.NewReader(buf))
   122  	list := make([]mapKey, 0)
   123  	for scanner.Scan() {
   124  		fields := strings.Fields(scanner.Text())
   125  		if len(fields) != 2 {
   126  			continue // skip malformed lines
   127  		}
   128  
   129  		key := mapKey{fields[0], fields[1]}
   130  		_, ok := seen[key]
   131  		if ok {
   132  			continue // already processed
   133  		}
   134  		seen[key] = struct{}{}
   135  
   136  		list = append(list, key)
   137  	}
   138  	if output == artifact.Progress {
   139  		bar = progressbar.Default(int64(len(list)))
   140  	}
   141  	for _, entry := range list {
   142  		tasks <- entry
   143  		wg.Add(1)
   144  	}
   145  
   146  	wg.Wait()
   147  	close(tasks)
   148  	close(results)
   149  
   150  	a.Deps = res
   151  	return res, nil
   152  }
   153  
   154  func (*clientOps) ReadConfig(file string) ([]byte, error) {
   155  	if file == "key" {
   156  		return []byte(sumDb), nil
   157  	}
   158  	if strings.HasSuffix(file, "/latest") {
   159  		// Looking for cached latest tree head.
   160  		// Empty result means empty tree.
   161  		return []byte{}, nil
   162  	}
   163  	return nil, fmt.Errorf("unknown config %s", file)
   164  }
   165  
   166  func (*clientOps) WriteConfig(file string, old, new []byte) error {
   167  	// Ignore writes.
   168  	return nil
   169  }
   170  
   171  func (*clientOps) ReadCache(file string) ([]byte, error) {
   172  	return nil, fmt.Errorf("no cache")
   173  }
   174  
   175  func (*clientOps) WriteCache(file string, data []byte) {
   176  	// Ignore writes.
   177  }
   178  
   179  func (*clientOps) Log(msg string) {
   180  	log.Print(msg)
   181  }
   182  
   183  func (*clientOps) SecurityError(msg string) {
   184  	log.Fatal(msg)
   185  }
   186  
   187  func init() {
   188  	http.DefaultClient.Timeout = 1 * time.Minute
   189  }
   190  
   191  func (*clientOps) ReadRemote(path string) ([]byte, error) {
   192  	name := sumDb
   193  	if i := strings.Index(name, "+"); i >= 0 {
   194  		name = name[:i]
   195  	}
   196  	target := "https://" + name + path
   197  	resp, err := http.Get(target)
   198  	if err != nil {
   199  		return nil, err
   200  	}
   201  	defer resp.Body.Close()
   202  	if resp.StatusCode != 200 {
   203  		return nil, fmt.Errorf("GET %v: %v", target, resp.Status)
   204  	}
   205  	data, err := ioutil.ReadAll(io.LimitReader(resp.Body, 1<<20))
   206  	if err != nil {
   207  		return nil, err
   208  	}
   209  
   210  	return data, nil
   211  }