2014-06-20 12:33:26 -05:00
|
|
|
package terraform
|
|
|
|
|
|
|
|
import (
|
2014-06-25 20:22:42 -05:00
|
|
|
"bytes"
|
2014-06-20 12:33:26 -05:00
|
|
|
"encoding/gob"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
2017-07-17 09:35:48 -05:00
|
|
|
"log"
|
2014-06-20 12:44:49 -05:00
|
|
|
"sync"
|
2014-06-20 12:33:26 -05:00
|
|
|
|
2014-09-22 17:37:29 -05:00
|
|
|
"github.com/hashicorp/terraform/config/module"
|
2014-06-20 12:33:26 -05:00
|
|
|
)
|
|
|
|
|
2014-07-02 13:28:23 -05:00
|
|
|
func init() {
|
2014-07-02 18:17:26 -05:00
|
|
|
gob.Register(make([]interface{}, 0))
|
2014-07-02 19:04:48 -05:00
|
|
|
gob.Register(make([]map[string]interface{}, 0))
|
2014-08-05 00:08:55 -05:00
|
|
|
gob.Register(make(map[string]interface{}))
|
2014-07-24 09:17:30 -05:00
|
|
|
gob.Register(make(map[string]string))
|
2014-07-02 13:28:23 -05:00
|
|
|
}
|
|
|
|
|
2014-06-20 12:33:26 -05:00
|
|
|
// Plan represents a single Terraform execution plan, which contains
|
|
|
|
// all the information necessary to make an infrastructure change.
|
2017-01-18 22:50:57 -06:00
|
|
|
//
|
|
|
|
// A plan has to contain basically the entire state of the world
|
|
|
|
// necessary to make a change: the state, diff, config, backend config, etc.
|
|
|
|
// This is so that it can run alone without any other data.
|
2014-06-20 12:33:26 -05:00
|
|
|
type Plan struct {
|
2016-02-23 10:24:53 -06:00
|
|
|
Diff *Diff
|
|
|
|
Module *module.Tree
|
|
|
|
State *State
|
2016-07-18 12:52:10 -05:00
|
|
|
Vars map[string]interface{}
|
2016-02-23 10:24:53 -06:00
|
|
|
Targets []string
|
2014-06-20 12:44:49 -05:00
|
|
|
|
2017-06-05 19:08:02 -05:00
|
|
|
TerraformVersion string
|
|
|
|
ProviderSHA256s map[string][]byte
|
|
|
|
|
2017-01-18 22:50:57 -06:00
|
|
|
// Backend is the backend that this plan should use and store data with.
|
|
|
|
Backend *BackendState
|
|
|
|
|
2014-06-20 12:44:49 -05:00
|
|
|
once sync.Once
|
|
|
|
}
|
|
|
|
|
2014-07-03 13:32:44 -05:00
|
|
|
// Context returns a Context with the data encapsulated in this plan.
|
|
|
|
//
|
|
|
|
// The following fields in opts are overridden by the plan: Config,
|
2017-07-17 09:35:48 -05:00
|
|
|
// Diff, Variables.
|
|
|
|
//
|
|
|
|
// If State is not provided, it is set from the plan. If it _is_ provided,
|
|
|
|
// it must be Equal to the state stored in plan, but may have a newer
|
|
|
|
// serial.
|
2016-03-11 13:07:54 -06:00
|
|
|
func (p *Plan) Context(opts *ContextOpts) (*Context, error) {
|
2017-06-05 19:08:02 -05:00
|
|
|
var err error
|
|
|
|
opts, err = p.contextOpts(opts)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return NewContext(opts)
|
|
|
|
}
|
|
|
|
|
|
|
|
// contextOpts mutates the given base ContextOpts in place to use input
|
|
|
|
// objects obtained from the receiving plan.
|
|
|
|
func (p *Plan) contextOpts(base *ContextOpts) (*ContextOpts, error) {
|
|
|
|
opts := base
|
|
|
|
|
2014-07-03 13:32:44 -05:00
|
|
|
opts.Diff = p.Diff
|
2014-09-24 16:56:48 -05:00
|
|
|
opts.Module = p.Module
|
2016-02-23 10:24:53 -06:00
|
|
|
opts.Targets = p.Targets
|
2017-06-05 19:08:02 -05:00
|
|
|
opts.ProviderSHA256s = p.ProviderSHA256s
|
|
|
|
|
2017-07-17 09:35:48 -05:00
|
|
|
if opts.State == nil {
|
|
|
|
opts.State = p.State
|
|
|
|
} else if !opts.State.Equal(p.State) {
|
|
|
|
// Even if we're overriding the state, it should be logically equal
|
|
|
|
// to what's in plan. The only valid change to have made by the time
|
|
|
|
// we get here is to have incremented the serial.
|
|
|
|
//
|
|
|
|
// Due to the fact that serialization may change the representation of
|
|
|
|
// the state, there is little chance that these aren't actually equal.
|
|
|
|
// Log the error condition for reference, but continue with the state
|
|
|
|
// we have.
|
|
|
|
log.Println("[WARNING] Plan state and ContextOpts state are not equal")
|
|
|
|
}
|
|
|
|
|
2017-06-05 19:08:02 -05:00
|
|
|
thisVersion := VersionString()
|
|
|
|
if p.TerraformVersion != "" && p.TerraformVersion != thisVersion {
|
|
|
|
return nil, fmt.Errorf(
|
|
|
|
"plan was created with a different version of Terraform (created with %s, but running %s)",
|
|
|
|
p.TerraformVersion, thisVersion,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2016-07-18 12:52:10 -05:00
|
|
|
opts.Variables = make(map[string]interface{})
|
|
|
|
for k, v := range p.Vars {
|
|
|
|
opts.Variables[k] = v
|
|
|
|
}
|
|
|
|
|
2017-06-05 19:08:02 -05:00
|
|
|
return opts, nil
|
2014-07-03 13:32:44 -05:00
|
|
|
}
|
|
|
|
|
2014-06-20 12:44:49 -05:00
|
|
|
func (p *Plan) String() string {
|
2014-06-25 20:22:42 -05:00
|
|
|
buf := new(bytes.Buffer)
|
|
|
|
buf.WriteString("DIFF:\n\n")
|
|
|
|
buf.WriteString(p.Diff.String())
|
2014-09-23 13:43:21 -05:00
|
|
|
buf.WriteString("\n\nSTATE:\n\n")
|
2014-06-25 20:22:42 -05:00
|
|
|
buf.WriteString(p.State.String())
|
|
|
|
return buf.String()
|
2014-06-20 12:44:49 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
func (p *Plan) init() {
|
|
|
|
p.once.Do(func() {
|
|
|
|
if p.Diff == nil {
|
|
|
|
p.Diff = new(Diff)
|
|
|
|
p.Diff.init()
|
|
|
|
}
|
|
|
|
|
|
|
|
if p.State == nil {
|
|
|
|
p.State = new(State)
|
|
|
|
p.State.init()
|
|
|
|
}
|
|
|
|
|
|
|
|
if p.Vars == nil {
|
2016-07-18 12:52:10 -05:00
|
|
|
p.Vars = make(map[string]interface{})
|
2014-06-20 12:44:49 -05:00
|
|
|
}
|
|
|
|
})
|
2014-06-20 12:33:26 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// The format byte is prefixed into the plan file format so that we have
|
|
|
|
// the ability in the future to change the file format if we want for any
|
|
|
|
// reason.
|
2014-07-11 23:43:24 -05:00
|
|
|
const planFormatMagic = "tfplan"
|
2017-06-05 19:08:02 -05:00
|
|
|
const planFormatVersion byte = 2
|
2014-06-20 12:33:26 -05:00
|
|
|
|
|
|
|
// ReadPlan reads a plan structure out of a reader in the format that
|
|
|
|
// was written by WritePlan.
|
|
|
|
func ReadPlan(src io.Reader) (*Plan, error) {
|
|
|
|
var result *Plan
|
2014-07-11 23:43:24 -05:00
|
|
|
var err error
|
|
|
|
n := 0
|
|
|
|
|
|
|
|
// Verify the magic bytes
|
|
|
|
magic := make([]byte, len(planFormatMagic))
|
|
|
|
for n < len(magic) {
|
|
|
|
n, err = src.Read(magic[n:])
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("error while reading magic bytes: %s", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if string(magic) != planFormatMagic {
|
|
|
|
return nil, fmt.Errorf("not a valid plan file")
|
|
|
|
}
|
2014-06-20 12:33:26 -05:00
|
|
|
|
2014-07-11 23:43:24 -05:00
|
|
|
// Verify the version is something we can read
|
2014-06-20 12:33:26 -05:00
|
|
|
var formatByte [1]byte
|
2014-07-11 23:43:24 -05:00
|
|
|
n, err = src.Read(formatByte[:])
|
2014-06-20 12:33:26 -05:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if n != len(formatByte) {
|
|
|
|
return nil, errors.New("failed to read plan version byte")
|
|
|
|
}
|
|
|
|
|
2014-07-11 23:43:24 -05:00
|
|
|
if formatByte[0] != planFormatVersion {
|
2014-06-20 12:33:26 -05:00
|
|
|
return nil, fmt.Errorf("unknown plan file version: %d", formatByte[0])
|
|
|
|
}
|
|
|
|
|
|
|
|
dec := gob.NewDecoder(src)
|
|
|
|
if err := dec.Decode(&result); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// WritePlan writes a plan somewhere in a binary format.
|
|
|
|
func WritePlan(d *Plan, dst io.Writer) error {
|
2014-07-11 23:43:24 -05:00
|
|
|
// Write the magic bytes so we can determine the file format later
|
|
|
|
n, err := dst.Write([]byte(planFormatMagic))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if n != len(planFormatMagic) {
|
|
|
|
return errors.New("failed to write plan format magic bytes")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Write a version byte so we can iterate on version at some point
|
|
|
|
n, err = dst.Write([]byte{planFormatVersion})
|
2014-06-20 12:33:26 -05:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if n != 1 {
|
|
|
|
return errors.New("failed to write plan version byte")
|
|
|
|
}
|
|
|
|
|
|
|
|
return gob.NewEncoder(dst).Encode(d)
|
|
|
|
}
|