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