github.com/adrian-bl/terraform@v0.7.0-rc2.0.20160705220747-de0a34fc3517/terraform/plan.go (about)

     1  package terraform
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/gob"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  	"sync"
    10  
    11  	"github.com/hashicorp/terraform/config/module"
    12  )
    13  
    14  func init() {
    15  	gob.Register(make([]interface{}, 0))
    16  	gob.Register(make([]map[string]interface{}, 0))
    17  	gob.Register(make(map[string]interface{}))
    18  	gob.Register(make(map[string]string))
    19  }
    20  
    21  // Plan represents a single Terraform execution plan, which contains
    22  // all the information necessary to make an infrastructure change.
    23  type Plan struct {
    24  	Diff    *Diff
    25  	Module  *module.Tree
    26  	State   *State
    27  	Vars    map[string]string
    28  	Targets []string
    29  
    30  	once sync.Once
    31  }
    32  
    33  // Context returns a Context with the data encapsulated in this plan.
    34  //
    35  // The following fields in opts are overridden by the plan: Config,
    36  // Diff, State, Variables.
    37  func (p *Plan) Context(opts *ContextOpts) (*Context, error) {
    38  	opts.Diff = p.Diff
    39  	opts.Module = p.Module
    40  	opts.State = p.State
    41  	opts.Variables = p.Vars
    42  	opts.Targets = p.Targets
    43  	return NewContext(opts)
    44  }
    45  
    46  func (p *Plan) String() string {
    47  	buf := new(bytes.Buffer)
    48  	buf.WriteString("DIFF:\n\n")
    49  	buf.WriteString(p.Diff.String())
    50  	buf.WriteString("\n\nSTATE:\n\n")
    51  	buf.WriteString(p.State.String())
    52  	return buf.String()
    53  }
    54  
    55  func (p *Plan) init() {
    56  	p.once.Do(func() {
    57  		if p.Diff == nil {
    58  			p.Diff = new(Diff)
    59  			p.Diff.init()
    60  		}
    61  
    62  		if p.State == nil {
    63  			p.State = new(State)
    64  			p.State.init()
    65  		}
    66  
    67  		if p.Vars == nil {
    68  			p.Vars = make(map[string]string)
    69  		}
    70  	})
    71  }
    72  
    73  // The format byte is prefixed into the plan file format so that we have
    74  // the ability in the future to change the file format if we want for any
    75  // reason.
    76  const planFormatMagic = "tfplan"
    77  const planFormatVersion byte = 1
    78  
    79  // ReadPlan reads a plan structure out of a reader in the format that
    80  // was written by WritePlan.
    81  func ReadPlan(src io.Reader) (*Plan, error) {
    82  	var result *Plan
    83  	var err error
    84  	n := 0
    85  
    86  	// Verify the magic bytes
    87  	magic := make([]byte, len(planFormatMagic))
    88  	for n < len(magic) {
    89  		n, err = src.Read(magic[n:])
    90  		if err != nil {
    91  			return nil, fmt.Errorf("error while reading magic bytes: %s", err)
    92  		}
    93  	}
    94  	if string(magic) != planFormatMagic {
    95  		return nil, fmt.Errorf("not a valid plan file")
    96  	}
    97  
    98  	// Verify the version is something we can read
    99  	var formatByte [1]byte
   100  	n, err = src.Read(formatByte[:])
   101  	if err != nil {
   102  		return nil, err
   103  	}
   104  	if n != len(formatByte) {
   105  		return nil, errors.New("failed to read plan version byte")
   106  	}
   107  
   108  	if formatByte[0] != planFormatVersion {
   109  		return nil, fmt.Errorf("unknown plan file version: %d", formatByte[0])
   110  	}
   111  
   112  	dec := gob.NewDecoder(src)
   113  	if err := dec.Decode(&result); err != nil {
   114  		return nil, err
   115  	}
   116  
   117  	return result, nil
   118  }
   119  
   120  // WritePlan writes a plan somewhere in a binary format.
   121  func WritePlan(d *Plan, dst io.Writer) error {
   122  	// Write the magic bytes so we can determine the file format later
   123  	n, err := dst.Write([]byte(planFormatMagic))
   124  	if err != nil {
   125  		return err
   126  	}
   127  	if n != len(planFormatMagic) {
   128  		return errors.New("failed to write plan format magic bytes")
   129  	}
   130  
   131  	// Write a version byte so we can iterate on version at some point
   132  	n, err = dst.Write([]byte{planFormatVersion})
   133  	if err != nil {
   134  		return err
   135  	}
   136  	if n != 1 {
   137  		return errors.New("failed to write plan version byte")
   138  	}
   139  
   140  	return gob.NewEncoder(dst).Encode(d)
   141  }