]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
1 | package terraform |
2 | ||
3 | import ( | |
4 | "archive/tar" | |
5 | "bytes" | |
6 | "compress/gzip" | |
7 | "encoding/json" | |
8 | "fmt" | |
9 | "io" | |
10 | "os" | |
11 | "path/filepath" | |
12 | "sync" | |
13 | "time" | |
14 | ) | |
15 | ||
16 | // DebugInfo is the global handler for writing the debug archive. All methods | |
17 | // are safe to call concurrently. Setting DebugInfo to nil will disable writing | |
18 | // the debug archive. All methods are safe to call on the nil value. | |
19 | var dbug *debugInfo | |
20 | ||
21 | // SetDebugInfo initializes the debug handler with a backing file in the | |
22 | // provided directory. This must be called before any other terraform package | |
23 | // operations or not at all. Once his is called, CloseDebugInfo should be | |
24 | // called before program exit. | |
25 | func SetDebugInfo(path string) error { | |
26 | if os.Getenv("TF_DEBUG") == "" { | |
27 | return nil | |
28 | } | |
29 | ||
30 | di, err := newDebugInfoFile(path) | |
31 | if err != nil { | |
32 | return err | |
33 | } | |
34 | ||
35 | dbug = di | |
36 | return nil | |
37 | } | |
38 | ||
39 | // CloseDebugInfo is the exported interface to Close the debug info handler. | |
40 | // The debug handler needs to be closed before program exit, so we export this | |
41 | // function to be deferred in the appropriate entrypoint for our executable. | |
42 | func CloseDebugInfo() error { | |
43 | return dbug.Close() | |
44 | } | |
45 | ||
46 | // newDebugInfoFile initializes the global debug handler with a backing file in | |
47 | // the provided directory. | |
48 | func newDebugInfoFile(dir string) (*debugInfo, error) { | |
49 | err := os.MkdirAll(dir, 0755) | |
50 | if err != nil { | |
51 | return nil, err | |
52 | } | |
53 | ||
54 | // FIXME: not guaranteed unique, but good enough for now | |
55 | name := fmt.Sprintf("debug-%s", time.Now().Format("2006-01-02-15-04-05.999999999")) | |
56 | archivePath := filepath.Join(dir, name+".tar.gz") | |
57 | ||
58 | f, err := os.OpenFile(archivePath, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666) | |
59 | if err != nil { | |
60 | return nil, err | |
61 | } | |
62 | return newDebugInfo(name, f) | |
63 | } | |
64 | ||
65 | // newDebugInfo initializes the global debug handler. | |
66 | func newDebugInfo(name string, w io.Writer) (*debugInfo, error) { | |
67 | gz := gzip.NewWriter(w) | |
68 | ||
69 | d := &debugInfo{ | |
70 | name: name, | |
71 | w: w, | |
72 | gz: gz, | |
73 | tar: tar.NewWriter(gz), | |
74 | } | |
75 | ||
76 | // create the subdirs we need | |
77 | topHdr := &tar.Header{ | |
78 | Name: name, | |
79 | Typeflag: tar.TypeDir, | |
80 | Mode: 0755, | |
81 | } | |
82 | graphsHdr := &tar.Header{ | |
83 | Name: name + "/graphs", | |
84 | Typeflag: tar.TypeDir, | |
85 | Mode: 0755, | |
86 | } | |
87 | err := d.tar.WriteHeader(topHdr) | |
88 | // if the first errors, the second will too | |
89 | err = d.tar.WriteHeader(graphsHdr) | |
90 | if err != nil { | |
91 | return nil, err | |
92 | } | |
93 | ||
94 | return d, nil | |
95 | } | |
96 | ||
97 | // debugInfo provides various methods for writing debug information to a | |
98 | // central archive. The debugInfo struct should be initialized once before any | |
99 | // output is written, and Close should be called before program exit. All | |
100 | // exported methods on debugInfo will be safe for concurrent use. The exported | |
101 | // methods are also all safe to call on a nil pointer, so that there is no need | |
102 | // for conditional blocks before writing debug information. | |
103 | // | |
104 | // Each write operation done by the debugInfo will flush the gzip.Writer and | |
105 | // tar.Writer, and call Sync() or Flush() on the output writer as needed. This | |
106 | // ensures that as much data as possible is written to storage in the event of | |
107 | // a crash. The append format of the tar file, and the stream format of the | |
108 | // gzip writer allow easy recovery f the data in the event that the debugInfo | |
109 | // is not closed before program exit. | |
110 | type debugInfo struct { | |
111 | sync.Mutex | |
112 | ||
113 | // archive root directory name | |
114 | name string | |
115 | ||
116 | // current operation phase | |
117 | phase string | |
118 | ||
119 | // step is monotonic counter for for recording the order of operations | |
120 | step int | |
121 | ||
122 | // flag to protect Close() | |
123 | closed bool | |
124 | ||
125 | // the debug log output is in a tar.gz format, written to the io.Writer w | |
126 | w io.Writer | |
127 | gz *gzip.Writer | |
128 | tar *tar.Writer | |
129 | } | |
130 | ||
131 | // Set the name of the current operational phase in the debug handler. Each file | |
132 | // in the archive will contain the name of the phase in which it was created, | |
133 | // i.e. "input", "apply", "plan", "refresh", "validate" | |
134 | func (d *debugInfo) SetPhase(phase string) { | |
135 | if d == nil { | |
136 | return | |
137 | } | |
138 | d.Lock() | |
139 | defer d.Unlock() | |
140 | ||
141 | d.phase = phase | |
142 | } | |
143 | ||
144 | // Close the debugInfo, finalizing the data in storage. This closes the | |
145 | // tar.Writer, the gzip.Wrtier, and if the output writer is an io.Closer, it is | |
146 | // also closed. | |
147 | func (d *debugInfo) Close() error { | |
148 | if d == nil { | |
149 | return nil | |
150 | } | |
151 | ||
152 | d.Lock() | |
153 | defer d.Unlock() | |
154 | ||
155 | if d.closed { | |
156 | return nil | |
157 | } | |
158 | d.closed = true | |
159 | ||
160 | d.tar.Close() | |
161 | d.gz.Close() | |
162 | ||
163 | if c, ok := d.w.(io.Closer); ok { | |
164 | return c.Close() | |
165 | } | |
166 | return nil | |
167 | } | |
168 | ||
169 | // debug buffer is an io.WriteCloser that will write itself to the debug | |
170 | // archive when closed. | |
171 | type debugBuffer struct { | |
172 | debugInfo *debugInfo | |
173 | name string | |
174 | buf bytes.Buffer | |
175 | } | |
176 | ||
177 | func (b *debugBuffer) Write(d []byte) (int, error) { | |
178 | return b.buf.Write(d) | |
179 | } | |
180 | ||
181 | func (b *debugBuffer) Close() error { | |
182 | return b.debugInfo.WriteFile(b.name, b.buf.Bytes()) | |
183 | } | |
184 | ||
185 | // ioutils only has a noop ReadCloser | |
186 | type nopWriteCloser struct{} | |
187 | ||
188 | func (nopWriteCloser) Write([]byte) (int, error) { return 0, nil } | |
189 | func (nopWriteCloser) Close() error { return nil } | |
190 | ||
191 | // NewFileWriter returns an io.WriteClose that will be buffered and written to | |
192 | // the debug archive when closed. | |
193 | func (d *debugInfo) NewFileWriter(name string) io.WriteCloser { | |
194 | if d == nil { | |
195 | return nopWriteCloser{} | |
196 | } | |
197 | ||
198 | return &debugBuffer{ | |
199 | debugInfo: d, | |
200 | name: name, | |
201 | } | |
202 | } | |
203 | ||
204 | type syncer interface { | |
205 | Sync() error | |
206 | } | |
207 | ||
208 | type flusher interface { | |
209 | Flush() error | |
210 | } | |
211 | ||
212 | // Flush the tar.Writer and the gzip.Writer. Flush() or Sync() will be called | |
213 | // on the output writer if they are available. | |
214 | func (d *debugInfo) flush() { | |
215 | d.tar.Flush() | |
216 | d.gz.Flush() | |
217 | ||
218 | if f, ok := d.w.(flusher); ok { | |
219 | f.Flush() | |
220 | } | |
221 | ||
222 | if s, ok := d.w.(syncer); ok { | |
223 | s.Sync() | |
224 | } | |
225 | } | |
226 | ||
227 | // WriteFile writes data as a single file to the debug arhive. | |
228 | func (d *debugInfo) WriteFile(name string, data []byte) error { | |
229 | if d == nil { | |
230 | return nil | |
231 | } | |
232 | ||
233 | d.Lock() | |
234 | defer d.Unlock() | |
235 | return d.writeFile(name, data) | |
236 | } | |
237 | ||
238 | func (d *debugInfo) writeFile(name string, data []byte) error { | |
239 | defer d.flush() | |
240 | path := fmt.Sprintf("%s/%d-%s-%s", d.name, d.step, d.phase, name) | |
241 | d.step++ | |
242 | ||
243 | hdr := &tar.Header{ | |
244 | Name: path, | |
245 | Mode: 0644, | |
246 | Size: int64(len(data)), | |
247 | } | |
248 | err := d.tar.WriteHeader(hdr) | |
249 | if err != nil { | |
250 | return err | |
251 | } | |
252 | ||
253 | _, err = d.tar.Write(data) | |
254 | return err | |
255 | } | |
256 | ||
257 | // DebugHook implements all methods of the terraform.Hook interface, and writes | |
258 | // the arguments to a file in the archive. When a suitable format for the | |
259 | // argument isn't available, the argument is encoded using json.Marshal. If the | |
260 | // debug handler is nil, all DebugHook methods are noop, so no time is spent in | |
261 | // marshaling the data structures. | |
262 | type DebugHook struct{} | |
263 | ||
264 | func (*DebugHook) PreApply(ii *InstanceInfo, is *InstanceState, id *InstanceDiff) (HookAction, error) { | |
265 | if dbug == nil { | |
266 | return HookActionContinue, nil | |
267 | } | |
268 | ||
269 | var buf bytes.Buffer | |
270 | ||
271 | if ii != nil { | |
272 | buf.WriteString(ii.HumanId() + "\n") | |
273 | } | |
274 | ||
275 | if is != nil { | |
276 | buf.WriteString(is.String() + "\n") | |
277 | } | |
278 | ||
279 | idCopy, err := id.Copy() | |
280 | if err != nil { | |
281 | return HookActionContinue, err | |
282 | } | |
283 | js, err := json.MarshalIndent(idCopy, "", " ") | |
284 | if err != nil { | |
285 | return HookActionContinue, err | |
286 | } | |
287 | buf.Write(js) | |
288 | ||
289 | dbug.WriteFile("hook-PreApply", buf.Bytes()) | |
290 | ||
291 | return HookActionContinue, nil | |
292 | } | |
293 | ||
294 | func (*DebugHook) PostApply(ii *InstanceInfo, is *InstanceState, err error) (HookAction, error) { | |
295 | if dbug == nil { | |
296 | return HookActionContinue, nil | |
297 | } | |
298 | ||
299 | var buf bytes.Buffer | |
300 | ||
301 | if ii != nil { | |
302 | buf.WriteString(ii.HumanId() + "\n") | |
303 | } | |
304 | ||
305 | if is != nil { | |
306 | buf.WriteString(is.String() + "\n") | |
307 | } | |
308 | ||
309 | if err != nil { | |
310 | buf.WriteString(err.Error()) | |
311 | } | |
312 | ||
313 | dbug.WriteFile("hook-PostApply", buf.Bytes()) | |
314 | ||
315 | return HookActionContinue, nil | |
316 | } | |
317 | ||
318 | func (*DebugHook) PreDiff(ii *InstanceInfo, is *InstanceState) (HookAction, error) { | |
319 | if dbug == nil { | |
320 | return HookActionContinue, nil | |
321 | } | |
322 | ||
323 | var buf bytes.Buffer | |
324 | if ii != nil { | |
325 | buf.WriteString(ii.HumanId() + "\n") | |
326 | } | |
327 | ||
328 | if is != nil { | |
329 | buf.WriteString(is.String()) | |
330 | buf.WriteString("\n") | |
331 | } | |
332 | dbug.WriteFile("hook-PreDiff", buf.Bytes()) | |
333 | ||
334 | return HookActionContinue, nil | |
335 | } | |
336 | ||
337 | func (*DebugHook) PostDiff(ii *InstanceInfo, id *InstanceDiff) (HookAction, error) { | |
338 | if dbug == nil { | |
339 | return HookActionContinue, nil | |
340 | } | |
341 | ||
342 | var buf bytes.Buffer | |
343 | if ii != nil { | |
344 | buf.WriteString(ii.HumanId() + "\n") | |
345 | } | |
346 | ||
347 | idCopy, err := id.Copy() | |
348 | if err != nil { | |
349 | return HookActionContinue, err | |
350 | } | |
351 | js, err := json.MarshalIndent(idCopy, "", " ") | |
352 | if err != nil { | |
353 | return HookActionContinue, err | |
354 | } | |
355 | buf.Write(js) | |
356 | ||
357 | dbug.WriteFile("hook-PostDiff", buf.Bytes()) | |
358 | ||
359 | return HookActionContinue, nil | |
360 | } | |
361 | ||
362 | func (*DebugHook) PreProvisionResource(ii *InstanceInfo, is *InstanceState) (HookAction, error) { | |
363 | if dbug == nil { | |
364 | return HookActionContinue, nil | |
365 | } | |
366 | ||
367 | var buf bytes.Buffer | |
368 | if ii != nil { | |
369 | buf.WriteString(ii.HumanId() + "\n") | |
370 | } | |
371 | ||
372 | if is != nil { | |
373 | buf.WriteString(is.String()) | |
374 | buf.WriteString("\n") | |
375 | } | |
376 | dbug.WriteFile("hook-PreProvisionResource", buf.Bytes()) | |
377 | ||
378 | return HookActionContinue, nil | |
379 | } | |
380 | ||
381 | func (*DebugHook) PostProvisionResource(ii *InstanceInfo, is *InstanceState) (HookAction, error) { | |
382 | if dbug == nil { | |
383 | return HookActionContinue, nil | |
384 | } | |
385 | ||
386 | var buf bytes.Buffer | |
387 | if ii != nil { | |
388 | buf.WriteString(ii.HumanId()) | |
389 | buf.WriteString("\n") | |
390 | } | |
391 | ||
392 | if is != nil { | |
393 | buf.WriteString(is.String()) | |
394 | buf.WriteString("\n") | |
395 | } | |
396 | dbug.WriteFile("hook-PostProvisionResource", buf.Bytes()) | |
397 | return HookActionContinue, nil | |
398 | } | |
399 | ||
400 | func (*DebugHook) PreProvision(ii *InstanceInfo, s string) (HookAction, error) { | |
401 | if dbug == nil { | |
402 | return HookActionContinue, nil | |
403 | } | |
404 | ||
405 | var buf bytes.Buffer | |
406 | if ii != nil { | |
407 | buf.WriteString(ii.HumanId()) | |
408 | buf.WriteString("\n") | |
409 | } | |
410 | buf.WriteString(s + "\n") | |
411 | ||
412 | dbug.WriteFile("hook-PreProvision", buf.Bytes()) | |
413 | return HookActionContinue, nil | |
414 | } | |
415 | ||
416 | func (*DebugHook) PostProvision(ii *InstanceInfo, s string, err error) (HookAction, error) { | |
417 | if dbug == nil { | |
418 | return HookActionContinue, nil | |
419 | } | |
420 | ||
421 | var buf bytes.Buffer | |
422 | if ii != nil { | |
423 | buf.WriteString(ii.HumanId() + "\n") | |
424 | } | |
425 | buf.WriteString(s + "\n") | |
426 | ||
427 | dbug.WriteFile("hook-PostProvision", buf.Bytes()) | |
428 | return HookActionContinue, nil | |
429 | } | |
430 | ||
431 | func (*DebugHook) ProvisionOutput(ii *InstanceInfo, s1 string, s2 string) { | |
432 | if dbug == nil { | |
433 | return | |
434 | } | |
435 | ||
436 | var buf bytes.Buffer | |
437 | if ii != nil { | |
438 | buf.WriteString(ii.HumanId()) | |
439 | buf.WriteString("\n") | |
440 | } | |
441 | buf.WriteString(s1 + "\n") | |
442 | buf.WriteString(s2 + "\n") | |
443 | ||
444 | dbug.WriteFile("hook-ProvisionOutput", buf.Bytes()) | |
445 | } | |
446 | ||
447 | func (*DebugHook) PreRefresh(ii *InstanceInfo, is *InstanceState) (HookAction, error) { | |
448 | if dbug == nil { | |
449 | return HookActionContinue, nil | |
450 | } | |
451 | ||
452 | var buf bytes.Buffer | |
453 | if ii != nil { | |
454 | buf.WriteString(ii.HumanId() + "\n") | |
455 | } | |
456 | ||
457 | if is != nil { | |
458 | buf.WriteString(is.String()) | |
459 | buf.WriteString("\n") | |
460 | } | |
461 | dbug.WriteFile("hook-PreRefresh", buf.Bytes()) | |
462 | return HookActionContinue, nil | |
463 | } | |
464 | ||
465 | func (*DebugHook) PostRefresh(ii *InstanceInfo, is *InstanceState) (HookAction, error) { | |
466 | if dbug == nil { | |
467 | return HookActionContinue, nil | |
468 | } | |
469 | ||
470 | var buf bytes.Buffer | |
471 | if ii != nil { | |
472 | buf.WriteString(ii.HumanId()) | |
473 | buf.WriteString("\n") | |
474 | } | |
475 | ||
476 | if is != nil { | |
477 | buf.WriteString(is.String()) | |
478 | buf.WriteString("\n") | |
479 | } | |
480 | dbug.WriteFile("hook-PostRefresh", buf.Bytes()) | |
481 | return HookActionContinue, nil | |
482 | } | |
483 | ||
484 | func (*DebugHook) PreImportState(ii *InstanceInfo, s string) (HookAction, error) { | |
485 | if dbug == nil { | |
486 | return HookActionContinue, nil | |
487 | } | |
488 | ||
489 | var buf bytes.Buffer | |
490 | if ii != nil { | |
491 | buf.WriteString(ii.HumanId()) | |
492 | buf.WriteString("\n") | |
493 | } | |
494 | buf.WriteString(s + "\n") | |
495 | ||
496 | dbug.WriteFile("hook-PreImportState", buf.Bytes()) | |
497 | return HookActionContinue, nil | |
498 | } | |
499 | ||
500 | func (*DebugHook) PostImportState(ii *InstanceInfo, iss []*InstanceState) (HookAction, error) { | |
501 | if dbug == nil { | |
502 | return HookActionContinue, nil | |
503 | } | |
504 | ||
505 | var buf bytes.Buffer | |
506 | ||
507 | if ii != nil { | |
508 | buf.WriteString(ii.HumanId() + "\n") | |
509 | } | |
510 | ||
511 | for _, is := range iss { | |
512 | if is != nil { | |
513 | buf.WriteString(is.String() + "\n") | |
514 | } | |
515 | } | |
516 | dbug.WriteFile("hook-PostImportState", buf.Bytes()) | |
517 | return HookActionContinue, nil | |
518 | } | |
519 | ||
520 | // skip logging this for now, since it could be huge | |
521 | func (*DebugHook) PostStateUpdate(*State) (HookAction, error) { | |
522 | return HookActionContinue, nil | |
523 | } |