github.com/spirius/terraform@v0.10.0-beta2.0.20170714185654-87b2c0cf8fea/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  //
    24  // A plan has to contain basically the entire state of the world
    25  // necessary to make a change: the state, diff, config, backend config, etc.
    26  // This is so that it can run alone without any other data.
    27  type Plan struct {
    28  	Diff    *Diff
    29  	Module  *module.Tree
    30  	State   *State
    31  	Vars    map[string]interface{}
    32  	Targets []string
    33  
    34  	TerraformVersion string
    35  	ProviderSHA256s  map[string][]byte
    36  
    37  	// Backend is the backend that this plan should use and store data with.
    38  	Backend *BackendState
    39  
    40  	once sync.Once
    41  }
    42  
    43  // Context returns a Context with the data encapsulated in this plan.
    44  //
    45  // The following fields in opts are overridden by the plan: Config,
    46  // Diff, State, Variables.
    47  func (p *Plan) Context(opts *ContextOpts) (*Context, error) {
    48  	var err error
    49  	opts, err = p.contextOpts(opts)
    50  	if err != nil {
    51  		return nil, err
    52  	}
    53  	return NewContext(opts)
    54  }
    55  
    56  // contextOpts mutates the given base ContextOpts in place to use input
    57  // objects obtained from the receiving plan.
    58  func (p *Plan) contextOpts(base *ContextOpts) (*ContextOpts, error) {
    59  	opts := base
    60  
    61  	opts.Diff = p.Diff
    62  	opts.Module = p.Module
    63  	opts.State = p.State
    64  	opts.Targets = p.Targets
    65  
    66  	opts.ProviderSHA256s = p.ProviderSHA256s
    67  
    68  	thisVersion := VersionString()
    69  	if p.TerraformVersion != "" && p.TerraformVersion != thisVersion {
    70  		return nil, fmt.Errorf(
    71  			"plan was created with a different version of Terraform (created with %s, but running %s)",
    72  			p.TerraformVersion, thisVersion,
    73  		)
    74  	}
    75  
    76  	opts.Variables = make(map[string]interface{})
    77  	for k, v := range p.Vars {
    78  		opts.Variables[k] = v
    79  	}
    80  
    81  	return opts, nil
    82  }
    83  
    84  func (p *Plan) String() string {
    85  	buf := new(bytes.Buffer)
    86  	buf.WriteString("DIFF:\n\n")
    87  	buf.WriteString(p.Diff.String())
    88  	buf.WriteString("\n\nSTATE:\n\n")
    89  	buf.WriteString(p.State.String())
    90  	return buf.String()
    91  }
    92  
    93  func (p *Plan) init() {
    94  	p.once.Do(func() {
    95  		if p.Diff == nil {
    96  			p.Diff = new(Diff)
    97  			p.Diff.init()
    98  		}
    99  
   100  		if p.State == nil {
   101  			p.State = new(State)
   102  			p.State.init()
   103  		}
   104  
   105  		if p.Vars == nil {
   106  			p.Vars = make(map[string]interface{})
   107  		}
   108  	})
   109  }
   110  
   111  // The format byte is prefixed into the plan file format so that we have
   112  // the ability in the future to change the file format if we want for any
   113  // reason.
   114  const planFormatMagic = "tfplan"
   115  const planFormatVersion byte = 2
   116  
   117  // ReadPlan reads a plan structure out of a reader in the format that
   118  // was written by WritePlan.
   119  func ReadPlan(src io.Reader) (*Plan, error) {
   120  	var result *Plan
   121  	var err error
   122  	n := 0
   123  
   124  	// Verify the magic bytes
   125  	magic := make([]byte, len(planFormatMagic))
   126  	for n < len(magic) {
   127  		n, err = src.Read(magic[n:])
   128  		if err != nil {
   129  			return nil, fmt.Errorf("error while reading magic bytes: %s", err)
   130  		}
   131  	}
   132  	if string(magic) != planFormatMagic {
   133  		return nil, fmt.Errorf("not a valid plan file")
   134  	}
   135  
   136  	// Verify the version is something we can read
   137  	var formatByte [1]byte
   138  	n, err = src.Read(formatByte[:])
   139  	if err != nil {
   140  		return nil, err
   141  	}
   142  	if n != len(formatByte) {
   143  		return nil, errors.New("failed to read plan version byte")
   144  	}
   145  
   146  	if formatByte[0] != planFormatVersion {
   147  		return nil, fmt.Errorf("unknown plan file version: %d", formatByte[0])
   148  	}
   149  
   150  	dec := gob.NewDecoder(src)
   151  	if err := dec.Decode(&result); err != nil {
   152  		return nil, err
   153  	}
   154  
   155  	return result, nil
   156  }
   157  
   158  // WritePlan writes a plan somewhere in a binary format.
   159  func WritePlan(d *Plan, dst io.Writer) error {
   160  	// Write the magic bytes so we can determine the file format later
   161  	n, err := dst.Write([]byte(planFormatMagic))
   162  	if err != nil {
   163  		return err
   164  	}
   165  	if n != len(planFormatMagic) {
   166  		return errors.New("failed to write plan format magic bytes")
   167  	}
   168  
   169  	// Write a version byte so we can iterate on version at some point
   170  	n, err = dst.Write([]byte{planFormatVersion})
   171  	if err != nil {
   172  		return err
   173  	}
   174  	if n != 1 {
   175  		return errors.New("failed to write plan version byte")
   176  	}
   177  
   178  	return gob.NewEncoder(dst).Encode(d)
   179  }