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  }