// Copyright 2023 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Package configstore abstracts interaction with the telemetry config server. // Telemetry config (golang.org/x/telemetry/config) is distributed as a go // module containing go.mod and config.json. Programs that upload collected // counters download the latest config using `go mod download`. This provides // verification of downloaded configuration and cacheability. package configstore import ( "bytes" "encoding/json" "fmt" "os" "os/exec" "path/filepath" "sync/atomic" "golang.org/x/telemetry/internal/telemetry" ) const ( ModulePath = "golang.org/x/telemetry/config" configFileName = "config.json" ) // needNoConsole is used on windows to set the windows.CREATE_NO_WINDOW // creation flag. var needNoConsole = func(cmd *exec.Cmd) {} var downloads int64 // Downloads reports, for testing purposes, the number of times [Download] has // been called. func Downloads() int64 { return atomic.LoadInt64(&downloads) } // Download fetches the requested telemetry UploadConfig using "go mod // download". If envOverlay is provided, it is appended to the environment used // for invoking the go command. // // The second result is the canonical version of the requested configuration. func Download(version string, envOverlay []string) (*telemetry.UploadConfig, string, error) { atomic.AddInt64(&downloads, 1) if version == "" { version = "latest" } modVer := ModulePath + "@" + version var stdout, stderr bytes.Buffer cmd := exec.Command("go", "mod", "download", "-json", modVer) needNoConsole(cmd) cmd.Env = append(os.Environ(), envOverlay...) cmd.Stdout = &stdout cmd.Stderr = &stderr if err := cmd.Run(); err != nil { var info struct { Error string } if err := json.Unmarshal(stdout.Bytes(), &info); err == nil && info.Error != "" { return nil, "", fmt.Errorf("failed to download config module: %v", info.Error) } return nil, "", fmt.Errorf("failed to download config module: %w\n%s", err, &stderr) } var info struct { Dir string Version string Error string } if err := json.Unmarshal(stdout.Bytes(), &info); err != nil || info.Dir == "" { return nil, "", fmt.Errorf("failed to download config module (invalid JSON): %w", err) } data, err := os.ReadFile(filepath.Join(info.Dir, configFileName)) if err != nil { return nil, "", fmt.Errorf("invalid config module: %w", err) } cfg := new(telemetry.UploadConfig) if err := json.Unmarshal(data, cfg); err != nil { return nil, "", fmt.Errorf("invalid config: %w", err) } return cfg, info.Version, nil }