github.com/kcburge/terraform@v0.11.12-beta1/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  	"github.com/hashicorp/terraform/version"
    14  )
    15  
    16  func init() {
    17  	gob.Register(make([]interface{}, 0))
    18  	gob.Register(make([]map[string]interface{}, 0))
    19  	gob.Register(make(map[string]interface{}))
    20  	gob.Register(make(map[string]string))
    21  }
    22  
    23  // Plan represents a single Terraform execution plan, which contains
    24  // all the information necessary to make an infrastructure change.
    25  //
    26  // A plan has to contain basically the entire state of the world
    27  // necessary to make a change: the state, diff, config, backend config, etc.
    28  // This is so that it can run alone without any other data.
    29  type Plan struct {
    30  	// Diff describes the resource actions that must be taken when this
    31  	// plan is applied.
    32  	Diff *Diff
    33  
    34  	// Module represents the entire configuration that was present when this
    35  	// plan was created.
    36  	Module *module.Tree
    37  
    38  	// State is the Terraform state that was current when this plan was
    39  	// created.
    40  	//
    41  	// It is not allowed to apply a plan that has a stale state, since its
    42  	// diff could be outdated.
    43  	State *State
    44  
    45  	// Vars retains the variables that were set when creating the plan, so
    46  	// that the same variables can be applied during apply.
    47  	Vars map[string]interface{}
    48  
    49  	// Targets, if non-empty, contains a set of resource address strings that
    50  	// identify graph nodes that were selected as targets for plan.
    51  	//
    52  	// When targets are set, any graph node that is not directly targeted or
    53  	// indirectly targeted via dependencies is excluded from the graph.
    54  	Targets []string
    55  
    56  	// TerraformVersion is the version of Terraform that was used to create
    57  	// this plan.
    58  	//
    59  	// It is not allowed to apply a plan created with a different version of
    60  	// Terraform, since the other fields of this structure may be interpreted
    61  	// in different ways between versions.
    62  	TerraformVersion string
    63  
    64  	// ProviderSHA256s is a map giving the SHA256 hashes of the exact binaries
    65  	// used as plugins for each provider during plan.
    66  	//
    67  	// These must match between plan and apply to ensure that the diff is
    68  	// correctly interpreted, since different provider versions may have
    69  	// different attributes or attribute value constraints.
    70  	ProviderSHA256s map[string][]byte
    71  
    72  	// Backend is the backend that this plan should use and store data with.
    73  	Backend *BackendState
    74  
    75  	// Destroy indicates that this plan was created for a full destroy operation
    76  	Destroy bool
    77  
    78  	once sync.Once
    79  }
    80  
    81  // Context returns a Context with the data encapsulated in this plan.
    82  //
    83  // The following fields in opts are overridden by the plan: Config,
    84  // Diff, Variables.
    85  //
    86  // If State is not provided, it is set from the plan. If it _is_ provided,
    87  // it must be Equal to the state stored in plan, but may have a newer
    88  // serial.
    89  func (p *Plan) Context(opts *ContextOpts) (*Context, error) {
    90  	var err error
    91  	opts, err = p.contextOpts(opts)
    92  	if err != nil {
    93  		return nil, err
    94  	}
    95  	return NewContext(opts)
    96  }
    97  
    98  // contextOpts mutates the given base ContextOpts in place to use input
    99  // objects obtained from the receiving plan.
   100  func (p *Plan) contextOpts(base *ContextOpts) (*ContextOpts, error) {
   101  	opts := base
   102  
   103  	opts.Diff = p.Diff
   104  	opts.Module = p.Module
   105  	opts.Targets = p.Targets
   106  	opts.ProviderSHA256s = p.ProviderSHA256s
   107  	opts.Destroy = p.Destroy
   108  
   109  	if opts.State == nil {
   110  		opts.State = p.State
   111  	} else if !opts.State.Equal(p.State) {
   112  		// Even if we're overriding the state, it should be logically equal
   113  		// to what's in plan. The only valid change to have made by the time
   114  		// we get here is to have incremented the serial.
   115  		//
   116  		// Due to the fact that serialization may change the representation of
   117  		// the state, there is little chance that these aren't actually equal.
   118  		// Log the error condition for reference, but continue with the state
   119  		// we have.
   120  		log.Println("[WARN] Plan state and ContextOpts state are not equal")
   121  	}
   122  
   123  	thisVersion := version.String()
   124  	if p.TerraformVersion != "" && p.TerraformVersion != thisVersion {
   125  		return nil, fmt.Errorf(
   126  			"plan was created with a different version of Terraform (created with %s, but running %s)",
   127  			p.TerraformVersion, thisVersion,
   128  		)
   129  	}
   130  
   131  	opts.Variables = make(map[string]interface{})
   132  	for k, v := range p.Vars {
   133  		opts.Variables[k] = v
   134  	}
   135  
   136  	return opts, nil
   137  }
   138  
   139  func (p *Plan) String() string {
   140  	buf := new(bytes.Buffer)
   141  	buf.WriteString("DIFF:\n\n")
   142  	buf.WriteString(p.Diff.String())
   143  	buf.WriteString("\n\nSTATE:\n\n")
   144  	buf.WriteString(p.State.String())
   145  	return buf.String()
   146  }
   147  
   148  func (p *Plan) init() {
   149  	p.once.Do(func() {
   150  		if p.Diff == nil {
   151  			p.Diff = new(Diff)
   152  			p.Diff.init()
   153  		}
   154  
   155  		if p.State == nil {
   156  			p.State = new(State)
   157  			p.State.init()
   158  		}
   159  
   160  		if p.Vars == nil {
   161  			p.Vars = make(map[string]interface{})
   162  		}
   163  	})
   164  }
   165  
   166  // The format byte is prefixed into the plan file format so that we have
   167  // the ability in the future to change the file format if we want for any
   168  // reason.
   169  const planFormatMagic = "tfplan"
   170  const planFormatVersion byte = 2
   171  
   172  // ReadPlan reads a plan structure out of a reader in the format that
   173  // was written by WritePlan.
   174  func ReadPlan(src io.Reader) (*Plan, error) {
   175  	var result *Plan
   176  	var err error
   177  	n := 0
   178  
   179  	// Verify the magic bytes
   180  	magic := make([]byte, len(planFormatMagic))
   181  	for n < len(magic) {
   182  		n, err = src.Read(magic[n:])
   183  		if err != nil {
   184  			return nil, fmt.Errorf("error while reading magic bytes: %s", err)
   185  		}
   186  	}
   187  	if string(magic) != planFormatMagic {
   188  		return nil, fmt.Errorf("not a valid plan file")
   189  	}
   190  
   191  	// Verify the version is something we can read
   192  	var formatByte [1]byte
   193  	n, err = src.Read(formatByte[:])
   194  	if err != nil {
   195  		return nil, err
   196  	}
   197  	if n != len(formatByte) {
   198  		return nil, errors.New("failed to read plan version byte")
   199  	}
   200  
   201  	if formatByte[0] != planFormatVersion {
   202  		return nil, fmt.Errorf("unknown plan file version: %d", formatByte[0])
   203  	}
   204  
   205  	dec := gob.NewDecoder(src)
   206  	if err := dec.Decode(&result); err != nil {
   207  		return nil, err
   208  	}
   209  
   210  	return result, nil
   211  }
   212  
   213  // WritePlan writes a plan somewhere in a binary format.
   214  func WritePlan(d *Plan, dst io.Writer) error {
   215  	// Write the magic bytes so we can determine the file format later
   216  	n, err := dst.Write([]byte(planFormatMagic))
   217  	if err != nil {
   218  		return err
   219  	}
   220  	if n != len(planFormatMagic) {
   221  		return errors.New("failed to write plan format magic bytes")
   222  	}
   223  
   224  	// Write a version byte so we can iterate on version at some point
   225  	n, err = dst.Write([]byte{planFormatVersion})
   226  	if err != nil {
   227  		return err
   228  	}
   229  	if n != 1 {
   230  		return errors.New("failed to write plan version byte")
   231  	}
   232  
   233  	return gob.NewEncoder(dst).Encode(d)
   234  }