]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
1 | package terraform |
2 | ||
3 | import ( | |
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" | |
15c0b25d | 13 | "github.com/hashicorp/terraform/version" |
bae9f6d2 JC |
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 { | |
15c0b25d AP |
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. | |
bae9f6d2 JC |
54 | Targets []string |
55 | ||
15c0b25d AP |
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. | |
c680a8e1 | 62 | TerraformVersion string |
15c0b25d AP |
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 | |
c680a8e1 | 71 | |
bae9f6d2 JC |
72 | // Backend is the backend that this plan should use and store data with. |
73 | Backend *BackendState | |
74 | ||
15c0b25d AP |
75 | // Destroy indicates that this plan was created for a full destroy operation |
76 | Destroy bool | |
77 | ||
bae9f6d2 JC |
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, | |
c680a8e1 RS |
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. | |
bae9f6d2 | 89 | func (p *Plan) Context(opts *ContextOpts) (*Context, error) { |
c680a8e1 RS |
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 | ||
bae9f6d2 JC |
103 | opts.Diff = p.Diff |
104 | opts.Module = p.Module | |
bae9f6d2 | 105 | opts.Targets = p.Targets |
c680a8e1 | 106 | opts.ProviderSHA256s = p.ProviderSHA256s |
15c0b25d | 107 | opts.Destroy = p.Destroy |
c680a8e1 RS |
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. | |
15c0b25d | 120 | log.Println("[WARN] Plan state and ContextOpts state are not equal") |
c680a8e1 RS |
121 | } |
122 | ||
15c0b25d | 123 | thisVersion := version.String() |
c680a8e1 RS |
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 | } | |
bae9f6d2 JC |
130 | |
131 | opts.Variables = make(map[string]interface{}) | |
132 | for k, v := range p.Vars { | |
133 | opts.Variables[k] = v | |
134 | } | |
135 | ||
c680a8e1 | 136 | return opts, nil |
bae9f6d2 JC |
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" | |
c680a8e1 | 170 | const planFormatVersion byte = 2 |
bae9f6d2 JC |
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 | } |