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