github.com/sl1pm4t/terraform@v0.6.4-0.20170725213156-870617d22df3/terraform/plan.go (about)

     1  package terraform
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/gob"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  	"log"
    10  	"sync"
    11  
    12  	"github.com/hashicorp/terraform/config/module"
    13  )
    14  
    15  func init() {
    16  	gob.Register(make([]interface{}, 0))
    17  	gob.Register(make([]map[string]interface{}, 0))
    18  	gob.Register(make(map[string]interface{}))
    19  	gob.Register(make(map[string]string))
    20  }
    21  
    22  // Plan represents a single Terraform execution plan, which contains
    23  // all the information necessary to make an infrastructure change.
    24  //
    25  // A plan has to contain basically the entire state of the world
    26  // necessary to make a change: the state, diff, config, backend config, etc.
    27  // This is so that it can run alone without any other data.
    28  type Plan struct {
    29  	Diff    *Diff
    30  	Module  *module.Tree
    31  	State   *State
    32  	Vars    map[string]interface{}
    33  	Targets []string
    34  
    35  	TerraformVersion string
    36  	ProviderSHA256s  map[string][]byte
    37  
    38  	// Backend is the backend that this plan should use and store data with.
    39  	Backend *BackendState
    40  
    41  	once sync.Once
    42  }
    43  
    44  // Context returns a Context with the data encapsulated in this plan.
    45  //
    46  // The following fields in opts are overridden by the plan: Config,
    47  // Diff, Variables.
    48  //
    49  // If State is not provided, it is set from the plan. If it _is_ provided,
    50  // it must be Equal to the state stored in plan, but may have a newer
    51  // serial.
    52  func (p *Plan) Context(opts *ContextOpts) (*Context, error) {
    53  	var err error
    54  	opts, err = p.contextOpts(opts)
    55  	if err != nil {
    56  		return nil, err
    57  	}
    58  	return NewContext(opts)
    59  }
    60  
    61  // contextOpts mutates the given base ContextOpts in place to use input
    62  // objects obtained from the receiving plan.
    63  func (p *Plan) contextOpts(base *ContextOpts) (*ContextOpts, error) {
    64  	opts := base
    65  
    66  	opts.Diff = p.Diff
    67  	opts.Module = p.Module
    68  	opts.Targets = p.Targets
    69  	opts.ProviderSHA256s = p.ProviderSHA256s
    70  
    71  	if opts.State == nil {
    72  		opts.State = p.State
    73  	} else if !opts.State.Equal(p.State) {
    74  		// Even if we're overriding the state, it should be logically equal
    75  		// to what's in plan. The only valid change to have made by the time
    76  		// we get here is to have incremented the serial.
    77  		//
    78  		// Due to the fact that serialization may change the representation of
    79  		// the state, there is little chance that these aren't actually equal.
    80  		// Log the error condition for reference, but continue with the state
    81  		// we have.
    82  		log.Println("[WARNING] Plan state and ContextOpts state are not equal")
    83  	}
    84  
    85  	thisVersion := VersionString()
    86  	if p.TerraformVersion != "" && p.TerraformVersion != thisVersion {
    87  		return nil, fmt.Errorf(
    88  			"plan was created with a different version of Terraform (created with %s, but running %s)",
    89  			p.TerraformVersion, thisVersion,
    90  		)
    91  	}
    92  
    93  	opts.Variables = make(map[string]interface{})
    94  	for k, v := range p.Vars {
    95  		opts.Variables[k] = v
    96  	}
    97  
    98  	return opts, nil
    99  }
   100  
   101  func (p *Plan) String() string {
   102  	buf := new(bytes.Buffer)
   103  	buf.WriteString("DIFF:\n\n")
   104  	buf.WriteString(p.Diff.String())
   105  	buf.WriteString("\n\nSTATE:\n\n")
   106  	buf.WriteString(p.State.String())
   107  	return buf.String()
   108  }
   109  
   110  func (p *Plan) init() {
   111  	p.once.Do(func() {
   112  		if p.Diff == nil {
   113  			p.Diff = new(Diff)
   114  			p.Diff.init()
   115  		}
   116  
   117  		if p.State == nil {
   118  			p.State = new(State)
   119  			p.State.init()
   120  		}
   121  
   122  		if p.Vars == nil {
   123  			p.Vars = make(map[string]interface{})
   124  		}
   125  	})
   126  }
   127  
   128  // The format byte is prefixed into the plan file format so that we have
   129  // the ability in the future to change the file format if we want for any
   130  // reason.
   131  const planFormatMagic = "tfplan"
   132  const planFormatVersion byte = 2
   133  
   134  // ReadPlan reads a plan structure out of a reader in the format that
   135  // was written by WritePlan.
   136  func ReadPlan(src io.Reader) (*Plan, error) {
   137  	var result *Plan
   138  	var err error
   139  	n := 0
   140  
   141  	// Verify the magic bytes
   142  	magic := make([]byte, len(planFormatMagic))
   143  	for n < len(magic) {
   144  		n, err = src.Read(magic[n:])
   145  		if err != nil {
   146  			return nil, fmt.Errorf("error while reading magic bytes: %s", err)
   147  		}
   148  	}
   149  	if string(magic) != planFormatMagic {
   150  		return nil, fmt.Errorf("not a valid plan file")
   151  	}
   152  
   153  	// Verify the version is something we can read
   154  	var formatByte [1]byte
   155  	n, err = src.Read(formatByte[:])
   156  	if err != nil {
   157  		return nil, err
   158  	}
   159  	if n != len(formatByte) {
   160  		return nil, errors.New("failed to read plan version byte")
   161  	}
   162  
   163  	if formatByte[0] != planFormatVersion {
   164  		return nil, fmt.Errorf("unknown plan file version: %d", formatByte[0])
   165  	}
   166  
   167  	dec := gob.NewDecoder(src)
   168  	if err := dec.Decode(&result); err != nil {
   169  		return nil, err
   170  	}
   171  
   172  	return result, nil
   173  }
   174  
   175  // WritePlan writes a plan somewhere in a binary format.
   176  func WritePlan(d *Plan, dst io.Writer) error {
   177  	// Write the magic bytes so we can determine the file format later
   178  	n, err := dst.Write([]byte(planFormatMagic))
   179  	if err != nil {
   180  		return err
   181  	}
   182  	if n != len(planFormatMagic) {
   183  		return errors.New("failed to write plan format magic bytes")
   184  	}
   185  
   186  	// Write a version byte so we can iterate on version at some point
   187  	n, err = dst.Write([]byte{planFormatVersion})
   188  	if err != nil {
   189  		return err
   190  	}
   191  	if n != 1 {
   192  		return errors.New("failed to write plan version byte")
   193  	}
   194  
   195  	return gob.NewEncoder(dst).Encode(d)
   196  }