github.com/mkuzmin/terraform@v0.3.7-0.20161118171027-ec4c00ff92a9/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  // WriteGraph takes a DebugGraph and writes both the DebugGraph as a dot file
   228  // in the debug archive, and extracts any logs that the DebugGraph collected
   229  // and writes them to a log file in the archive.
   230  func (d *debugInfo) WriteGraph(name string, g *Graph) error {
   231  	if d == nil || g == nil {
   232  		return nil
   233  	}
   234  	d.Lock()
   235  	defer d.Unlock()
   236  
   237  	// If we crash, the file won't be correctly closed out, but we can rebuild
   238  	// the archive if we have to as long as every file has been flushed and
   239  	// sync'ed.
   240  	defer d.flush()
   241  
   242  	dotPath := fmt.Sprintf("%s/graphs/%d-%s-%s.dot", d.name, d.step, d.phase, name)
   243  	d.step++
   244  
   245  	dotBytes := g.Dot(nil)
   246  	hdr := &tar.Header{
   247  		Name: dotPath,
   248  		Mode: 0644,
   249  		Size: int64(len(dotBytes)),
   250  	}
   251  
   252  	err := d.tar.WriteHeader(hdr)
   253  	if err != nil {
   254  		return err
   255  	}
   256  
   257  	_, err = d.tar.Write(dotBytes)
   258  	return err
   259  }
   260  
   261  // WriteFile writes data as a single file to the debug arhive.
   262  func (d *debugInfo) WriteFile(name string, data []byte) error {
   263  	if d == nil {
   264  		return nil
   265  	}
   266  
   267  	d.Lock()
   268  	defer d.Unlock()
   269  	return d.writeFile(name, data)
   270  }
   271  
   272  func (d *debugInfo) writeFile(name string, data []byte) error {
   273  	defer d.flush()
   274  	path := fmt.Sprintf("%s/%d-%s-%s", d.name, d.step, d.phase, name)
   275  	d.step++
   276  
   277  	hdr := &tar.Header{
   278  		Name: path,
   279  		Mode: 0644,
   280  		Size: int64(len(data)),
   281  	}
   282  	err := d.tar.WriteHeader(hdr)
   283  	if err != nil {
   284  		return err
   285  	}
   286  
   287  	_, err = d.tar.Write(data)
   288  	return err
   289  }
   290  
   291  // DebugHook implements all methods of the terraform.Hook interface, and writes
   292  // the arguments to a file in the archive. When a suitable format for the
   293  // argument isn't available, the argument is encoded using json.Marshal. If the
   294  // debug handler is nil, all DebugHook methods are noop, so no time is spent in
   295  // marshaling the data structures.
   296  type DebugHook struct{}
   297  
   298  func (*DebugHook) PreApply(ii *InstanceInfo, is *InstanceState, id *InstanceDiff) (HookAction, error) {
   299  	if dbug == nil {
   300  		return HookActionContinue, nil
   301  	}
   302  
   303  	var buf bytes.Buffer
   304  
   305  	if ii != nil {
   306  		buf.WriteString(ii.HumanId() + "\n")
   307  	}
   308  
   309  	if is != nil {
   310  		buf.WriteString(is.String() + "\n")
   311  	}
   312  
   313  	idCopy, err := id.Copy()
   314  	if err != nil {
   315  		return HookActionContinue, err
   316  	}
   317  	js, err := json.MarshalIndent(idCopy, "", "  ")
   318  	if err != nil {
   319  		return HookActionContinue, err
   320  	}
   321  	buf.Write(js)
   322  
   323  	dbug.WriteFile("hook-PreApply", buf.Bytes())
   324  
   325  	return HookActionContinue, nil
   326  }
   327  
   328  func (*DebugHook) PostApply(ii *InstanceInfo, is *InstanceState, err error) (HookAction, error) {
   329  	if dbug == nil {
   330  		return HookActionContinue, nil
   331  	}
   332  
   333  	var buf bytes.Buffer
   334  
   335  	if ii != nil {
   336  		buf.WriteString(ii.HumanId() + "\n")
   337  	}
   338  
   339  	if is != nil {
   340  		buf.WriteString(is.String() + "\n")
   341  	}
   342  
   343  	if err != nil {
   344  		buf.WriteString(err.Error())
   345  	}
   346  
   347  	dbug.WriteFile("hook-PostApply", buf.Bytes())
   348  
   349  	return HookActionContinue, nil
   350  }
   351  
   352  func (*DebugHook) PreDiff(ii *InstanceInfo, is *InstanceState) (HookAction, error) {
   353  	if dbug == nil {
   354  		return HookActionContinue, nil
   355  	}
   356  
   357  	var buf bytes.Buffer
   358  	if ii != nil {
   359  		buf.WriteString(ii.HumanId() + "\n")
   360  	}
   361  
   362  	if is != nil {
   363  		buf.WriteString(is.String())
   364  		buf.WriteString("\n")
   365  	}
   366  	dbug.WriteFile("hook-PreDiff", buf.Bytes())
   367  
   368  	return HookActionContinue, nil
   369  }
   370  
   371  func (*DebugHook) PostDiff(ii *InstanceInfo, id *InstanceDiff) (HookAction, error) {
   372  	if dbug == nil {
   373  		return HookActionContinue, nil
   374  	}
   375  
   376  	var buf bytes.Buffer
   377  	if ii != nil {
   378  		buf.WriteString(ii.HumanId() + "\n")
   379  	}
   380  
   381  	idCopy, err := id.Copy()
   382  	if err != nil {
   383  		return HookActionContinue, err
   384  	}
   385  	js, err := json.MarshalIndent(idCopy, "", "  ")
   386  	if err != nil {
   387  		return HookActionContinue, err
   388  	}
   389  	buf.Write(js)
   390  
   391  	dbug.WriteFile("hook-PostDiff", buf.Bytes())
   392  
   393  	return HookActionContinue, nil
   394  }
   395  
   396  func (*DebugHook) PreProvisionResource(ii *InstanceInfo, is *InstanceState) (HookAction, error) {
   397  	if dbug == nil {
   398  		return HookActionContinue, nil
   399  	}
   400  
   401  	var buf bytes.Buffer
   402  	if ii != nil {
   403  		buf.WriteString(ii.HumanId() + "\n")
   404  	}
   405  
   406  	if is != nil {
   407  		buf.WriteString(is.String())
   408  		buf.WriteString("\n")
   409  	}
   410  	dbug.WriteFile("hook-PreProvisionResource", buf.Bytes())
   411  
   412  	return HookActionContinue, nil
   413  }
   414  
   415  func (*DebugHook) PostProvisionResource(ii *InstanceInfo, is *InstanceState) (HookAction, error) {
   416  	if dbug == nil {
   417  		return HookActionContinue, nil
   418  	}
   419  
   420  	var buf bytes.Buffer
   421  	if ii != nil {
   422  		buf.WriteString(ii.HumanId())
   423  		buf.WriteString("\n")
   424  	}
   425  
   426  	if is != nil {
   427  		buf.WriteString(is.String())
   428  		buf.WriteString("\n")
   429  	}
   430  	dbug.WriteFile("hook-PostProvisionResource", buf.Bytes())
   431  	return HookActionContinue, nil
   432  }
   433  
   434  func (*DebugHook) PreProvision(ii *InstanceInfo, s string) (HookAction, error) {
   435  	if dbug == nil {
   436  		return HookActionContinue, nil
   437  	}
   438  
   439  	var buf bytes.Buffer
   440  	if ii != nil {
   441  		buf.WriteString(ii.HumanId())
   442  		buf.WriteString("\n")
   443  	}
   444  	buf.WriteString(s + "\n")
   445  
   446  	dbug.WriteFile("hook-PreProvision", buf.Bytes())
   447  	return HookActionContinue, nil
   448  }
   449  
   450  func (*DebugHook) PostProvision(ii *InstanceInfo, s string) (HookAction, error) {
   451  	if dbug == nil {
   452  		return HookActionContinue, nil
   453  	}
   454  
   455  	var buf bytes.Buffer
   456  	if ii != nil {
   457  		buf.WriteString(ii.HumanId() + "\n")
   458  	}
   459  	buf.WriteString(s + "\n")
   460  
   461  	dbug.WriteFile("hook-PostProvision", buf.Bytes())
   462  	return HookActionContinue, nil
   463  }
   464  
   465  func (*DebugHook) ProvisionOutput(ii *InstanceInfo, s1 string, s2 string) {
   466  	if dbug == nil {
   467  		return
   468  	}
   469  
   470  	var buf bytes.Buffer
   471  	if ii != nil {
   472  		buf.WriteString(ii.HumanId())
   473  		buf.WriteString("\n")
   474  	}
   475  	buf.WriteString(s1 + "\n")
   476  	buf.WriteString(s2 + "\n")
   477  
   478  	dbug.WriteFile("hook-ProvisionOutput", buf.Bytes())
   479  }
   480  
   481  func (*DebugHook) PreRefresh(ii *InstanceInfo, is *InstanceState) (HookAction, error) {
   482  	if dbug == nil {
   483  		return HookActionContinue, nil
   484  	}
   485  
   486  	var buf bytes.Buffer
   487  	if ii != nil {
   488  		buf.WriteString(ii.HumanId() + "\n")
   489  	}
   490  
   491  	if is != nil {
   492  		buf.WriteString(is.String())
   493  		buf.WriteString("\n")
   494  	}
   495  	dbug.WriteFile("hook-PreRefresh", buf.Bytes())
   496  	return HookActionContinue, nil
   497  }
   498  
   499  func (*DebugHook) PostRefresh(ii *InstanceInfo, is *InstanceState) (HookAction, error) {
   500  	if dbug == nil {
   501  		return HookActionContinue, nil
   502  	}
   503  
   504  	var buf bytes.Buffer
   505  	if ii != nil {
   506  		buf.WriteString(ii.HumanId())
   507  		buf.WriteString("\n")
   508  	}
   509  
   510  	if is != nil {
   511  		buf.WriteString(is.String())
   512  		buf.WriteString("\n")
   513  	}
   514  	dbug.WriteFile("hook-PostRefresh", buf.Bytes())
   515  	return HookActionContinue, nil
   516  }
   517  
   518  func (*DebugHook) PreImportState(ii *InstanceInfo, s string) (HookAction, error) {
   519  	if dbug == nil {
   520  		return HookActionContinue, nil
   521  	}
   522  
   523  	var buf bytes.Buffer
   524  	if ii != nil {
   525  		buf.WriteString(ii.HumanId())
   526  		buf.WriteString("\n")
   527  	}
   528  	buf.WriteString(s + "\n")
   529  
   530  	dbug.WriteFile("hook-PreImportState", buf.Bytes())
   531  	return HookActionContinue, nil
   532  }
   533  
   534  func (*DebugHook) PostImportState(ii *InstanceInfo, iss []*InstanceState) (HookAction, error) {
   535  	if dbug == nil {
   536  		return HookActionContinue, nil
   537  	}
   538  
   539  	var buf bytes.Buffer
   540  
   541  	if ii != nil {
   542  		buf.WriteString(ii.HumanId() + "\n")
   543  	}
   544  
   545  	for _, is := range iss {
   546  		if is != nil {
   547  			buf.WriteString(is.String() + "\n")
   548  		}
   549  	}
   550  	dbug.WriteFile("hook-PostImportState", buf.Bytes())
   551  	return HookActionContinue, nil
   552  }
   553  
   554  // skip logging this for now, since it could be huge
   555  func (*DebugHook) PostStateUpdate(*State) (HookAction, error) {
   556  	return HookActionContinue, nil
   557  }