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