github.com/paybyphone/terraform@v0.9.5-0.20170613192930-9706042ddd51/terraform/debug.go (about)

     1  package terraform
     2  
     3  import (
     4  	"archive/tar"
     5  	"bytes"
     6  	"compress/gzip"
     7  	"encoding/json"
     8  	"fmt"
     9  	"io"
    10  	"os"
    11  	"path/filepath"
    12  	"sync"
    13  	"time"
    14  )
    15  
    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.
    19  var dbug *debugInfo
    20  
    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") == "" {
    27  		return nil
    28  	}
    29  
    30  	di, err := newDebugInfoFile(path)
    31  	if err != nil {
    32  		return err
    33  	}
    34  
    35  	dbug = di
    36  	return nil
    37  }
    38  
    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 {
    43  	return dbug.Close()
    44  }
    45  
    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)
    50  	if err != nil {
    51  		return nil, err
    52  	}
    53  
    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")
    57  
    58  	f, err := os.OpenFile(archivePath, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666)
    59  	if err != nil {
    60  		return nil, err
    61  	}
    62  	return newDebugInfo(name, f)
    63  }
    64  
    65  // newDebugInfo initializes the global debug handler.
    66  func newDebugInfo(name string, w io.Writer) (*debugInfo, error) {
    67  	gz := gzip.NewWriter(w)
    68  
    69  	d := &debugInfo{
    70  		name: name,
    71  		w:    w,
    72  		gz:   gz,
    73  		tar:  tar.NewWriter(gz),
    74  	}
    75  
    76  	// create the subdirs we need
    77  	topHdr := &tar.Header{
    78  		Name:     name,
    79  		Typeflag: tar.TypeDir,
    80  		Mode:     0755,
    81  	}
    82  	graphsHdr := &tar.Header{
    83  		Name:     name + "/graphs",
    84  		Typeflag: tar.TypeDir,
    85  		Mode:     0755,
    86  	}
    87  	err := d.tar.WriteHeader(topHdr)
    88  	// if the first errors, the second will too
    89  	err = d.tar.WriteHeader(graphsHdr)
    90  	if err != nil {
    91  		return nil, err
    92  	}
    93  
    94  	return d, nil
    95  }
    96  
    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.
   103  //
   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 {
   111  	sync.Mutex
   112  
   113  	// archive root directory name
   114  	name string
   115  
   116  	// current operation phase
   117  	phase string
   118  
   119  	// step is monotonic counter for for recording the order of operations
   120  	step int
   121  
   122  	// flag to protect Close()
   123  	closed bool
   124  
   125  	// the debug log output is in a tar.gz format, written to the io.Writer w
   126  	w   io.Writer
   127  	gz  *gzip.Writer
   128  	tar *tar.Writer
   129  }
   130  
   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) {
   135  	if d == nil {
   136  		return
   137  	}
   138  	d.Lock()
   139  	defer d.Unlock()
   140  
   141  	d.phase = phase
   142  }
   143  
   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
   146  // also closed.
   147  func (d *debugInfo) Close() error {
   148  	if d == nil {
   149  		return nil
   150  	}
   151  
   152  	d.Lock()
   153  	defer d.Unlock()
   154  
   155  	if d.closed {
   156  		return nil
   157  	}
   158  	d.closed = true
   159  
   160  	d.tar.Close()
   161  	d.gz.Close()
   162  
   163  	if c, ok := d.w.(io.Closer); ok {
   164  		return c.Close()
   165  	}
   166  	return nil
   167  }
   168  
   169  // debug buffer is an io.WriteCloser that will write itself to the debug
   170  // archive when closed.
   171  type debugBuffer struct {
   172  	debugInfo *debugInfo
   173  	name      string
   174  	buf       bytes.Buffer
   175  }
   176  
   177  func (b *debugBuffer) Write(d []byte) (int, error) {
   178  	return b.buf.Write(d)
   179  }
   180  
   181  func (b *debugBuffer) Close() error {
   182  	return b.debugInfo.WriteFile(b.name, b.buf.Bytes())
   183  }
   184  
   185  // ioutils only has a noop ReadCloser
   186  type nopWriteCloser struct{}
   187  
   188  func (nopWriteCloser) Write([]byte) (int, error) { return 0, nil }
   189  func (nopWriteCloser) Close() error              { return nil }
   190  
   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 {
   194  	if d == nil {
   195  		return nopWriteCloser{}
   196  	}
   197  
   198  	return &debugBuffer{
   199  		debugInfo: d,
   200  		name:      name,
   201  	}
   202  }
   203  
   204  type syncer interface {
   205  	Sync() error
   206  }
   207  
   208  type flusher interface {
   209  	Flush() error
   210  }
   211  
   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() {
   215  	d.tar.Flush()
   216  	d.gz.Flush()
   217  
   218  	if f, ok := d.w.(flusher); ok {
   219  		f.Flush()
   220  	}
   221  
   222  	if s, ok := d.w.(syncer); ok {
   223  		s.Sync()
   224  	}
   225  }
   226  
   227  // WriteFile writes data as a single file to the debug arhive.
   228  func (d *debugInfo) WriteFile(name string, data []byte) error {
   229  	if d == nil {
   230  		return nil
   231  	}
   232  
   233  	d.Lock()
   234  	defer d.Unlock()
   235  	return d.writeFile(name, data)
   236  }
   237  
   238  func (d *debugInfo) writeFile(name string, data []byte) error {
   239  	defer d.flush()
   240  	path := fmt.Sprintf("%s/%d-%s-%s", d.name, d.step, d.phase, name)
   241  	d.step++
   242  
   243  	hdr := &tar.Header{
   244  		Name: path,
   245  		Mode: 0644,
   246  		Size: int64(len(data)),
   247  	}
   248  	err := d.tar.WriteHeader(hdr)
   249  	if err != nil {
   250  		return err
   251  	}
   252  
   253  	_, err = d.tar.Write(data)
   254  	return err
   255  }
   256  
   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{}
   263  
   264  func (*DebugHook) PreApply(ii *InstanceInfo, is *InstanceState, id *InstanceDiff) (HookAction, error) {
   265  	if dbug == nil {
   266  		return HookActionContinue, nil
   267  	}
   268  
   269  	var buf bytes.Buffer
   270  
   271  	if ii != nil {
   272  		buf.WriteString(ii.HumanId() + "\n")
   273  	}
   274  
   275  	if is != nil {
   276  		buf.WriteString(is.String() + "\n")
   277  	}
   278  
   279  	idCopy, err := id.Copy()
   280  	if err != nil {
   281  		return HookActionContinue, err
   282  	}
   283  	js, err := json.MarshalIndent(idCopy, "", "  ")
   284  	if err != nil {
   285  		return HookActionContinue, err
   286  	}
   287  	buf.Write(js)
   288  
   289  	dbug.WriteFile("hook-PreApply", buf.Bytes())
   290  
   291  	return HookActionContinue, nil
   292  }
   293  
   294  func (*DebugHook) PostApply(ii *InstanceInfo, is *InstanceState, err error) (HookAction, error) {
   295  	if dbug == nil {
   296  		return HookActionContinue, nil
   297  	}
   298  
   299  	var buf bytes.Buffer
   300  
   301  	if ii != nil {
   302  		buf.WriteString(ii.HumanId() + "\n")
   303  	}
   304  
   305  	if is != nil {
   306  		buf.WriteString(is.String() + "\n")
   307  	}
   308  
   309  	if err != nil {
   310  		buf.WriteString(err.Error())
   311  	}
   312  
   313  	dbug.WriteFile("hook-PostApply", buf.Bytes())
   314  
   315  	return HookActionContinue, nil
   316  }
   317  
   318  func (*DebugHook) PreDiff(ii *InstanceInfo, is *InstanceState) (HookAction, error) {
   319  	if dbug == nil {
   320  		return HookActionContinue, nil
   321  	}
   322  
   323  	var buf bytes.Buffer
   324  	if ii != nil {
   325  		buf.WriteString(ii.HumanId() + "\n")
   326  	}
   327  
   328  	if is != nil {
   329  		buf.WriteString(is.String())
   330  		buf.WriteString("\n")
   331  	}
   332  	dbug.WriteFile("hook-PreDiff", buf.Bytes())
   333  
   334  	return HookActionContinue, nil
   335  }
   336  
   337  func (*DebugHook) PostDiff(ii *InstanceInfo, id *InstanceDiff) (HookAction, error) {
   338  	if dbug == nil {
   339  		return HookActionContinue, nil
   340  	}
   341  
   342  	var buf bytes.Buffer
   343  	if ii != nil {
   344  		buf.WriteString(ii.HumanId() + "\n")
   345  	}
   346  
   347  	idCopy, err := id.Copy()
   348  	if err != nil {
   349  		return HookActionContinue, err
   350  	}
   351  	js, err := json.MarshalIndent(idCopy, "", "  ")
   352  	if err != nil {
   353  		return HookActionContinue, err
   354  	}
   355  	buf.Write(js)
   356  
   357  	dbug.WriteFile("hook-PostDiff", buf.Bytes())
   358  
   359  	return HookActionContinue, nil
   360  }
   361  
   362  func (*DebugHook) PreProvisionResource(ii *InstanceInfo, is *InstanceState) (HookAction, error) {
   363  	if dbug == nil {
   364  		return HookActionContinue, nil
   365  	}
   366  
   367  	var buf bytes.Buffer
   368  	if ii != nil {
   369  		buf.WriteString(ii.HumanId() + "\n")
   370  	}
   371  
   372  	if is != nil {
   373  		buf.WriteString(is.String())
   374  		buf.WriteString("\n")
   375  	}
   376  	dbug.WriteFile("hook-PreProvisionResource", buf.Bytes())
   377  
   378  	return HookActionContinue, nil
   379  }
   380  
   381  func (*DebugHook) PostProvisionResource(ii *InstanceInfo, is *InstanceState) (HookAction, error) {
   382  	if dbug == nil {
   383  		return HookActionContinue, nil
   384  	}
   385  
   386  	var buf bytes.Buffer
   387  	if ii != nil {
   388  		buf.WriteString(ii.HumanId())
   389  		buf.WriteString("\n")
   390  	}
   391  
   392  	if is != nil {
   393  		buf.WriteString(is.String())
   394  		buf.WriteString("\n")
   395  	}
   396  	dbug.WriteFile("hook-PostProvisionResource", buf.Bytes())
   397  	return HookActionContinue, nil
   398  }
   399  
   400  func (*DebugHook) PreProvision(ii *InstanceInfo, s string) (HookAction, error) {
   401  	if dbug == nil {
   402  		return HookActionContinue, nil
   403  	}
   404  
   405  	var buf bytes.Buffer
   406  	if ii != nil {
   407  		buf.WriteString(ii.HumanId())
   408  		buf.WriteString("\n")
   409  	}
   410  	buf.WriteString(s + "\n")
   411  
   412  	dbug.WriteFile("hook-PreProvision", buf.Bytes())
   413  	return HookActionContinue, nil
   414  }
   415  
   416  func (*DebugHook) PostProvision(ii *InstanceInfo, s string, err error) (HookAction, error) {
   417  	if dbug == nil {
   418  		return HookActionContinue, nil
   419  	}
   420  
   421  	var buf bytes.Buffer
   422  	if ii != nil {
   423  		buf.WriteString(ii.HumanId() + "\n")
   424  	}
   425  	buf.WriteString(s + "\n")
   426  
   427  	dbug.WriteFile("hook-PostProvision", buf.Bytes())
   428  	return HookActionContinue, nil
   429  }
   430  
   431  func (*DebugHook) ProvisionOutput(ii *InstanceInfo, s1 string, s2 string) {
   432  	if dbug == nil {
   433  		return
   434  	}
   435  
   436  	var buf bytes.Buffer
   437  	if ii != nil {
   438  		buf.WriteString(ii.HumanId())
   439  		buf.WriteString("\n")
   440  	}
   441  	buf.WriteString(s1 + "\n")
   442  	buf.WriteString(s2 + "\n")
   443  
   444  	dbug.WriteFile("hook-ProvisionOutput", buf.Bytes())
   445  }
   446  
   447  func (*DebugHook) PreRefresh(ii *InstanceInfo, is *InstanceState) (HookAction, error) {
   448  	if dbug == nil {
   449  		return HookActionContinue, nil
   450  	}
   451  
   452  	var buf bytes.Buffer
   453  	if ii != nil {
   454  		buf.WriteString(ii.HumanId() + "\n")
   455  	}
   456  
   457  	if is != nil {
   458  		buf.WriteString(is.String())
   459  		buf.WriteString("\n")
   460  	}
   461  	dbug.WriteFile("hook-PreRefresh", buf.Bytes())
   462  	return HookActionContinue, nil
   463  }
   464  
   465  func (*DebugHook) PostRefresh(ii *InstanceInfo, is *InstanceState) (HookAction, error) {
   466  	if dbug == nil {
   467  		return HookActionContinue, nil
   468  	}
   469  
   470  	var buf bytes.Buffer
   471  	if ii != nil {
   472  		buf.WriteString(ii.HumanId())
   473  		buf.WriteString("\n")
   474  	}
   475  
   476  	if is != nil {
   477  		buf.WriteString(is.String())
   478  		buf.WriteString("\n")
   479  	}
   480  	dbug.WriteFile("hook-PostRefresh", buf.Bytes())
   481  	return HookActionContinue, nil
   482  }
   483  
   484  func (*DebugHook) PreImportState(ii *InstanceInfo, s string) (HookAction, error) {
   485  	if dbug == nil {
   486  		return HookActionContinue, nil
   487  	}
   488  
   489  	var buf bytes.Buffer
   490  	if ii != nil {
   491  		buf.WriteString(ii.HumanId())
   492  		buf.WriteString("\n")
   493  	}
   494  	buf.WriteString(s + "\n")
   495  
   496  	dbug.WriteFile("hook-PreImportState", buf.Bytes())
   497  	return HookActionContinue, nil
   498  }
   499  
   500  func (*DebugHook) PostImportState(ii *InstanceInfo, iss []*InstanceState) (HookAction, error) {
   501  	if dbug == nil {
   502  		return HookActionContinue, nil
   503  	}
   504  
   505  	var buf bytes.Buffer
   506  
   507  	if ii != nil {
   508  		buf.WriteString(ii.HumanId() + "\n")
   509  	}
   510  
   511  	for _, is := range iss {
   512  		if is != nil {
   513  			buf.WriteString(is.String() + "\n")
   514  		}
   515  	}
   516  	dbug.WriteFile("hook-PostImportState", buf.Bytes())
   517  	return HookActionContinue, nil
   518  }
   519  
   520  // skip logging this for now, since it could be huge
   521  func (*DebugHook) PostStateUpdate(*State) (HookAction, error) {
   522  	return HookActionContinue, nil
   523  }