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