1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
|
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 != "0" {
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
}
|