run.go (4353B)
1 // Copyright 2022 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package scan 6 7 import ( 8 "context" 9 "fmt" 10 "io" 11 "os/exec" 12 "path" 13 "path/filepath" 14 "runtime/debug" 15 "strings" 16 "time" 17 18 "golang.org/x/telemetry/counter" 19 "golang.org/x/vuln/internal/client" 20 "golang.org/x/vuln/internal/govulncheck" 21 "golang.org/x/vuln/internal/openvex" 22 "golang.org/x/vuln/internal/sarif" 23 ) 24 25 // RunGovulncheck performs main govulncheck functionality. 26 // On failure, the returned error wraps an exit code error (see scan.Cmd.Wait). 27 func RunGovulncheck(ctx context.Context, env []string, r io.Reader, stdout io.Writer, stderr io.Writer, args []string) error { 28 cfg := &config{env: env} 29 if err := parseFlags(cfg, stderr, args); err != nil { 30 return err 31 } 32 33 client, err := client.NewClient(cfg.db, nil) 34 if err != nil { 35 return fmt.Errorf("creating client: %w", err) 36 } 37 38 prepareConfig(ctx, cfg, client) 39 var handler govulncheck.Handler 40 switch cfg.format { 41 case formatJSON: 42 handler = govulncheck.NewJSONHandler(stdout) 43 case formatSarif: 44 handler = sarif.NewHandler(stdout) 45 case formatOpenVEX: 46 handler = openvex.NewHandler(stdout) 47 default: 48 th := NewTextHandler(stdout) 49 cfg.show.Update(th) 50 handler = th 51 } 52 53 if err := handler.Config(&cfg.Config); err != nil { 54 return err 55 } 56 57 if cfg.version { 58 // If the -version flag is passed, exit before doing anything else. This is different than 59 // passing -show which includes "version". 60 return nil 61 } 62 63 incTelemetryFlagCounters(cfg) 64 65 switch cfg.ScanMode { 66 case govulncheck.ScanModeSource: 67 dir := filepath.FromSlash(cfg.dir) 68 err = runSource(ctx, handler, cfg, client, dir) 69 case govulncheck.ScanModeBinary: 70 err = runBinary(ctx, handler, cfg, client) 71 case govulncheck.ScanModeExtract: 72 return runExtract(cfg, stdout) 73 case govulncheck.ScanModeQuery: 74 err = runQuery(ctx, handler, cfg, client) 75 case govulncheck.ScanModeConvert: 76 err = govulncheck.HandleJSON(r, handler) 77 } 78 if err != nil { 79 return err 80 } 81 return Flush(handler) 82 } 83 84 func prepareConfig(ctx context.Context, cfg *config, client *client.Client) { 85 cfg.ProtocolVersion = govulncheck.ProtocolVersion 86 cfg.DB = cfg.db 87 if cfg.ScanMode == govulncheck.ScanModeSource && cfg.GoVersion == "" { 88 const goverPrefix = "GOVERSION=" 89 for _, env := range cfg.env { 90 if val := strings.TrimPrefix(env, goverPrefix); val != env { 91 cfg.GoVersion = val 92 } 93 } 94 if cfg.GoVersion == "" { 95 if out, err := exec.Command("go", "env", "GOVERSION").Output(); err == nil { 96 cfg.GoVersion = strings.TrimSpace(string(out)) 97 } 98 } 99 } 100 if bi, ok := debug.ReadBuildInfo(); ok { 101 scannerVersion(cfg, bi) 102 } 103 if mod, err := client.LastModifiedTime(ctx); err == nil { 104 cfg.DBLastModified = &mod 105 } 106 } 107 108 // scannerVersion reconstructs the current version of 109 // this binary used from the build info. 110 func scannerVersion(cfg *config, bi *debug.BuildInfo) { 111 if bi.Path != "" { 112 cfg.ScannerName = strings.TrimSuffix(path.Base(bi.Path), ".test") 113 } 114 if bi.Main.Version != "" && bi.Main.Version != "(devel)" { 115 cfg.ScannerVersion = bi.Main.Version 116 return 117 } 118 119 // TODO(https://go.dev/issue/29228): we need to manually construct the 120 // version string when it is "(devel)" until #29228 is resolved. 121 var revision, at string 122 for _, s := range bi.Settings { 123 if s.Key == "vcs.revision" { 124 revision = s.Value 125 } 126 if s.Key == "vcs.time" { 127 at = s.Value 128 } 129 } 130 buf := strings.Builder{} 131 buf.WriteString("v0.0.0") 132 if revision != "" { 133 buf.WriteString("-") 134 buf.WriteString(revision[:12]) 135 } 136 if at != "" { 137 // commit time is of the form 2023-01-25T19:57:54Z 138 p, err := time.Parse(time.RFC3339, at) 139 if err == nil { 140 buf.WriteString("-") 141 buf.WriteString(p.Format("20060102150405")) 142 } 143 } 144 cfg.ScannerVersion = buf.String() 145 } 146 147 func incTelemetryFlagCounters(cfg *config) { 148 counter.Inc(fmt.Sprintf("govulncheck/mode:%s", cfg.ScanMode)) 149 counter.Inc(fmt.Sprintf("govulncheck/scan:%s", cfg.ScanLevel)) 150 counter.Inc(fmt.Sprintf("govulncheck/format:%s", cfg.format)) 151 152 if len(cfg.show) == 0 { 153 counter.Inc("govulncheck/show:none") 154 } 155 for _, s := range cfg.show { 156 counter.Inc(fmt.Sprintf("govulncheck/show:%s", s)) 157 } 158 } 159 160 func Flush(h govulncheck.Handler) error { 161 if th, ok := h.(interface{ Flush() error }); ok { 162 return th.Flush() 163 } 164 return nil 165 }