16 // DebugInfo is the global handler for writing the debug archive. All methods
17 // are safe to call concurrently. Setting DebugInfo to nil will disable writing
18 // the debug archive. All methods are safe to call on the nil value.
21 // SetDebugInfo initializes the debug handler with a backing file in the
22 // provided directory. This must be called before any other terraform package
23 // operations or not at all. Once his is called, CloseDebugInfo should be
24 // called before program exit.
25 func SetDebugInfo(path string) error {
26 if os.Getenv("TF_DEBUG") == "" {
30 di, err := newDebugInfoFile(path)
39 // CloseDebugInfo is the exported interface to Close the debug info handler.
40 // The debug handler needs to be closed before program exit, so we export this
41 // function to be deferred in the appropriate entrypoint for our executable.
42 func CloseDebugInfo() error {
46 // newDebugInfoFile initializes the global debug handler with a backing file in
47 // the provided directory.
48 func newDebugInfoFile(dir string) (*debugInfo, error) {
49 err := os.MkdirAll(dir, 0755)
54 // FIXME: not guaranteed unique, but good enough for now
55 name := fmt.Sprintf("debug-%s", time.Now().Format("2006-01-02-15-04-05.999999999"))
56 archivePath := filepath.Join(dir, name+".tar.gz")
58 f, err := os.OpenFile(archivePath, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666)
62 return newDebugInfo(name, f)
65 // newDebugInfo initializes the global debug handler.
66 func newDebugInfo(name string, w io.Writer) (*debugInfo, error) {
67 gz := gzip.NewWriter(w)
73 tar: tar.NewWriter(gz),
76 // create the subdirs we need
77 topHdr := &tar.Header{
79 Typeflag: tar.TypeDir,
82 graphsHdr := &tar.Header{
83 Name: name + "/graphs",
84 Typeflag: tar.TypeDir,
87 err := d.tar.WriteHeader(topHdr)
88 // if the first errors, the second will too
89 err = d.tar.WriteHeader(graphsHdr)
97 // debugInfo provides various methods for writing debug information to a
98 // central archive. The debugInfo struct should be initialized once before any
99 // output is written, and Close should be called before program exit. All
100 // exported methods on debugInfo will be safe for concurrent use. The exported
101 // methods are also all safe to call on a nil pointer, so that there is no need
102 // for conditional blocks before writing debug information.
104 // Each write operation done by the debugInfo will flush the gzip.Writer and
105 // tar.Writer, and call Sync() or Flush() on the output writer as needed. This
106 // ensures that as much data as possible is written to storage in the event of
107 // a crash. The append format of the tar file, and the stream format of the
108 // gzip writer allow easy recovery f the data in the event that the debugInfo
109 // is not closed before program exit.
110 type debugInfo struct {
113 // archive root directory name
116 // current operation phase
119 // step is monotonic counter for for recording the order of operations
122 // flag to protect Close()
125 // the debug log output is in a tar.gz format, written to the io.Writer w
131 // Set the name of the current operational phase in the debug handler. Each file
132 // in the archive will contain the name of the phase in which it was created,
133 // i.e. "input", "apply", "plan", "refresh", "validate"
134 func (d *debugInfo) SetPhase(phase string) {
144 // Close the debugInfo, finalizing the data in storage. This closes the
145 // tar.Writer, the gzip.Wrtier, and if the output writer is an io.Closer, it is
147 func (d *debugInfo) Close() error {
163 if c, ok := d.w.(io.Closer); ok {
169 // debug buffer is an io.WriteCloser that will write itself to the debug
170 // archive when closed.
171 type debugBuffer struct {
177 func (b *debugBuffer) Write(d []byte) (int, error) {
178 return b.buf.Write(d)
181 func (b *debugBuffer) Close() error {
182 return b.debugInfo.WriteFile(b.name, b.buf.Bytes())
185 // ioutils only has a noop ReadCloser
186 type nopWriteCloser struct{}
188 func (nopWriteCloser) Write([]byte) (int, error) { return 0, nil }
189 func (nopWriteCloser) Close() error { return nil }
191 // NewFileWriter returns an io.WriteClose that will be buffered and written to
192 // the debug archive when closed.
193 func (d *debugInfo) NewFileWriter(name string) io.WriteCloser {
195 return nopWriteCloser{}
204 type syncer interface {
208 type flusher interface {
212 // Flush the tar.Writer and the gzip.Writer. Flush() or Sync() will be called
213 // on the output writer if they are available.
214 func (d *debugInfo) flush() {
218 if f, ok := d.w.(flusher); ok {
222 if s, ok := d.w.(syncer); ok {
227 // WriteFile writes data as a single file to the debug arhive.
228 func (d *debugInfo) WriteFile(name string, data []byte) error {
235 return d.writeFile(name, data)
238 func (d *debugInfo) writeFile(name string, data []byte) error {
240 path := fmt.Sprintf("%s/%d-%s-%s", d.name, d.step, d.phase, name)
246 Size: int64(len(data)),
248 err := d.tar.WriteHeader(hdr)
253 _, err = d.tar.Write(data)
257 // DebugHook implements all methods of the terraform.Hook interface, and writes
258 // the arguments to a file in the archive. When a suitable format for the
259 // argument isn't available, the argument is encoded using json.Marshal. If the
260 // debug handler is nil, all DebugHook methods are noop, so no time is spent in
261 // marshaling the data structures.
262 type DebugHook struct{}
264 func (*DebugHook) PreApply(ii *InstanceInfo, is *InstanceState, id *InstanceDiff) (HookAction, error) {
266 return HookActionContinue, nil
272 buf.WriteString(ii.HumanId() + "\n")
276 buf.WriteString(is.String() + "\n")
279 idCopy, err := id.Copy()
281 return HookActionContinue, err
283 js, err := json.MarshalIndent(idCopy, "", " ")
285 return HookActionContinue, err
289 dbug.WriteFile("hook-PreApply", buf.Bytes())
291 return HookActionContinue, nil
294 func (*DebugHook) PostApply(ii *InstanceInfo, is *InstanceState, err error) (HookAction, error) {
296 return HookActionContinue, nil
302 buf.WriteString(ii.HumanId() + "\n")
306 buf.WriteString(is.String() + "\n")
310 buf.WriteString(err.Error())
313 dbug.WriteFile("hook-PostApply", buf.Bytes())
315 return HookActionContinue, nil
318 func (*DebugHook) PreDiff(ii *InstanceInfo, is *InstanceState) (HookAction, error) {
320 return HookActionContinue, nil
325 buf.WriteString(ii.HumanId() + "\n")
329 buf.WriteString(is.String())
330 buf.WriteString("\n")
332 dbug.WriteFile("hook-PreDiff", buf.Bytes())
334 return HookActionContinue, nil
337 func (*DebugHook) PostDiff(ii *InstanceInfo, id *InstanceDiff) (HookAction, error) {
339 return HookActionContinue, nil
344 buf.WriteString(ii.HumanId() + "\n")
347 idCopy, err := id.Copy()
349 return HookActionContinue, err
351 js, err := json.MarshalIndent(idCopy, "", " ")
353 return HookActionContinue, err
357 dbug.WriteFile("hook-PostDiff", buf.Bytes())
359 return HookActionContinue, nil
362 func (*DebugHook) PreProvisionResource(ii *InstanceInfo, is *InstanceState) (HookAction, error) {
364 return HookActionContinue, nil
369 buf.WriteString(ii.HumanId() + "\n")
373 buf.WriteString(is.String())
374 buf.WriteString("\n")
376 dbug.WriteFile("hook-PreProvisionResource", buf.Bytes())
378 return HookActionContinue, nil
381 func (*DebugHook) PostProvisionResource(ii *InstanceInfo, is *InstanceState) (HookAction, error) {
383 return HookActionContinue, nil
388 buf.WriteString(ii.HumanId())
389 buf.WriteString("\n")
393 buf.WriteString(is.String())
394 buf.WriteString("\n")
396 dbug.WriteFile("hook-PostProvisionResource", buf.Bytes())
397 return HookActionContinue, nil
400 func (*DebugHook) PreProvision(ii *InstanceInfo, s string) (HookAction, error) {
402 return HookActionContinue, nil
407 buf.WriteString(ii.HumanId())
408 buf.WriteString("\n")
410 buf.WriteString(s + "\n")
412 dbug.WriteFile("hook-PreProvision", buf.Bytes())
413 return HookActionContinue, nil
416 func (*DebugHook) PostProvision(ii *InstanceInfo, s string, err error) (HookAction, error) {
418 return HookActionContinue, nil
423 buf.WriteString(ii.HumanId() + "\n")
425 buf.WriteString(s + "\n")
427 dbug.WriteFile("hook-PostProvision", buf.Bytes())
428 return HookActionContinue, nil
431 func (*DebugHook) ProvisionOutput(ii *InstanceInfo, s1 string, s2 string) {
438 buf.WriteString(ii.HumanId())
439 buf.WriteString("\n")
441 buf.WriteString(s1 + "\n")
442 buf.WriteString(s2 + "\n")
444 dbug.WriteFile("hook-ProvisionOutput", buf.Bytes())
447 func (*DebugHook) PreRefresh(ii *InstanceInfo, is *InstanceState) (HookAction, error) {
449 return HookActionContinue, nil
454 buf.WriteString(ii.HumanId() + "\n")
458 buf.WriteString(is.String())
459 buf.WriteString("\n")
461 dbug.WriteFile("hook-PreRefresh", buf.Bytes())
462 return HookActionContinue, nil
465 func (*DebugHook) PostRefresh(ii *InstanceInfo, is *InstanceState) (HookAction, error) {
467 return HookActionContinue, nil
472 buf.WriteString(ii.HumanId())
473 buf.WriteString("\n")
477 buf.WriteString(is.String())
478 buf.WriteString("\n")
480 dbug.WriteFile("hook-PostRefresh", buf.Bytes())
481 return HookActionContinue, nil
484 func (*DebugHook) PreImportState(ii *InstanceInfo, s string) (HookAction, error) {
486 return HookActionContinue, nil
491 buf.WriteString(ii.HumanId())
492 buf.WriteString("\n")
494 buf.WriteString(s + "\n")
496 dbug.WriteFile("hook-PreImportState", buf.Bytes())
497 return HookActionContinue, nil
500 func (*DebugHook) PostImportState(ii *InstanceInfo, iss []*InstanceState) (HookAction, error) {
502 return HookActionContinue, nil
508 buf.WriteString(ii.HumanId() + "\n")
511 for _, is := range iss {
513 buf.WriteString(is.String() + "\n")
516 dbug.WriteFile("hook-PostImportState", buf.Bytes())
517 return HookActionContinue, nil
520 // skip logging this for now, since it could be huge
521 func (*DebugHook) PostStateUpdate(*State) (HookAction, error) {
522 return HookActionContinue, nil