]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blame - vendor/github.com/hashicorp/terraform/terraform/plan.go
vendor: github.com/hashicorp/terraform/...@v0.10.0
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / terraform / terraform / plan.go
CommitLineData
bae9f6d2
JC
1package terraform
2
3import (
4 "bytes"
5 "encoding/gob"
6 "errors"
7 "fmt"
8 "io"
c680a8e1 9 "log"
bae9f6d2
JC
10 "sync"
11
12 "github.com/hashicorp/terraform/config/module"
13)
14
15func 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.
28type Plan struct {
29 Diff *Diff
30 Module *module.Tree
31 State *State
32 Vars map[string]interface{}
33 Targets []string
34
c680a8e1
RS
35 TerraformVersion string
36 ProviderSHA256s map[string][]byte
37
bae9f6d2
JC
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,
c680a8e1
RS
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.
bae9f6d2 52func (p *Plan) Context(opts *ContextOpts) (*Context, error) {
c680a8e1
RS
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.
63func (p *Plan) contextOpts(base *ContextOpts) (*ContextOpts, error) {
64 opts := base
65
bae9f6d2
JC
66 opts.Diff = p.Diff
67 opts.Module = p.Module
bae9f6d2 68 opts.Targets = p.Targets
c680a8e1
RS
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 }
bae9f6d2
JC
92
93 opts.Variables = make(map[string]interface{})
94 for k, v := range p.Vars {
95 opts.Variables[k] = v
96 }
97
c680a8e1 98 return opts, nil
bae9f6d2
JC
99}
100
101func (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
110func (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.
131const planFormatMagic = "tfplan"
c680a8e1 132const planFormatVersion byte = 2
bae9f6d2
JC
133
134// ReadPlan reads a plan structure out of a reader in the format that
135// was written by WritePlan.
136func 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.
176func 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}