]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - vendor/github.com/hashicorp/terraform/terraform/plan.go
deps: github.com/hashicorp/terraform@sdk-v0.11-with-go-modules
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / terraform / terraform / plan.go
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 }