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