github.com/ben-turner/terraform@v0.11.8-0.20180503104400-0cc9e050ecd4/command/command_test.go (about) 1 package command 2 3 import ( 4 "bytes" 5 "crypto/md5" 6 "encoding/base64" 7 "encoding/json" 8 "flag" 9 "fmt" 10 "io" 11 "io/ioutil" 12 "log" 13 "net/http" 14 "net/http/httptest" 15 "os" 16 "os/exec" 17 "path/filepath" 18 "strings" 19 "syscall" 20 "testing" 21 22 "github.com/hashicorp/terraform/config/module" 23 "github.com/hashicorp/terraform/helper/logging" 24 "github.com/hashicorp/terraform/terraform" 25 ) 26 27 // This is the directory where our test fixtures are. 28 var fixtureDir = "./test-fixtures" 29 30 // a top level temp directory which will be cleaned after all tests 31 var testingDir string 32 33 func init() { 34 test = true 35 36 // Expand the fixture dir on init because we change the working 37 // directory in some tests. 38 var err error 39 fixtureDir, err = filepath.Abs(fixtureDir) 40 if err != nil { 41 panic(err) 42 } 43 44 testingDir, err = ioutil.TempDir(testingDir, "tf") 45 if err != nil { 46 panic(err) 47 } 48 } 49 50 func TestMain(m *testing.M) { 51 defer os.RemoveAll(testingDir) 52 53 flag.Parse() 54 if testing.Verbose() { 55 // if we're verbose, use the logging requested by TF_LOG 56 logging.SetOutput() 57 } else { 58 // otherwise silence all logs 59 log.SetOutput(ioutil.Discard) 60 } 61 62 os.Exit(m.Run()) 63 } 64 65 func tempDir(t *testing.T) string { 66 t.Helper() 67 68 dir, err := ioutil.TempDir(testingDir, "tf") 69 if err != nil { 70 t.Fatalf("err: %s", err) 71 } 72 if err := os.RemoveAll(dir); err != nil { 73 t.Fatalf("err: %s", err) 74 } 75 76 return dir 77 } 78 79 func testFixturePath(name string) string { 80 return filepath.Join(fixtureDir, name) 81 } 82 83 func metaOverridesForProvider(p terraform.ResourceProvider) *testingOverrides { 84 return &testingOverrides{ 85 ProviderResolver: terraform.ResourceProviderResolverFixed( 86 map[string]terraform.ResourceProviderFactory{ 87 "test": func() (terraform.ResourceProvider, error) { 88 return p, nil 89 }, 90 }, 91 ), 92 } 93 } 94 95 func metaOverridesForProviderAndProvisioner(p terraform.ResourceProvider, pr terraform.ResourceProvisioner) *testingOverrides { 96 return &testingOverrides{ 97 ProviderResolver: terraform.ResourceProviderResolverFixed( 98 map[string]terraform.ResourceProviderFactory{ 99 "test": func() (terraform.ResourceProvider, error) { 100 return p, nil 101 }, 102 }, 103 ), 104 Provisioners: map[string]terraform.ResourceProvisionerFactory{ 105 "shell": func() (terraform.ResourceProvisioner, error) { 106 return pr, nil 107 }, 108 }, 109 } 110 } 111 112 func testModule(t *testing.T, name string) *module.Tree { 113 t.Helper() 114 115 mod, err := module.NewTreeModule("", filepath.Join(fixtureDir, name)) 116 if err != nil { 117 t.Fatalf("err: %s", err) 118 } 119 120 s := module.NewStorage(tempDir(t), nil, nil) 121 s.Mode = module.GetModeGet 122 if err := mod.Load(s); err != nil { 123 t.Fatalf("err: %s", err) 124 } 125 126 return mod 127 } 128 129 // testPlan returns a non-nil noop plan. 130 func testPlan(t *testing.T) *terraform.Plan { 131 t.Helper() 132 133 state := terraform.NewState() 134 state.RootModule().Outputs["foo"] = &terraform.OutputState{ 135 Type: "string", 136 Value: "foo", 137 } 138 139 return &terraform.Plan{ 140 Module: testModule(t, "apply"), 141 State: state, 142 } 143 } 144 145 func testPlanFile(t *testing.T, plan *terraform.Plan) string { 146 t.Helper() 147 148 path := testTempFile(t) 149 150 f, err := os.Create(path) 151 if err != nil { 152 t.Fatalf("err: %s", err) 153 } 154 defer f.Close() 155 156 if err := terraform.WritePlan(plan, f); err != nil { 157 t.Fatalf("err: %s", err) 158 } 159 160 return path 161 } 162 163 func testReadPlan(t *testing.T, path string) *terraform.Plan { 164 t.Helper() 165 166 f, err := os.Open(path) 167 if err != nil { 168 t.Fatalf("err: %s", err) 169 } 170 defer f.Close() 171 172 p, err := terraform.ReadPlan(f) 173 if err != nil { 174 t.Fatalf("err: %s", err) 175 } 176 177 return p 178 } 179 180 // testState returns a test State structure that we use for a lot of tests. 181 func testState() *terraform.State { 182 state := &terraform.State{ 183 Modules: []*terraform.ModuleState{ 184 &terraform.ModuleState{ 185 Path: []string{"root"}, 186 Resources: map[string]*terraform.ResourceState{ 187 "test_instance.foo": &terraform.ResourceState{ 188 Type: "test_instance", 189 Primary: &terraform.InstanceState{ 190 ID: "bar", 191 }, 192 }, 193 }, 194 Outputs: map[string]*terraform.OutputState{}, 195 }, 196 }, 197 } 198 state.Init() 199 return state 200 } 201 202 func testStateFile(t *testing.T, s *terraform.State) string { 203 t.Helper() 204 205 path := testTempFile(t) 206 207 f, err := os.Create(path) 208 if err != nil { 209 t.Fatalf("err: %s", err) 210 } 211 defer f.Close() 212 213 if err := terraform.WriteState(s, f); err != nil { 214 t.Fatalf("err: %s", err) 215 } 216 217 return path 218 } 219 220 // testStateFileDefault writes the state out to the default statefile 221 // in the cwd. Use `testCwd` to change into a temp cwd. 222 func testStateFileDefault(t *testing.T, s *terraform.State) string { 223 t.Helper() 224 225 f, err := os.Create(DefaultStateFilename) 226 if err != nil { 227 t.Fatalf("err: %s", err) 228 } 229 defer f.Close() 230 231 if err := terraform.WriteState(s, f); err != nil { 232 t.Fatalf("err: %s", err) 233 } 234 235 return DefaultStateFilename 236 } 237 238 // testStateFileRemote writes the state out to the remote statefile 239 // in the cwd. Use `testCwd` to change into a temp cwd. 240 func testStateFileRemote(t *testing.T, s *terraform.State) string { 241 t.Helper() 242 243 path := filepath.Join(DefaultDataDir, DefaultStateFilename) 244 if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { 245 t.Fatalf("err: %s", err) 246 } 247 248 f, err := os.Create(path) 249 if err != nil { 250 t.Fatalf("err: %s", err) 251 } 252 defer f.Close() 253 254 if err := terraform.WriteState(s, f); err != nil { 255 t.Fatalf("err: %s", err) 256 } 257 258 return path 259 } 260 261 // testStateRead reads the state from a file 262 func testStateRead(t *testing.T, path string) *terraform.State { 263 t.Helper() 264 265 f, err := os.Open(path) 266 if err != nil { 267 t.Fatalf("err: %s", err) 268 } 269 defer f.Close() 270 271 newState, err := terraform.ReadState(f) 272 if err != nil { 273 t.Fatalf("err: %s", err) 274 } 275 276 return newState 277 } 278 279 // testStateOutput tests that the state at the given path contains 280 // the expected state string. 281 func testStateOutput(t *testing.T, path string, expected string) { 282 t.Helper() 283 284 newState := testStateRead(t, path) 285 actual := strings.TrimSpace(newState.String()) 286 expected = strings.TrimSpace(expected) 287 if actual != expected { 288 t.Fatalf("expected:\n%s\nactual:\n%s", expected, actual) 289 } 290 } 291 292 func testProvider() *terraform.MockResourceProvider { 293 p := new(terraform.MockResourceProvider) 294 p.DiffReturn = &terraform.InstanceDiff{} 295 p.RefreshFn = func( 296 info *terraform.InstanceInfo, 297 s *terraform.InstanceState) (*terraform.InstanceState, error) { 298 return s, nil 299 } 300 p.ResourcesReturn = []terraform.ResourceType{ 301 terraform.ResourceType{ 302 Name: "test_instance", 303 }, 304 } 305 306 return p 307 } 308 309 func testTempFile(t *testing.T) string { 310 t.Helper() 311 312 return filepath.Join(testTempDir(t), "state.tfstate") 313 } 314 315 func testTempDir(t *testing.T) string { 316 t.Helper() 317 318 d, err := ioutil.TempDir(testingDir, "tf") 319 if err != nil { 320 t.Fatalf("err: %s", err) 321 } 322 323 return d 324 } 325 326 // testRename renames the path to new and returns a function to defer to 327 // revert the rename. 328 func testRename(t *testing.T, base, path, new string) func() { 329 t.Helper() 330 331 if base != "" { 332 path = filepath.Join(base, path) 333 new = filepath.Join(base, new) 334 } 335 336 if err := os.Rename(path, new); err != nil { 337 t.Fatalf("err: %s", err) 338 } 339 340 return func() { 341 // Just re-rename and ignore the return value 342 testRename(t, "", new, path) 343 } 344 } 345 346 // testChdir changes the directory and returns a function to defer to 347 // revert the old cwd. 348 func testChdir(t *testing.T, new string) func() { 349 t.Helper() 350 351 old, err := os.Getwd() 352 if err != nil { 353 t.Fatalf("err: %s", err) 354 } 355 356 if err := os.Chdir(new); err != nil { 357 t.Fatalf("err: %v", err) 358 } 359 360 return func() { 361 // Re-run the function ignoring the defer result 362 testChdir(t, old) 363 } 364 } 365 366 // testCwd is used to change the current working directory 367 // into a test directory that should be remoted after 368 func testCwd(t *testing.T) (string, string) { 369 t.Helper() 370 371 tmp, err := ioutil.TempDir(testingDir, "tf") 372 if err != nil { 373 t.Fatalf("err: %v", err) 374 } 375 376 cwd, err := os.Getwd() 377 if err != nil { 378 t.Fatalf("err: %v", err) 379 } 380 381 if err := os.Chdir(tmp); err != nil { 382 t.Fatalf("err: %v", err) 383 } 384 385 return tmp, cwd 386 } 387 388 // testFixCwd is used to as a defer to testDir 389 func testFixCwd(t *testing.T, tmp, cwd string) { 390 t.Helper() 391 392 if err := os.Chdir(cwd); err != nil { 393 t.Fatalf("err: %v", err) 394 } 395 396 if err := os.RemoveAll(tmp); err != nil { 397 t.Fatalf("err: %v", err) 398 } 399 } 400 401 // testStdinPipe changes os.Stdin to be a pipe that sends the data from 402 // the reader before closing the pipe. 403 // 404 // The returned function should be deferred to properly clean up and restore 405 // the original stdin. 406 func testStdinPipe(t *testing.T, src io.Reader) func() { 407 t.Helper() 408 409 r, w, err := os.Pipe() 410 if err != nil { 411 t.Fatalf("err: %s", err) 412 } 413 414 // Modify stdin to point to our new pipe 415 old := os.Stdin 416 os.Stdin = r 417 418 // Copy the data from the reader to the pipe 419 go func() { 420 defer w.Close() 421 io.Copy(w, src) 422 }() 423 424 return func() { 425 // Close our read end 426 r.Close() 427 428 // Reset stdin 429 os.Stdin = old 430 } 431 } 432 433 // Modify os.Stdout to write to the given buffer. Note that this is generally 434 // not useful since the commands are configured to write to a cli.Ui, not 435 // Stdout directly. Commands like `console` though use the raw stdout. 436 func testStdoutCapture(t *testing.T, dst io.Writer) func() { 437 t.Helper() 438 439 r, w, err := os.Pipe() 440 if err != nil { 441 t.Fatalf("err: %s", err) 442 } 443 444 // Modify stdout 445 old := os.Stdout 446 os.Stdout = w 447 448 // Copy 449 doneCh := make(chan struct{}) 450 go func() { 451 defer close(doneCh) 452 defer r.Close() 453 io.Copy(dst, r) 454 }() 455 456 return func() { 457 // Close the writer end of the pipe 458 w.Sync() 459 w.Close() 460 461 // Reset stdout 462 os.Stdout = old 463 464 // Wait for the data copy to complete to avoid a race reading data 465 <-doneCh 466 } 467 } 468 469 // testInteractiveInput configures tests so that the answers given are sent 470 // in order to interactive prompts. The returned function must be called 471 // in a defer to clean up. 472 func testInteractiveInput(t *testing.T, answers []string) func() { 473 t.Helper() 474 475 // Disable test mode so input is called 476 test = false 477 478 // Setup reader/writers 479 testInputResponse = answers 480 defaultInputReader = bytes.NewBufferString("") 481 defaultInputWriter = new(bytes.Buffer) 482 483 // Return the cleanup 484 return func() { 485 test = true 486 testInputResponse = nil 487 } 488 } 489 490 // testInputMap configures tests so that the given answers are returned 491 // for calls to Input when the right question is asked. The key is the 492 // question "Id" that is used. 493 func testInputMap(t *testing.T, answers map[string]string) func() { 494 t.Helper() 495 496 // Disable test mode so input is called 497 test = false 498 499 // Setup reader/writers 500 defaultInputReader = bytes.NewBufferString("") 501 defaultInputWriter = new(bytes.Buffer) 502 503 // Setup answers 504 testInputResponse = nil 505 testInputResponseMap = answers 506 507 // Return the cleanup 508 return func() { 509 test = true 510 testInputResponseMap = nil 511 } 512 } 513 514 // testBackendState is used to make a test HTTP server to test a configured 515 // backend. This returns the complete state that can be saved. Use 516 // `testStateFileRemote` to write the returned state. 517 func testBackendState(t *testing.T, s *terraform.State, c int) (*terraform.State, *httptest.Server) { 518 t.Helper() 519 520 var b64md5 string 521 buf := bytes.NewBuffer(nil) 522 523 cb := func(resp http.ResponseWriter, req *http.Request) { 524 if req.Method == "PUT" { 525 resp.WriteHeader(c) 526 return 527 } 528 if s == nil { 529 resp.WriteHeader(404) 530 return 531 } 532 533 resp.Header().Set("Content-MD5", b64md5) 534 resp.Write(buf.Bytes()) 535 } 536 537 // If a state was given, make sure we calculate the proper b64md5 538 if s != nil { 539 enc := json.NewEncoder(buf) 540 if err := enc.Encode(s); err != nil { 541 t.Fatalf("err: %v", err) 542 } 543 md5 := md5.Sum(buf.Bytes()) 544 b64md5 = base64.StdEncoding.EncodeToString(md5[:16]) 545 } 546 547 srv := httptest.NewServer(http.HandlerFunc(cb)) 548 549 state := terraform.NewState() 550 state.Backend = &terraform.BackendState{ 551 Type: "http", 552 Config: map[string]interface{}{"address": srv.URL}, 553 Hash: 2529831861221416334, 554 } 555 556 return state, srv 557 } 558 559 // testRemoteState is used to make a test HTTP server to return a given 560 // state file that can be used for testing legacy remote state. 561 func testRemoteState(t *testing.T, s *terraform.State, c int) (*terraform.RemoteState, *httptest.Server) { 562 t.Helper() 563 564 var b64md5 string 565 buf := bytes.NewBuffer(nil) 566 567 cb := func(resp http.ResponseWriter, req *http.Request) { 568 if req.Method == "PUT" { 569 resp.WriteHeader(c) 570 return 571 } 572 if s == nil { 573 resp.WriteHeader(404) 574 return 575 } 576 577 resp.Header().Set("Content-MD5", b64md5) 578 resp.Write(buf.Bytes()) 579 } 580 581 srv := httptest.NewServer(http.HandlerFunc(cb)) 582 remote := &terraform.RemoteState{ 583 Type: "http", 584 Config: map[string]string{"address": srv.URL}, 585 } 586 587 if s != nil { 588 // Set the remote data 589 s.Remote = remote 590 591 enc := json.NewEncoder(buf) 592 if err := enc.Encode(s); err != nil { 593 t.Fatalf("err: %v", err) 594 } 595 md5 := md5.Sum(buf.Bytes()) 596 b64md5 = base64.StdEncoding.EncodeToString(md5[:16]) 597 } 598 599 return remote, srv 600 } 601 602 // testlockState calls a separate process to the lock the state file at path. 603 // deferFunc should be called in the caller to properly unlock the file. 604 // Since many tests change the working durectory, the sourcedir argument must be 605 // supplied to locate the statelocker.go source. 606 func testLockState(sourceDir, path string) (func(), error) { 607 // build and run the binary ourselves so we can quickly terminate it for cleanup 608 buildDir, err := ioutil.TempDir(testingDir, "locker") 609 if err != nil { 610 return nil, err 611 } 612 cleanFunc := func() { 613 os.RemoveAll(buildDir) 614 } 615 616 source := filepath.Join(sourceDir, "statelocker.go") 617 lockBin := filepath.Join(buildDir, "statelocker") 618 619 out, err := exec.Command("go", "build", "-o", lockBin, source).CombinedOutput() 620 if err != nil { 621 cleanFunc() 622 return nil, fmt.Errorf("%s %s", err, out) 623 } 624 625 locker := exec.Command(lockBin, path) 626 pr, pw, err := os.Pipe() 627 if err != nil { 628 cleanFunc() 629 return nil, err 630 } 631 defer pr.Close() 632 defer pw.Close() 633 locker.Stderr = pw 634 locker.Stdout = pw 635 636 if err := locker.Start(); err != nil { 637 return nil, err 638 } 639 deferFunc := func() { 640 cleanFunc() 641 locker.Process.Signal(syscall.SIGTERM) 642 locker.Wait() 643 } 644 645 // wait for the process to lock 646 buf := make([]byte, 1024) 647 n, err := pr.Read(buf) 648 if err != nil { 649 return deferFunc, fmt.Errorf("read from statelocker returned: %s", err) 650 } 651 652 output := string(buf[:n]) 653 if !strings.HasPrefix(output, "LOCKID") { 654 return deferFunc, fmt.Errorf("statelocker wrote: %s", string(buf[:n])) 655 } 656 return deferFunc, nil 657 }