packer-cn/packer/telemetry.go

176 lines
4.1 KiB
Go
Raw Normal View History

package packer
import (
"context"
"log"
"os"
"path/filepath"
"sort"
"time"
checkpoint "github.com/hashicorp/go-checkpoint"
2020-12-17 16:29:25 -05:00
"github.com/hashicorp/packer-plugin-sdk/pathing"
packerVersion "github.com/hashicorp/packer/version"
)
2017-11-02 22:46:03 -04:00
const TelemetryVersion string = "beta/packer/5"
const TelemetryPanicVersion string = "beta/packer_panic/4"
var CheckpointReporter *CheckpointTelemetry
type PackerReport struct {
Spans []*TelemetrySpan `json:"spans"`
ExitCode int `json:"exit_code"`
Error string `json:"error"`
Command string `json:"command"`
}
type CheckpointTelemetry struct {
spans []*TelemetrySpan
signatureFile string
startTime time.Time
}
func NewCheckpointReporter(disableSignature bool) *CheckpointTelemetry {
if disabled := os.Getenv("CHECKPOINT_DISABLE"); disabled != "" {
return nil
}
2020-12-02 16:19:45 -05:00
configDir, err := pathing.ConfigDir()
if err != nil {
2017-06-21 17:07:12 -04:00
log.Printf("[WARN] (telemetry) setup error: %s", err)
return nil
}
signatureFile := ""
if disableSignature {
2017-06-21 17:07:12 -04:00
log.Printf("[INFO] (telemetry) Checkpoint signature disabled")
} else {
signatureFile = filepath.Join(configDir, "checkpoint_signature")
}
return &CheckpointTelemetry{
signatureFile: signatureFile,
startTime: time.Now().UTC(),
}
}
func (c *CheckpointTelemetry) baseParams(prefix string) *checkpoint.ReportParams {
version := packerVersion.Version
if packerVersion.VersionPrerelease != "" {
2017-06-16 20:23:41 -04:00
version += "-" + packerVersion.VersionPrerelease
}
return &checkpoint.ReportParams{
Product: "packer",
SchemaVersion: prefix,
StartTime: c.startTime,
Version: version,
RunID: os.Getenv("PACKER_RUN_UUID"),
SignatureFile: c.signatureFile,
}
}
func (c *CheckpointTelemetry) ReportPanic(m string) error {
if c == nil {
return nil
}
panicParams := c.baseParams(TelemetryPanicVersion)
panicParams.Payload = m
panicParams.EndTime = time.Now().UTC()
// This timeout can be longer because it runs in the real main.
// We're also okay waiting a bit longer to collect panic information
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
return checkpoint.Report(ctx, panicParams)
}
2017-11-02 22:46:03 -04:00
func (c *CheckpointTelemetry) AddSpan(name, pluginType string, options interface{}) *TelemetrySpan {
if c == nil {
return nil
}
2017-06-21 17:07:12 -04:00
log.Printf("[INFO] (telemetry) Starting %s %s", pluginType, name)
2017-11-02 22:46:03 -04:00
ts := &TelemetrySpan{
Name: name,
2017-11-03 02:45:01 -04:00
Options: flattenConfigKeys(options),
StartTime: time.Now().UTC(),
2017-11-03 02:45:01 -04:00
Type: pluginType,
}
c.spans = append(c.spans, ts)
return ts
}
func (c *CheckpointTelemetry) Finalize(command string, errCode int, err error) error {
if c == nil {
return nil
}
params := c.baseParams(TelemetryVersion)
params.EndTime = time.Now().UTC()
extra := &PackerReport{
Spans: c.spans,
ExitCode: errCode,
Command: command,
}
if err != nil {
extra.Error = err.Error()
}
params.Payload = extra
2017-11-03 02:45:01 -04:00
// b, _ := json.MarshalIndent(params, "", " ")
// log.Println(string(b))
2017-11-03 02:54:03 -04:00
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
log.Printf("[INFO] (telemetry) Finalizing.")
return checkpoint.Report(ctx, params)
}
type TelemetrySpan struct {
EndTime time.Time `json:"end_time"`
Error string `json:"error"`
2017-11-03 02:45:01 -04:00
Name string `json:"name"`
2017-11-02 22:46:03 -04:00
Options []string `json:"options"`
2017-11-03 02:45:01 -04:00
StartTime time.Time `json:"start_time"`
Type string `json:"type"`
}
func (s *TelemetrySpan) End(err error) {
if s == nil {
return
}
s.EndTime = time.Now().UTC()
2017-06-21 17:07:12 -04:00
log.Printf("[INFO] (telemetry) ending %s", s.Name)
if err != nil {
s.Error = err.Error()
}
}
2017-11-03 02:45:01 -04:00
func flattenConfigKeys(options interface{}) []string {
var flatten func(string, interface{}) []string
flatten = func(prefix string, options interface{}) (strOpts []string) {
if m, ok := options.(map[string]interface{}); ok {
for k, v := range m {
if prefix != "" {
k = prefix + "/" + k
}
if n, ok := v.(map[string]interface{}); ok {
strOpts = append(strOpts, flatten(k, n)...)
} else {
strOpts = append(strOpts, k)
}
}
}
return
}
flattened := flatten("", options)
sort.Strings(flattened)
return flattened
2017-11-03 02:45:01 -04:00
}