download.go (2690B)
1 // Copyright 2023 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 configstore abstracts interaction with the telemetry config server. 6 // Telemetry config (golang.org/x/telemetry/config) is distributed as a go 7 // module containing go.mod and config.json. Programs that upload collected 8 // counters download the latest config using `go mod download`. This provides 9 // verification of downloaded configuration and cacheability. 10 package configstore 11 12 import ( 13 "bytes" 14 "encoding/json" 15 "fmt" 16 "os" 17 "os/exec" 18 "path/filepath" 19 "sync/atomic" 20 21 "golang.org/x/telemetry/internal/telemetry" 22 ) 23 24 const ( 25 ModulePath = "golang.org/x/telemetry/config" 26 configFileName = "config.json" 27 ) 28 29 // needNoConsole is used on windows to set the windows.CREATE_NO_WINDOW 30 // creation flag. 31 var needNoConsole = func(cmd *exec.Cmd) {} 32 33 var downloads int64 34 35 // Downloads reports, for testing purposes, the number of times [Download] has 36 // been called. 37 func Downloads() int64 { 38 return atomic.LoadInt64(&downloads) 39 } 40 41 // Download fetches the requested telemetry UploadConfig using "go mod 42 // download". If envOverlay is provided, it is appended to the environment used 43 // for invoking the go command. 44 // 45 // The second result is the canonical version of the requested configuration. 46 func Download(version string, envOverlay []string) (*telemetry.UploadConfig, string, error) { 47 atomic.AddInt64(&downloads, 1) 48 49 if version == "" { 50 version = "latest" 51 } 52 modVer := ModulePath + "@" + version 53 var stdout, stderr bytes.Buffer 54 cmd := exec.Command("go", "mod", "download", "-json", modVer) 55 needNoConsole(cmd) 56 cmd.Env = append(os.Environ(), envOverlay...) 57 cmd.Stdout = &stdout 58 cmd.Stderr = &stderr 59 if err := cmd.Run(); err != nil { 60 var info struct { 61 Error string 62 } 63 if err := json.Unmarshal(stdout.Bytes(), &info); err == nil && info.Error != "" { 64 return nil, "", fmt.Errorf("failed to download config module: %v", info.Error) 65 } 66 return nil, "", fmt.Errorf("failed to download config module: %w\n%s", err, &stderr) 67 } 68 69 var info struct { 70 Dir string 71 Version string 72 Error string 73 } 74 if err := json.Unmarshal(stdout.Bytes(), &info); err != nil || info.Dir == "" { 75 return nil, "", fmt.Errorf("failed to download config module (invalid JSON): %w", err) 76 } 77 data, err := os.ReadFile(filepath.Join(info.Dir, configFileName)) 78 if err != nil { 79 return nil, "", fmt.Errorf("invalid config module: %w", err) 80 } 81 cfg := new(telemetry.UploadConfig) 82 if err := json.Unmarshal(data, cfg); err != nil { 83 return nil, "", fmt.Errorf("invalid config: %w", err) 84 } 85 return cfg, info.Version, nil 86 }