mirror of
https://github.com/hashicorp/packer.git
synced 2026-09-20 15:01:40 -04:00
* Updating the license from MPL to Business Source License Going forward, this project will be licensed under the Business Source License v1.1. Please see our blog post for more details at https://hashi.co/bsl-blog, FAQ at https://hashi.co/license-faq, and details of the license at www.hashicorp.com/bsl. * Update copyright file headers to BUSL-1.1 --------- Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com>
255 lines
5.9 KiB
Go
255 lines
5.9 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package packer
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"time"
|
|
|
|
checkpoint "github.com/hashicorp/go-checkpoint"
|
|
"github.com/hashicorp/packer-plugin-sdk/pathing"
|
|
packerVersion "github.com/hashicorp/packer/version"
|
|
"github.com/zclconf/go-cty/cty"
|
|
)
|
|
|
|
type PackerTemplateType string
|
|
|
|
const (
|
|
UnknownTemplate PackerTemplateType = "Unknown"
|
|
HCL2Template PackerTemplateType = "HCL2"
|
|
JSONTemplate PackerTemplateType = "JSON"
|
|
)
|
|
|
|
const TelemetryVersion string = "beta/packer/7"
|
|
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"`
|
|
TemplateType PackerTemplateType `json:"template_type"`
|
|
UseBundled bool `json:"use_bundled"`
|
|
}
|
|
|
|
type CheckpointTelemetry struct {
|
|
spans []*TelemetrySpan
|
|
signatureFile string
|
|
startTime time.Time
|
|
templateType PackerTemplateType
|
|
useBundled bool
|
|
}
|
|
|
|
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(),
|
|
templateType: UnknownTemplate,
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
// SetTemplateType registers the template type being processed for a Packer command
|
|
func (c *CheckpointTelemetry) SetTemplateType(t PackerTemplateType) {
|
|
if c == nil {
|
|
return
|
|
}
|
|
|
|
c.templateType = t
|
|
}
|
|
|
|
// SetBundledUsage marks the template as using bundled plugins
|
|
func (c *CheckpointTelemetry) SetBundledUsage() {
|
|
c.useBundled = true
|
|
}
|
|
|
|
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()
|
|
}
|
|
|
|
extra.UseBundled = c.useBundled
|
|
extra.TemplateType = c.templateType
|
|
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) {
|
|
switch opt := options.(type) {
|
|
case map[string]interface{}:
|
|
return flattenJSON(prefix, options)
|
|
case cty.Value:
|
|
return flattenHCL(prefix, opt)
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
flattened := flatten("", options)
|
|
sort.Strings(flattened)
|
|
return flattened
|
|
}
|
|
|
|
func flattenJSON(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, flattenJSON(k, n)...)
|
|
} else {
|
|
strOpts = append(strOpts, k)
|
|
}
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func flattenHCL(prefix string, v cty.Value) (args []string) {
|
|
if v.IsNull() {
|
|
return []string{}
|
|
}
|
|
t := v.Type()
|
|
switch {
|
|
case t.IsObjectType(), t.IsMapType():
|
|
if !v.IsKnown() {
|
|
return []string{}
|
|
}
|
|
it := v.ElementIterator()
|
|
for it.Next() {
|
|
key, val := it.Element()
|
|
keyStr := key.AsString()
|
|
|
|
if val.IsNull() {
|
|
continue
|
|
}
|
|
|
|
if prefix != "" {
|
|
keyStr = fmt.Sprintf("%s/%s", prefix, keyStr)
|
|
}
|
|
|
|
if val.Type().IsObjectType() || val.Type().IsMapType() {
|
|
args = append(args, flattenHCL(keyStr, val)...)
|
|
} else {
|
|
args = append(args, keyStr)
|
|
}
|
|
}
|
|
}
|
|
return args
|
|
}
|