176 lines
4.1 KiB
Go
176 lines
4.1 KiB
Go
package packer
|
|
|
|
import (
|
|
"context"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"time"
|
|
|
|
checkpoint "github.com/hashicorp/go-checkpoint"
|
|
"github.com/hashicorp/packer/packer-plugin-sdk/pathing"
|
|
packerVersion "github.com/hashicorp/packer/version"
|
|
)
|
|
|
|
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
|
|
}
|
|
|
|
configDir, err := pathing.ConfigDir()
|
|
if err != nil {
|
|
log.Printf("[WARN] (telemetry) setup error: %s", err)
|
|
return nil
|
|
}
|
|
|
|
signatureFile := ""
|
|
if disableSignature {
|
|
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 != "" {
|
|
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)
|
|
}
|
|
|
|
func (c *CheckpointTelemetry) AddSpan(name, pluginType string, options interface{}) *TelemetrySpan {
|
|
if c == nil {
|
|
return nil
|
|
}
|
|
log.Printf("[INFO] (telemetry) Starting %s %s", pluginType, name)
|
|
|
|
ts := &TelemetrySpan{
|
|
Name: name,
|
|
Options: flattenConfigKeys(options),
|
|
StartTime: time.Now().UTC(),
|
|
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
|
|
// b, _ := json.MarshalIndent(params, "", " ")
|
|
// log.Println(string(b))
|
|
|
|
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"`
|
|
Name string `json:"name"`
|
|
Options []string `json:"options"`
|
|
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()
|
|
log.Printf("[INFO] (telemetry) ending %s", s.Name)
|
|
if err != nil {
|
|
s.Error = err.Error()
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|