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