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