github.phpd.cn/hashicorp/packer@v1.3.2/packer/telemetry.go (about) 1 package packer 2 3 import ( 4 "context" 5 "log" 6 "os" 7 "path/filepath" 8 "sort" 9 "time" 10 11 checkpoint "github.com/hashicorp/go-checkpoint" 12 packerVersion "github.com/hashicorp/packer/version" 13 ) 14 15 const TelemetryVersion string = "beta/packer/5" 16 const TelemetryPanicVersion string = "beta/packer_panic/4" 17 18 var CheckpointReporter *CheckpointTelemetry 19 20 type PackerReport struct { 21 Spans []*TelemetrySpan `json:"spans"` 22 ExitCode int `json:"exit_code"` 23 Error string `json:"error"` 24 Command string `json:"command"` 25 } 26 27 type CheckpointTelemetry struct { 28 spans []*TelemetrySpan 29 signatureFile string 30 startTime time.Time 31 } 32 33 func NewCheckpointReporter(disableSignature bool) *CheckpointTelemetry { 34 if disabled := os.Getenv("CHECKPOINT_DISABLE"); disabled != "" { 35 return nil 36 } 37 38 configDir, err := ConfigDir() 39 if err != nil { 40 log.Printf("[WARN] (telemetry) setup error: %s", err) 41 return nil 42 } 43 44 signatureFile := "" 45 if disableSignature { 46 log.Printf("[INFO] (telemetry) Checkpoint signature disabled") 47 } else { 48 signatureFile = filepath.Join(configDir, "checkpoint_signature") 49 } 50 51 return &CheckpointTelemetry{ 52 signatureFile: signatureFile, 53 startTime: time.Now().UTC(), 54 } 55 } 56 57 func (c *CheckpointTelemetry) baseParams(prefix string) *checkpoint.ReportParams { 58 version := packerVersion.Version 59 if packerVersion.VersionPrerelease != "" { 60 version += "-" + packerVersion.VersionPrerelease 61 } 62 63 return &checkpoint.ReportParams{ 64 Product: "packer", 65 SchemaVersion: prefix, 66 StartTime: c.startTime, 67 Version: version, 68 RunID: os.Getenv("PACKER_RUN_UUID"), 69 SignatureFile: c.signatureFile, 70 } 71 } 72 73 func (c *CheckpointTelemetry) ReportPanic(m string) error { 74 if c == nil { 75 return nil 76 } 77 panicParams := c.baseParams(TelemetryPanicVersion) 78 panicParams.Payload = m 79 panicParams.EndTime = time.Now().UTC() 80 81 // This timeout can be longer because it runs in the real main. 82 // We're also okay waiting a bit longer to collect panic information 83 ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) 84 defer cancel() 85 86 return checkpoint.Report(ctx, panicParams) 87 } 88 89 func (c *CheckpointTelemetry) AddSpan(name, pluginType string, options interface{}) *TelemetrySpan { 90 if c == nil { 91 return nil 92 } 93 log.Printf("[INFO] (telemetry) Starting %s %s", pluginType, name) 94 95 ts := &TelemetrySpan{ 96 Name: name, 97 Options: flattenConfigKeys(options), 98 StartTime: time.Now().UTC(), 99 Type: pluginType, 100 } 101 c.spans = append(c.spans, ts) 102 return ts 103 } 104 105 func (c *CheckpointTelemetry) Finalize(command string, errCode int, err error) error { 106 if c == nil { 107 return nil 108 } 109 110 params := c.baseParams(TelemetryVersion) 111 params.EndTime = time.Now().UTC() 112 113 extra := &PackerReport{ 114 Spans: c.spans, 115 ExitCode: errCode, 116 Command: command, 117 } 118 if err != nil { 119 extra.Error = err.Error() 120 } 121 params.Payload = extra 122 // b, _ := json.MarshalIndent(params, "", " ") 123 // log.Println(string(b)) 124 125 ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) 126 defer cancel() 127 128 log.Printf("[INFO] (telemetry) Finalizing.") 129 return checkpoint.Report(ctx, params) 130 } 131 132 type TelemetrySpan struct { 133 EndTime time.Time `json:"end_time"` 134 Error string `json:"error"` 135 Name string `json:"name"` 136 Options []string `json:"options"` 137 StartTime time.Time `json:"start_time"` 138 Type string `json:"type"` 139 } 140 141 func (s *TelemetrySpan) End(err error) { 142 if s == nil { 143 return 144 } 145 s.EndTime = time.Now().UTC() 146 log.Printf("[INFO] (telemetry) ending %s", s.Name) 147 if err != nil { 148 s.Error = err.Error() 149 } 150 } 151 152 func flattenConfigKeys(options interface{}) []string { 153 var flatten func(string, interface{}) []string 154 155 flatten = func(prefix string, options interface{}) (strOpts []string) { 156 if m, ok := options.(map[string]interface{}); ok { 157 for k, v := range m { 158 if prefix != "" { 159 k = prefix + "/" + k 160 } 161 if n, ok := v.(map[string]interface{}); ok { 162 strOpts = append(strOpts, flatten(k, n)...) 163 } else { 164 strOpts = append(strOpts, k) 165 } 166 } 167 } 168 return 169 } 170 171 flattened := flatten("", options) 172 sort.Strings(flattened) 173 return flattened 174 }