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 }