github.com/rhenning/terraform@v0.8.0-beta2/terraform/debug.go (about) 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 // WriteGraph takes a DebugGraph and writes both the DebugGraph as a dot file 228 // in the debug archive, and extracts any logs that the DebugGraph collected 229 // and writes them to a log file in the archive. 230 func (d *debugInfo) WriteGraph(name string, g *Graph) error { 231 if d == nil || g == nil { 232 return nil 233 } 234 d.Lock() 235 defer d.Unlock() 236 237 // If we crash, the file won't be correctly closed out, but we can rebuild 238 // the archive if we have to as long as every file has been flushed and 239 // sync'ed. 240 defer d.flush() 241 242 dotPath := fmt.Sprintf("%s/graphs/%d-%s-%s.dot", d.name, d.step, d.phase, name) 243 d.step++ 244 245 dotBytes := g.Dot(nil) 246 hdr := &tar.Header{ 247 Name: dotPath, 248 Mode: 0644, 249 Size: int64(len(dotBytes)), 250 } 251 252 err := d.tar.WriteHeader(hdr) 253 if err != nil { 254 return err 255 } 256 257 _, err = d.tar.Write(dotBytes) 258 return err 259 } 260 261 // WriteFile writes data as a single file to the debug arhive. 262 func (d *debugInfo) WriteFile(name string, data []byte) error { 263 if d == nil { 264 return nil 265 } 266 267 d.Lock() 268 defer d.Unlock() 269 return d.writeFile(name, data) 270 } 271 272 func (d *debugInfo) writeFile(name string, data []byte) error { 273 defer d.flush() 274 path := fmt.Sprintf("%s/%d-%s-%s", d.name, d.step, d.phase, name) 275 d.step++ 276 277 hdr := &tar.Header{ 278 Name: path, 279 Mode: 0644, 280 Size: int64(len(data)), 281 } 282 err := d.tar.WriteHeader(hdr) 283 if err != nil { 284 return err 285 } 286 287 _, err = d.tar.Write(data) 288 return err 289 } 290 291 // DebugHook implements all methods of the terraform.Hook interface, and writes 292 // the arguments to a file in the archive. When a suitable format for the 293 // argument isn't available, the argument is encoded using json.Marshal. If the 294 // debug handler is nil, all DebugHook methods are noop, so no time is spent in 295 // marshaling the data structures. 296 type DebugHook struct{} 297 298 func (*DebugHook) PreApply(ii *InstanceInfo, is *InstanceState, id *InstanceDiff) (HookAction, error) { 299 if dbug == nil { 300 return HookActionContinue, nil 301 } 302 303 var buf bytes.Buffer 304 305 if ii != nil { 306 buf.WriteString(ii.HumanId() + "\n") 307 } 308 309 if is != nil { 310 buf.WriteString(is.String() + "\n") 311 } 312 313 idCopy, err := id.Copy() 314 if err != nil { 315 return HookActionContinue, err 316 } 317 js, err := json.MarshalIndent(idCopy, "", " ") 318 if err != nil { 319 return HookActionContinue, err 320 } 321 buf.Write(js) 322 323 dbug.WriteFile("hook-PreApply", buf.Bytes()) 324 325 return HookActionContinue, nil 326 } 327 328 func (*DebugHook) PostApply(ii *InstanceInfo, is *InstanceState, err error) (HookAction, error) { 329 if dbug == nil { 330 return HookActionContinue, nil 331 } 332 333 var buf bytes.Buffer 334 335 if ii != nil { 336 buf.WriteString(ii.HumanId() + "\n") 337 } 338 339 if is != nil { 340 buf.WriteString(is.String() + "\n") 341 } 342 343 if err != nil { 344 buf.WriteString(err.Error()) 345 } 346 347 dbug.WriteFile("hook-PostApply", buf.Bytes()) 348 349 return HookActionContinue, nil 350 } 351 352 func (*DebugHook) PreDiff(ii *InstanceInfo, is *InstanceState) (HookAction, error) { 353 if dbug == nil { 354 return HookActionContinue, nil 355 } 356 357 var buf bytes.Buffer 358 if ii != nil { 359 buf.WriteString(ii.HumanId() + "\n") 360 } 361 362 if is != nil { 363 buf.WriteString(is.String()) 364 buf.WriteString("\n") 365 } 366 dbug.WriteFile("hook-PreDiff", buf.Bytes()) 367 368 return HookActionContinue, nil 369 } 370 371 func (*DebugHook) PostDiff(ii *InstanceInfo, id *InstanceDiff) (HookAction, error) { 372 if dbug == nil { 373 return HookActionContinue, nil 374 } 375 376 var buf bytes.Buffer 377 if ii != nil { 378 buf.WriteString(ii.HumanId() + "\n") 379 } 380 381 idCopy, err := id.Copy() 382 if err != nil { 383 return HookActionContinue, err 384 } 385 js, err := json.MarshalIndent(idCopy, "", " ") 386 if err != nil { 387 return HookActionContinue, err 388 } 389 buf.Write(js) 390 391 dbug.WriteFile("hook-PostDiff", buf.Bytes()) 392 393 return HookActionContinue, nil 394 } 395 396 func (*DebugHook) PreProvisionResource(ii *InstanceInfo, is *InstanceState) (HookAction, error) { 397 if dbug == nil { 398 return HookActionContinue, nil 399 } 400 401 var buf bytes.Buffer 402 if ii != nil { 403 buf.WriteString(ii.HumanId() + "\n") 404 } 405 406 if is != nil { 407 buf.WriteString(is.String()) 408 buf.WriteString("\n") 409 } 410 dbug.WriteFile("hook-PreProvisionResource", buf.Bytes()) 411 412 return HookActionContinue, nil 413 } 414 415 func (*DebugHook) PostProvisionResource(ii *InstanceInfo, is *InstanceState) (HookAction, error) { 416 if dbug == nil { 417 return HookActionContinue, nil 418 } 419 420 var buf bytes.Buffer 421 if ii != nil { 422 buf.WriteString(ii.HumanId()) 423 buf.WriteString("\n") 424 } 425 426 if is != nil { 427 buf.WriteString(is.String()) 428 buf.WriteString("\n") 429 } 430 dbug.WriteFile("hook-PostProvisionResource", buf.Bytes()) 431 return HookActionContinue, nil 432 } 433 434 func (*DebugHook) PreProvision(ii *InstanceInfo, s string) (HookAction, error) { 435 if dbug == nil { 436 return HookActionContinue, nil 437 } 438 439 var buf bytes.Buffer 440 if ii != nil { 441 buf.WriteString(ii.HumanId()) 442 buf.WriteString("\n") 443 } 444 buf.WriteString(s + "\n") 445 446 dbug.WriteFile("hook-PreProvision", buf.Bytes()) 447 return HookActionContinue, nil 448 } 449 450 func (*DebugHook) PostProvision(ii *InstanceInfo, s string) (HookAction, error) { 451 if dbug == nil { 452 return HookActionContinue, nil 453 } 454 455 var buf bytes.Buffer 456 if ii != nil { 457 buf.WriteString(ii.HumanId() + "\n") 458 } 459 buf.WriteString(s + "\n") 460 461 dbug.WriteFile("hook-PostProvision", buf.Bytes()) 462 return HookActionContinue, nil 463 } 464 465 func (*DebugHook) ProvisionOutput(ii *InstanceInfo, s1 string, s2 string) { 466 if dbug == nil { 467 return 468 } 469 470 var buf bytes.Buffer 471 if ii != nil { 472 buf.WriteString(ii.HumanId()) 473 buf.WriteString("\n") 474 } 475 buf.WriteString(s1 + "\n") 476 buf.WriteString(s2 + "\n") 477 478 dbug.WriteFile("hook-ProvisionOutput", buf.Bytes()) 479 } 480 481 func (*DebugHook) PreRefresh(ii *InstanceInfo, is *InstanceState) (HookAction, error) { 482 if dbug == nil { 483 return HookActionContinue, nil 484 } 485 486 var buf bytes.Buffer 487 if ii != nil { 488 buf.WriteString(ii.HumanId() + "\n") 489 } 490 491 if is != nil { 492 buf.WriteString(is.String()) 493 buf.WriteString("\n") 494 } 495 dbug.WriteFile("hook-PreRefresh", buf.Bytes()) 496 return HookActionContinue, nil 497 } 498 499 func (*DebugHook) PostRefresh(ii *InstanceInfo, is *InstanceState) (HookAction, error) { 500 if dbug == nil { 501 return HookActionContinue, nil 502 } 503 504 var buf bytes.Buffer 505 if ii != nil { 506 buf.WriteString(ii.HumanId()) 507 buf.WriteString("\n") 508 } 509 510 if is != nil { 511 buf.WriteString(is.String()) 512 buf.WriteString("\n") 513 } 514 dbug.WriteFile("hook-PostRefresh", buf.Bytes()) 515 return HookActionContinue, nil 516 } 517 518 func (*DebugHook) PreImportState(ii *InstanceInfo, s string) (HookAction, error) { 519 if dbug == nil { 520 return HookActionContinue, nil 521 } 522 523 var buf bytes.Buffer 524 if ii != nil { 525 buf.WriteString(ii.HumanId()) 526 buf.WriteString("\n") 527 } 528 buf.WriteString(s + "\n") 529 530 dbug.WriteFile("hook-PreImportState", buf.Bytes()) 531 return HookActionContinue, nil 532 } 533 534 func (*DebugHook) PostImportState(ii *InstanceInfo, iss []*InstanceState) (HookAction, error) { 535 if dbug == nil { 536 return HookActionContinue, nil 537 } 538 539 var buf bytes.Buffer 540 541 if ii != nil { 542 buf.WriteString(ii.HumanId() + "\n") 543 } 544 545 for _, is := range iss { 546 if is != nil { 547 buf.WriteString(is.String() + "\n") 548 } 549 } 550 dbug.WriteFile("hook-PostImportState", buf.Bytes()) 551 return HookActionContinue, nil 552 } 553 554 // skip logging this for now, since it could be huge 555 func (*DebugHook) PostStateUpdate(*State) (HookAction, error) { 556 return HookActionContinue, nil 557 }