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