github.com/sixgill/terraform@v0.9.0-beta2.0.20170316214032-033f6226ae50/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  	// Backend is the backend that this plan should use and store data with.
    35  	Backend *BackendState
    36  
    37  	once sync.Once
    38  }
    39  
    40  // Context returns a Context with the data encapsulated in this plan.
    41  //
    42  // The following fields in opts are overridden by the plan: Config,
    43  // Diff, State, Variables.
    44  func (p *Plan) Context(opts *ContextOpts) (*Context, error) {
    45  	opts.Diff = p.Diff
    46  	opts.Module = p.Module
    47  	opts.State = p.State
    48  	opts.Targets = p.Targets
    49  
    50  	opts.Variables = make(map[string]interface{})
    51  	for k, v := range p.Vars {
    52  		opts.Variables[k] = v
    53  	}
    54  
    55  	return NewContext(opts)
    56  }
    57  
    58  func (p *Plan) String() string {
    59  	buf := new(bytes.Buffer)
    60  	buf.WriteString("DIFF:\n\n")
    61  	buf.WriteString(p.Diff.String())
    62  	buf.WriteString("\n\nSTATE:\n\n")
    63  	buf.WriteString(p.State.String())
    64  	return buf.String()
    65  }
    66  
    67  func (p *Plan) init() {
    68  	p.once.Do(func() {
    69  		if p.Diff == nil {
    70  			p.Diff = new(Diff)
    71  			p.Diff.init()
    72  		}
    73  
    74  		if p.State == nil {
    75  			p.State = new(State)
    76  			p.State.init()
    77  		}
    78  
    79  		if p.Vars == nil {
    80  			p.Vars = make(map[string]interface{})
    81  		}
    82  	})
    83  }
    84  
    85  // The format byte is prefixed into the plan file format so that we have
    86  // the ability in the future to change the file format if we want for any
    87  // reason.
    88  const planFormatMagic = "tfplan"
    89  const planFormatVersion byte = 1
    90  
    91  // ReadPlan reads a plan structure out of a reader in the format that
    92  // was written by WritePlan.
    93  func ReadPlan(src io.Reader) (*Plan, error) {
    94  	var result *Plan
    95  	var err error
    96  	n := 0
    97  
    98  	// Verify the magic bytes
    99  	magic := make([]byte, len(planFormatMagic))
   100  	for n < len(magic) {
   101  		n, err = src.Read(magic[n:])
   102  		if err != nil {
   103  			return nil, fmt.Errorf("error while reading magic bytes: %s", err)
   104  		}
   105  	}
   106  	if string(magic) != planFormatMagic {
   107  		return nil, fmt.Errorf("not a valid plan file")
   108  	}
   109  
   110  	// Verify the version is something we can read
   111  	var formatByte [1]byte
   112  	n, err = src.Read(formatByte[:])
   113  	if err != nil {
   114  		return nil, err
   115  	}
   116  	if n != len(formatByte) {
   117  		return nil, errors.New("failed to read plan version byte")
   118  	}
   119  
   120  	if formatByte[0] != planFormatVersion {
   121  		return nil, fmt.Errorf("unknown plan file version: %d", formatByte[0])
   122  	}
   123  
   124  	dec := gob.NewDecoder(src)
   125  	if err := dec.Decode(&result); err != nil {
   126  		return nil, err
   127  	}
   128  
   129  	return result, nil
   130  }
   131  
   132  // WritePlan writes a plan somewhere in a binary format.
   133  func WritePlan(d *Plan, dst io.Writer) error {
   134  	// Write the magic bytes so we can determine the file format later
   135  	n, err := dst.Write([]byte(planFormatMagic))
   136  	if err != nil {
   137  		return err
   138  	}
   139  	if n != len(planFormatMagic) {
   140  		return errors.New("failed to write plan format magic bytes")
   141  	}
   142  
   143  	// Write a version byte so we can iterate on version at some point
   144  	n, err = dst.Write([]byte{planFormatVersion})
   145  	if err != nil {
   146  		return err
   147  	}
   148  	if n != 1 {
   149  		return errors.New("failed to write plan version byte")
   150  	}
   151  
   152  	return gob.NewEncoder(dst).Encode(d)
   153  }