]>
Commit | Line | Data |
---|---|---|
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 | ) | |
14 | ||
15 | func 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. | |
28 | type Plan struct { | |
29 | Diff *Diff | |
30 | Module *module.Tree | |
31 | State *State | |
32 | Vars map[string]interface{} | |
33 | Targets []string | |
34 | ||
35 | TerraformVersion string | |
36 | ProviderSHA256s map[string][]byte | |
37 | ||
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, | |
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. | |
52 | func (p *Plan) Context(opts *ContextOpts) (*Context, error) { | |
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. | |
63 | func (p *Plan) contextOpts(base *ContextOpts) (*ContextOpts, error) { | |
64 | opts := base | |
65 | ||
66 | opts.Diff = p.Diff | |
67 | opts.Module = p.Module | |
68 | opts.Targets = p.Targets | |
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 | } | |
92 | ||
93 | opts.Variables = make(map[string]interface{}) | |
94 | for k, v := range p.Vars { | |
95 | opts.Variables[k] = v | |
96 | } | |
97 | ||
98 | return opts, nil | |
99 | } | |
100 | ||
101 | func (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 | ||
110 | func (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. | |
131 | const planFormatMagic = "tfplan" | |
132 | const planFormatVersion byte = 2 | |
133 | ||
134 | // ReadPlan reads a plan structure out of a reader in the format that | |
135 | // was written by WritePlan. | |
136 | func 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. | |
176 | func 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 | } |