github.com/rjeczalik/terraform@v0.6.7-0.20160812060014-e251d5c7bd39/command/push_test.go (about) 1 package command 2 3 import ( 4 "archive/tar" 5 "bytes" 6 "compress/gzip" 7 "io" 8 "os" 9 "reflect" 10 "sort" 11 "testing" 12 13 atlas "github.com/hashicorp/atlas-go/v1" 14 "github.com/hashicorp/terraform/terraform" 15 "github.com/mitchellh/cli" 16 ) 17 18 func TestPush_good(t *testing.T) { 19 tmp, cwd := testCwd(t) 20 defer testFixCwd(t, tmp, cwd) 21 22 // Create remote state file, this should be pulled 23 conf, srv := testRemoteState(t, testState(), 200) 24 defer srv.Close() 25 26 // Persist local remote state 27 s := terraform.NewState() 28 s.Serial = 5 29 s.Remote = conf 30 testStateFileRemote(t, s) 31 32 // Path where the archive will be "uploaded" to 33 archivePath := testTempFile(t) 34 defer os.Remove(archivePath) 35 36 client := &mockPushClient{File: archivePath} 37 ui := new(cli.MockUi) 38 c := &PushCommand{ 39 Meta: Meta{ 40 ContextOpts: testCtxConfig(testProvider()), 41 Ui: ui, 42 }, 43 44 client: client, 45 } 46 47 args := []string{ 48 "-vcs=false", 49 testFixturePath("push"), 50 } 51 if code := c.Run(args); code != 0 { 52 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 53 } 54 55 actual := testArchiveStr(t, archivePath) 56 expected := []string{ 57 ".terraform/", 58 ".terraform/terraform.tfstate", 59 "main.tf", 60 } 61 if !reflect.DeepEqual(actual, expected) { 62 t.Fatalf("bad: %#v", actual) 63 } 64 65 variables := make(map[string]interface{}) 66 if !reflect.DeepEqual(client.UpsertOptions.Variables, variables) { 67 t.Fatalf("bad: %#v", client.UpsertOptions) 68 } 69 70 if client.UpsertOptions.Name != "foo" { 71 t.Fatalf("bad: %#v", client.UpsertOptions) 72 } 73 } 74 75 func TestPush_input(t *testing.T) { 76 tmp, cwd := testCwd(t) 77 defer testFixCwd(t, tmp, cwd) 78 79 // Create remote state file, this should be pulled 80 conf, srv := testRemoteState(t, testState(), 200) 81 defer srv.Close() 82 83 // Persist local remote state 84 s := terraform.NewState() 85 s.Serial = 5 86 s.Remote = conf 87 testStateFileRemote(t, s) 88 89 // Path where the archive will be "uploaded" to 90 archivePath := testTempFile(t) 91 defer os.Remove(archivePath) 92 93 client := &mockPushClient{File: archivePath} 94 ui := new(cli.MockUi) 95 c := &PushCommand{ 96 Meta: Meta{ 97 ContextOpts: testCtxConfig(testProvider()), 98 Ui: ui, 99 }, 100 101 client: client, 102 } 103 104 // Disable test mode so input would be asked and setup the 105 // input reader/writers. 106 test = false 107 defer func() { test = true }() 108 defaultInputReader = bytes.NewBufferString("foo\n") 109 defaultInputWriter = new(bytes.Buffer) 110 111 args := []string{ 112 "-vcs=false", 113 testFixturePath("push-input"), 114 } 115 if code := c.Run(args); code != 0 { 116 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 117 } 118 119 variables := map[string]interface{}{ 120 "foo": "foo", 121 } 122 123 if !reflect.DeepEqual(client.UpsertOptions.Variables, variables) { 124 t.Fatalf("bad: %#v", client.UpsertOptions.Variables) 125 } 126 } 127 128 // We want a variable from atlas to fill a missing variable locally 129 func TestPush_inputPartial(t *testing.T) { 130 tmp, cwd := testCwd(t) 131 defer testFixCwd(t, tmp, cwd) 132 133 // Create remote state file, this should be pulled 134 conf, srv := testRemoteState(t, testState(), 200) 135 defer srv.Close() 136 137 // Persist local remote state 138 s := terraform.NewState() 139 s.Serial = 5 140 s.Remote = conf 141 testStateFileRemote(t, s) 142 143 // Path where the archive will be "uploaded" to 144 archivePath := testTempFile(t) 145 defer os.Remove(archivePath) 146 147 client := &mockPushClient{ 148 File: archivePath, 149 GetResult: map[string]atlas.TFVar{ 150 "foo": atlas.TFVar{Key: "foo", Value: "bar"}, 151 }, 152 } 153 ui := new(cli.MockUi) 154 c := &PushCommand{ 155 Meta: Meta{ 156 ContextOpts: testCtxConfig(testProvider()), 157 Ui: ui, 158 }, 159 160 client: client, 161 } 162 163 // Disable test mode so input would be asked and setup the 164 // input reader/writers. 165 test = false 166 defer func() { test = true }() 167 defaultInputReader = bytes.NewBufferString("foo\n") 168 defaultInputWriter = new(bytes.Buffer) 169 170 args := []string{ 171 "-vcs=false", 172 testFixturePath("push-input-partial"), 173 } 174 if code := c.Run(args); code != 0 { 175 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 176 } 177 178 expectedTFVars := []atlas.TFVar{ 179 {Key: "bar", Value: "foo"}, 180 {Key: "foo", Value: "bar"}, 181 } 182 if !reflect.DeepEqual(client.UpsertOptions.TFVars, expectedTFVars) { 183 t.Logf("expected: %#v", expectedTFVars) 184 t.Fatalf("got: %#v", client.UpsertOptions.TFVars) 185 } 186 } 187 188 // This tests that the push command will override Atlas variables 189 // if requested. 190 func TestPush_localOverride(t *testing.T) { 191 // Disable test mode so input would be asked and setup the 192 // input reader/writers. 193 test = false 194 defer func() { test = true }() 195 defaultInputReader = bytes.NewBufferString("nope\n") 196 defaultInputWriter = new(bytes.Buffer) 197 198 tmp, cwd := testCwd(t) 199 defer testFixCwd(t, tmp, cwd) 200 201 // Create remote state file, this should be pulled 202 conf, srv := testRemoteState(t, testState(), 200) 203 defer srv.Close() 204 205 // Persist local remote state 206 s := terraform.NewState() 207 s.Serial = 5 208 s.Remote = conf 209 testStateFileRemote(t, s) 210 211 // Path where the archive will be "uploaded" to 212 archivePath := testTempFile(t) 213 defer os.Remove(archivePath) 214 215 client := &mockPushClient{File: archivePath} 216 // Provided vars should override existing ones 217 client.GetResult = map[string]atlas.TFVar{ 218 "foo": atlas.TFVar{ 219 Key: "foo", 220 Value: "old", 221 }, 222 } 223 ui := new(cli.MockUi) 224 c := &PushCommand{ 225 Meta: Meta{ 226 ContextOpts: testCtxConfig(testProvider()), 227 Ui: ui, 228 }, 229 230 client: client, 231 } 232 233 path := testFixturePath("push-tfvars") 234 args := []string{ 235 "-var-file", path + "/terraform.tfvars", 236 "-vcs=false", 237 "-overwrite=foo", 238 path, 239 } 240 if code := c.Run(args); code != 0 { 241 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 242 } 243 244 actual := testArchiveStr(t, archivePath) 245 expected := []string{ 246 ".terraform/", 247 ".terraform/terraform.tfstate", 248 "main.tf", 249 "terraform.tfvars", 250 } 251 if !reflect.DeepEqual(actual, expected) { 252 t.Fatalf("bad: %#v", actual) 253 } 254 255 if client.UpsertOptions.Name != "foo" { 256 t.Fatalf("bad: %#v", client.UpsertOptions) 257 } 258 259 expectedTFVars := pushTFVars() 260 261 if !reflect.DeepEqual(client.UpsertOptions.TFVars, expectedTFVars) { 262 t.Logf("expected: %#v", expectedTFVars) 263 t.Fatalf("got: %#v", client.UpsertOptions.TFVars) 264 } 265 } 266 267 // This tests that the push command will override Atlas variables 268 // even if we don't have it defined locally 269 func TestPush_remoteOverride(t *testing.T) { 270 // Disable test mode so input would be asked and setup the 271 // input reader/writers. 272 test = false 273 defer func() { test = true }() 274 defaultInputReader = bytes.NewBufferString("nope\n") 275 defaultInputWriter = new(bytes.Buffer) 276 277 tmp, cwd := testCwd(t) 278 defer testFixCwd(t, tmp, cwd) 279 280 // Create remote state file, this should be pulled 281 conf, srv := testRemoteState(t, testState(), 200) 282 defer srv.Close() 283 284 // Persist local remote state 285 s := terraform.NewState() 286 s.Serial = 5 287 s.Remote = conf 288 testStateFileRemote(t, s) 289 290 // Path where the archive will be "uploaded" to 291 archivePath := testTempFile(t) 292 defer os.Remove(archivePath) 293 294 client := &mockPushClient{File: archivePath} 295 // Provided vars should override existing ones 296 client.GetResult = map[string]atlas.TFVar{ 297 "remote": atlas.TFVar{ 298 Key: "remote", 299 Value: "old", 300 }, 301 } 302 ui := new(cli.MockUi) 303 c := &PushCommand{ 304 Meta: Meta{ 305 ContextOpts: testCtxConfig(testProvider()), 306 Ui: ui, 307 }, 308 309 client: client, 310 } 311 312 path := testFixturePath("push-tfvars") 313 args := []string{ 314 "-var-file", path + "/terraform.tfvars", 315 "-vcs=false", 316 "-overwrite=remote", 317 "-var", 318 "remote=new", 319 path, 320 } 321 322 if code := c.Run(args); code != 0 { 323 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 324 } 325 326 actual := testArchiveStr(t, archivePath) 327 expected := []string{ 328 ".terraform/", 329 ".terraform/terraform.tfstate", 330 "main.tf", 331 "terraform.tfvars", 332 } 333 if !reflect.DeepEqual(actual, expected) { 334 t.Fatalf("bad: %#v", actual) 335 } 336 337 if client.UpsertOptions.Name != "foo" { 338 t.Fatalf("bad: %#v", client.UpsertOptions) 339 } 340 341 found := false 342 // find the "remote" var and make sure we're going to set it 343 for _, tfVar := range client.UpsertOptions.TFVars { 344 if tfVar.Key == "remote" { 345 found = true 346 if tfVar.Value != "new" { 347 t.Log("'remote' variable should be set to 'new'") 348 t.Fatalf("sending instead: %#v", tfVar) 349 } 350 } 351 } 352 353 if !found { 354 t.Fatal("'remote' variable not being sent to atlas") 355 } 356 } 357 358 // This tests that the push command prefers Atlas variables over 359 // local ones. 360 func TestPush_preferAtlas(t *testing.T) { 361 // Disable test mode so input would be asked and setup the 362 // input reader/writers. 363 test = false 364 defer func() { test = true }() 365 defaultInputReader = bytes.NewBufferString("nope\n") 366 defaultInputWriter = new(bytes.Buffer) 367 368 tmp, cwd := testCwd(t) 369 defer testFixCwd(t, tmp, cwd) 370 371 // Create remote state file, this should be pulled 372 conf, srv := testRemoteState(t, testState(), 200) 373 defer srv.Close() 374 375 // Persist local remote state 376 s := terraform.NewState() 377 s.Serial = 5 378 s.Remote = conf 379 testStateFileRemote(t, s) 380 381 // Path where the archive will be "uploaded" to 382 archivePath := testTempFile(t) 383 defer os.Remove(archivePath) 384 385 client := &mockPushClient{File: archivePath} 386 // Provided vars should override existing ones 387 client.GetResult = map[string]atlas.TFVar{ 388 "foo": atlas.TFVar{ 389 Key: "foo", 390 Value: "old", 391 }, 392 } 393 ui := new(cli.MockUi) 394 c := &PushCommand{ 395 Meta: Meta{ 396 ContextOpts: testCtxConfig(testProvider()), 397 Ui: ui, 398 }, 399 400 client: client, 401 } 402 403 path := testFixturePath("push-tfvars") 404 args := []string{ 405 "-var-file", path + "/terraform.tfvars", 406 "-vcs=false", 407 path, 408 } 409 if code := c.Run(args); code != 0 { 410 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 411 } 412 413 actual := testArchiveStr(t, archivePath) 414 expected := []string{ 415 ".terraform/", 416 ".terraform/terraform.tfstate", 417 "main.tf", 418 "terraform.tfvars", 419 } 420 if !reflect.DeepEqual(actual, expected) { 421 t.Fatalf("bad: %#v", actual) 422 } 423 424 if client.UpsertOptions.Name != "foo" { 425 t.Fatalf("bad: %#v", client.UpsertOptions) 426 } 427 428 // change the expected response to match our change 429 expectedTFVars := pushTFVars() 430 for i, v := range expectedTFVars { 431 if v.Key == "foo" { 432 expectedTFVars[i] = atlas.TFVar{Key: "foo", Value: "old"} 433 } 434 } 435 436 if !reflect.DeepEqual(expectedTFVars, client.UpsertOptions.TFVars) { 437 t.Logf("expected: %#v", expectedTFVars) 438 t.Fatalf("got: %#v", client.UpsertOptions.TFVars) 439 } 440 } 441 442 // This tests that the push command will send the variables in tfvars 443 func TestPush_tfvars(t *testing.T) { 444 // Disable test mode so input would be asked and setup the 445 // input reader/writers. 446 test = false 447 defer func() { test = true }() 448 defaultInputReader = bytes.NewBufferString("nope\n") 449 defaultInputWriter = new(bytes.Buffer) 450 451 tmp, cwd := testCwd(t) 452 defer testFixCwd(t, tmp, cwd) 453 454 // Create remote state file, this should be pulled 455 conf, srv := testRemoteState(t, testState(), 200) 456 defer srv.Close() 457 458 // Persist local remote state 459 s := terraform.NewState() 460 s.Serial = 5 461 s.Remote = conf 462 testStateFileRemote(t, s) 463 464 // Path where the archive will be "uploaded" to 465 archivePath := testTempFile(t) 466 defer os.Remove(archivePath) 467 468 client := &mockPushClient{File: archivePath} 469 ui := new(cli.MockUi) 470 c := &PushCommand{ 471 Meta: Meta{ 472 ContextOpts: testCtxConfig(testProvider()), 473 Ui: ui, 474 }, 475 476 client: client, 477 } 478 479 path := testFixturePath("push-tfvars") 480 args := []string{ 481 "-var-file", path + "/terraform.tfvars", 482 "-vcs=false", 483 "-var", 484 "bar=1", 485 path, 486 } 487 if code := c.Run(args); code != 0 { 488 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 489 } 490 491 actual := testArchiveStr(t, archivePath) 492 expected := []string{ 493 ".terraform/", 494 ".terraform/terraform.tfstate", 495 "main.tf", 496 "terraform.tfvars", 497 } 498 if !reflect.DeepEqual(actual, expected) { 499 t.Fatalf("bad: %#v", actual) 500 } 501 502 if client.UpsertOptions.Name != "foo" { 503 t.Fatalf("bad: %#v", client.UpsertOptions) 504 } 505 506 //now check TFVars 507 tfvars := pushTFVars() 508 // update bar to match cli value 509 for i, v := range tfvars { 510 if v.Key == "bar" { 511 tfvars[i].Value = "1" 512 tfvars[i].IsHCL = true 513 } 514 } 515 516 for i, expected := range tfvars { 517 got := client.UpsertOptions.TFVars[i] 518 if got != expected { 519 t.Logf("%2d expected: %#v", i, expected) 520 t.Fatalf(" got: %#v", got) 521 } 522 } 523 } 524 525 func TestPush_name(t *testing.T) { 526 tmp, cwd := testCwd(t) 527 defer testFixCwd(t, tmp, cwd) 528 529 // Create remote state file, this should be pulled 530 conf, srv := testRemoteState(t, testState(), 200) 531 defer srv.Close() 532 533 // Persist local remote state 534 s := terraform.NewState() 535 s.Serial = 5 536 s.Remote = conf 537 testStateFileRemote(t, s) 538 539 // Path where the archive will be "uploaded" to 540 archivePath := testTempFile(t) 541 defer os.Remove(archivePath) 542 543 client := &mockPushClient{File: archivePath} 544 ui := new(cli.MockUi) 545 c := &PushCommand{ 546 Meta: Meta{ 547 ContextOpts: testCtxConfig(testProvider()), 548 Ui: ui, 549 }, 550 551 client: client, 552 } 553 554 args := []string{ 555 "-name", "bar", 556 "-vcs=false", 557 testFixturePath("push"), 558 } 559 if code := c.Run(args); code != 0 { 560 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 561 } 562 563 if client.UpsertOptions.Name != "bar" { 564 t.Fatalf("bad: %#v", client.UpsertOptions) 565 } 566 } 567 568 func TestPush_noState(t *testing.T) { 569 tmp, cwd := testCwd(t) 570 defer testFixCwd(t, tmp, cwd) 571 572 ui := new(cli.MockUi) 573 c := &PushCommand{ 574 Meta: Meta{ 575 ContextOpts: testCtxConfig(testProvider()), 576 Ui: ui, 577 }, 578 } 579 580 args := []string{} 581 if code := c.Run(args); code != 1 { 582 t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) 583 } 584 } 585 586 func TestPush_noRemoteState(t *testing.T) { 587 state := &terraform.State{ 588 Modules: []*terraform.ModuleState{ 589 &terraform.ModuleState{ 590 Path: []string{"root"}, 591 Resources: map[string]*terraform.ResourceState{ 592 "test_instance.foo": &terraform.ResourceState{ 593 Type: "test_instance", 594 Primary: &terraform.InstanceState{ 595 ID: "bar", 596 }, 597 }, 598 }, 599 }, 600 }, 601 } 602 statePath := testStateFile(t, state) 603 604 ui := new(cli.MockUi) 605 c := &PushCommand{ 606 Meta: Meta{ 607 Ui: ui, 608 }, 609 } 610 611 args := []string{ 612 "-state", statePath, 613 } 614 if code := c.Run(args); code != 1 { 615 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 616 } 617 } 618 619 func TestPush_plan(t *testing.T) { 620 tmp, cwd := testCwd(t) 621 defer testFixCwd(t, tmp, cwd) 622 623 // Create remote state file, this should be pulled 624 conf, srv := testRemoteState(t, testState(), 200) 625 defer srv.Close() 626 627 // Persist local remote state 628 s := terraform.NewState() 629 s.Serial = 5 630 s.Remote = conf 631 testStateFileRemote(t, s) 632 633 // Create a plan 634 planPath := testPlanFile(t, &terraform.Plan{ 635 Module: testModule(t, "apply"), 636 }) 637 638 ui := new(cli.MockUi) 639 c := &PushCommand{ 640 Meta: Meta{ 641 ContextOpts: testCtxConfig(testProvider()), 642 Ui: ui, 643 }, 644 } 645 646 args := []string{planPath} 647 if code := c.Run(args); code != 1 { 648 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 649 } 650 } 651 652 func testArchiveStr(t *testing.T, path string) []string { 653 f, err := os.Open(path) 654 if err != nil { 655 t.Fatalf("err: %s", err) 656 } 657 defer f.Close() 658 659 // Ungzip 660 gzipR, err := gzip.NewReader(f) 661 if err != nil { 662 t.Fatalf("err: %s", err) 663 } 664 665 // Accumulator 666 result := make([]string, 0, 10) 667 668 // Untar 669 tarR := tar.NewReader(gzipR) 670 for { 671 header, err := tarR.Next() 672 if err == io.EOF { 673 break 674 } 675 if err != nil { 676 t.Fatalf("err: %s", err) 677 } 678 679 result = append(result, header.Name) 680 } 681 682 sort.Strings(result) 683 return result 684 } 685 686 func pushTFVars() []atlas.TFVar { 687 return []atlas.TFVar{ 688 {"bar", "foo", false}, 689 {"baz", `{ 690 A = "a" 691 interp = "${file("t.txt")}" 692 }`, true}, 693 {"fob", `["a", "quotes \"in\" quotes"]`, true}, 694 {"foo", "bar", false}, 695 } 696 } 697 698 // the structure returned from the push-tfvars test fixture 699 func pushTFVarsMap() map[string]atlas.TFVar { 700 vars := make(map[string]atlas.TFVar) 701 for _, v := range pushTFVars() { 702 vars[v.Key] = v 703 } 704 return vars 705 }