github.com/paybyphone/terraform@v0.9.5-0.20170613192930-9706042ddd51/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 // 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 }