github.com/paybyphone/terraform@v0.9.5-0.20170613192930-9706042ddd51/command/apply_test.go (about) 1 package command 2 3 import ( 4 "bytes" 5 "fmt" 6 "io/ioutil" 7 "log" 8 "net" 9 "net/http" 10 "net/url" 11 "os" 12 "path/filepath" 13 "reflect" 14 "strings" 15 "sync" 16 "testing" 17 "time" 18 19 "github.com/hashicorp/terraform/state" 20 "github.com/hashicorp/terraform/terraform" 21 "github.com/mitchellh/cli" 22 ) 23 24 func TestApply(t *testing.T) { 25 statePath := testTempFile(t) 26 27 p := testProvider() 28 ui := new(cli.MockUi) 29 c := &ApplyCommand{ 30 Meta: Meta{ 31 testingOverrides: metaOverridesForProvider(p), 32 Ui: ui, 33 }, 34 } 35 36 args := []string{ 37 "-state", statePath, 38 testFixturePath("apply"), 39 } 40 if code := c.Run(args); code != 0 { 41 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 42 } 43 44 if _, err := os.Stat(statePath); err != nil { 45 t.Fatalf("err: %s", err) 46 } 47 48 f, err := os.Open(statePath) 49 if err != nil { 50 t.Fatalf("err: %s", err) 51 } 52 defer f.Close() 53 54 state, err := terraform.ReadState(f) 55 if err != nil { 56 t.Fatalf("err: %s", err) 57 } 58 if state == nil { 59 t.Fatal("state should not be nil") 60 } 61 } 62 63 // test apply with locked state 64 func TestApply_lockedState(t *testing.T) { 65 statePath := testTempFile(t) 66 67 unlock, err := testLockState("./testdata", statePath) 68 if err != nil { 69 t.Fatal(err) 70 } 71 defer unlock() 72 73 p := testProvider() 74 ui := new(cli.MockUi) 75 c := &ApplyCommand{ 76 Meta: Meta{ 77 testingOverrides: metaOverridesForProvider(p), 78 Ui: ui, 79 }, 80 } 81 82 args := []string{ 83 "-state", statePath, 84 testFixturePath("apply"), 85 } 86 if code := c.Run(args); code == 0 { 87 t.Fatal("expected error") 88 } 89 90 output := ui.ErrorWriter.String() 91 if !strings.Contains(output, "lock") { 92 t.Fatal("command output does not look like a lock error:", output) 93 } 94 } 95 96 // test apply with locked state, waiting for unlock 97 func TestApply_lockedStateWait(t *testing.T) { 98 statePath := testTempFile(t) 99 100 unlock, err := testLockState("./testdata", statePath) 101 if err != nil { 102 t.Fatal(err) 103 } 104 105 // unlock during apply 106 go func() { 107 time.Sleep(500 * time.Millisecond) 108 unlock() 109 }() 110 111 p := testProvider() 112 ui := new(cli.MockUi) 113 c := &ApplyCommand{ 114 Meta: Meta{ 115 testingOverrides: metaOverridesForProvider(p), 116 Ui: ui, 117 }, 118 } 119 120 // wait 4s just in case the lock process doesn't release in under a second, 121 // and we want our context to be alive for a second retry at the 3s mark. 122 args := []string{ 123 "-state", statePath, 124 "-lock-timeout", "4s", 125 testFixturePath("apply"), 126 } 127 if code := c.Run(args); code != 0 { 128 log.Fatalf("lock should have succeed in less than 3s: %s", ui.ErrorWriter) 129 } 130 } 131 132 // high water mark counter 133 type hwm struct { 134 sync.Mutex 135 val int 136 max int 137 } 138 139 func (t *hwm) Inc() { 140 t.Lock() 141 defer t.Unlock() 142 t.val++ 143 if t.val > t.max { 144 t.max = t.val 145 } 146 } 147 148 func (t *hwm) Dec() { 149 t.Lock() 150 defer t.Unlock() 151 t.val-- 152 } 153 154 func (t *hwm) Max() int { 155 t.Lock() 156 defer t.Unlock() 157 return t.max 158 } 159 160 func TestApply_parallelism(t *testing.T) { 161 provider := testProvider() 162 statePath := testTempFile(t) 163 164 par := 4 165 166 // This blocks all the appy functions. We close it when we exit so 167 // they end quickly after this test finishes. 168 block := make(chan struct{}) 169 // signal how many goroutines have started 170 started := make(chan int, 100) 171 172 runCount := &hwm{} 173 174 provider.ApplyFn = func( 175 i *terraform.InstanceInfo, 176 s *terraform.InstanceState, 177 d *terraform.InstanceDiff) (*terraform.InstanceState, error) { 178 // Increment so we're counting parallelism 179 started <- 1 180 runCount.Inc() 181 defer runCount.Dec() 182 // Block here to stage up our max number of parallel instances 183 <-block 184 185 return nil, nil 186 } 187 188 ui := new(cli.MockUi) 189 c := &ApplyCommand{ 190 Meta: Meta{ 191 testingOverrides: metaOverridesForProvider(provider), 192 Ui: ui, 193 }, 194 } 195 196 args := []string{ 197 "-state", statePath, 198 fmt.Sprintf("-parallelism=%d", par), 199 testFixturePath("parallelism"), 200 } 201 202 // Run in a goroutine. We can get any errors from the ui.OutputWriter 203 doneCh := make(chan int, 1) 204 go func() { 205 doneCh <- c.Run(args) 206 }() 207 208 timeout := time.After(5 * time.Second) 209 210 // ensure things are running 211 for i := 0; i < par; i++ { 212 select { 213 case <-timeout: 214 t.Fatal("timeout waiting for all goroutines to start") 215 case <-started: 216 } 217 } 218 219 // a little extra sleep, since we can't ensure all goroutines from the walk have 220 // really started 221 time.Sleep(100 * time.Millisecond) 222 close(block) 223 224 select { 225 case res := <-doneCh: 226 if res != 0 { 227 t.Fatal(ui.OutputWriter.String()) 228 } 229 case <-timeout: 230 t.Fatal("timeout waiting from Run()") 231 } 232 233 // The total in flight should equal the parallelism 234 if runCount.Max() != par { 235 t.Fatalf("Expected parallelism: %d, got: %d", par, runCount.Max()) 236 } 237 } 238 239 func TestApply_configInvalid(t *testing.T) { 240 p := testProvider() 241 ui := new(cli.MockUi) 242 c := &ApplyCommand{ 243 Meta: Meta{ 244 testingOverrides: metaOverridesForProvider(p), 245 Ui: ui, 246 }, 247 } 248 249 args := []string{ 250 "-state", testTempFile(t), 251 testFixturePath("apply-config-invalid"), 252 } 253 if code := c.Run(args); code != 1 { 254 t.Fatalf("bad: \n%s", ui.OutputWriter.String()) 255 } 256 } 257 258 func TestApply_defaultState(t *testing.T) { 259 td, err := ioutil.TempDir("", "tf") 260 if err != nil { 261 t.Fatalf("err: %s", err) 262 } 263 statePath := filepath.Join(td, DefaultStateFilename) 264 265 // Change to the temporary directory 266 cwd, err := os.Getwd() 267 if err != nil { 268 t.Fatalf("err: %s", err) 269 } 270 if err := os.Chdir(filepath.Dir(statePath)); err != nil { 271 t.Fatalf("err: %s", err) 272 } 273 defer os.Chdir(cwd) 274 275 p := testProvider() 276 ui := new(cli.MockUi) 277 c := &ApplyCommand{ 278 Meta: Meta{ 279 testingOverrides: metaOverridesForProvider(p), 280 Ui: ui, 281 }, 282 } 283 284 args := []string{ 285 testFixturePath("apply"), 286 } 287 if code := c.Run(args); code != 0 { 288 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 289 } 290 291 if _, err := os.Stat(statePath); err != nil { 292 t.Fatalf("err: %s", err) 293 } 294 295 f, err := os.Open(statePath) 296 if err != nil { 297 t.Fatalf("err: %s", err) 298 } 299 defer f.Close() 300 301 state, err := terraform.ReadState(f) 302 if err != nil { 303 t.Fatalf("err: %s", err) 304 } 305 if state == nil { 306 t.Fatal("state should not be nil") 307 } 308 } 309 310 func TestApply_error(t *testing.T) { 311 statePath := testTempFile(t) 312 313 p := testProvider() 314 ui := new(cli.MockUi) 315 c := &ApplyCommand{ 316 Meta: Meta{ 317 testingOverrides: metaOverridesForProvider(p), 318 Ui: ui, 319 }, 320 } 321 322 var lock sync.Mutex 323 errored := false 324 p.ApplyFn = func( 325 info *terraform.InstanceInfo, 326 s *terraform.InstanceState, 327 d *terraform.InstanceDiff) (*terraform.InstanceState, error) { 328 lock.Lock() 329 defer lock.Unlock() 330 331 if !errored { 332 errored = true 333 return nil, fmt.Errorf("error") 334 } 335 336 return &terraform.InstanceState{ID: "foo"}, nil 337 } 338 p.DiffFn = func( 339 *terraform.InstanceInfo, 340 *terraform.InstanceState, 341 *terraform.ResourceConfig) (*terraform.InstanceDiff, error) { 342 return &terraform.InstanceDiff{ 343 Attributes: map[string]*terraform.ResourceAttrDiff{ 344 "ami": &terraform.ResourceAttrDiff{ 345 New: "bar", 346 }, 347 }, 348 }, nil 349 } 350 351 args := []string{ 352 "-state", statePath, 353 testFixturePath("apply-error"), 354 } 355 if code := c.Run(args); code != 1 { 356 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 357 } 358 359 if _, err := os.Stat(statePath); err != nil { 360 t.Fatalf("err: %s", err) 361 } 362 363 f, err := os.Open(statePath) 364 if err != nil { 365 t.Fatalf("err: %s", err) 366 } 367 defer f.Close() 368 369 state, err := terraform.ReadState(f) 370 if err != nil { 371 t.Fatalf("err: %s", err) 372 } 373 if state == nil { 374 t.Fatal("state should not be nil") 375 } 376 if len(state.RootModule().Resources) == 0 { 377 t.Fatal("no resources in state") 378 } 379 } 380 381 func TestApply_input(t *testing.T) { 382 // Disable test mode so input would be asked 383 test = false 384 defer func() { test = true }() 385 386 // Set some default reader/writers for the inputs 387 defaultInputReader = bytes.NewBufferString("foo\n") 388 defaultInputWriter = new(bytes.Buffer) 389 390 statePath := testTempFile(t) 391 392 p := testProvider() 393 ui := new(cli.MockUi) 394 c := &ApplyCommand{ 395 Meta: Meta{ 396 testingOverrides: metaOverridesForProvider(p), 397 Ui: ui, 398 }, 399 } 400 401 args := []string{ 402 "-state", statePath, 403 testFixturePath("apply-input"), 404 } 405 if code := c.Run(args); code != 0 { 406 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 407 } 408 409 if !p.InputCalled { 410 t.Fatal("input should be called") 411 } 412 } 413 414 // When only a partial set of the variables are set, Terraform 415 // should still ask for the unset ones by default (with -input=true) 416 func TestApply_inputPartial(t *testing.T) { 417 // Disable test mode so input would be asked 418 test = false 419 defer func() { test = true }() 420 421 // Set some default reader/writers for the inputs 422 defaultInputReader = bytes.NewBufferString("one\ntwo\n") 423 defaultInputWriter = new(bytes.Buffer) 424 425 statePath := testTempFile(t) 426 427 p := testProvider() 428 ui := new(cli.MockUi) 429 c := &ApplyCommand{ 430 Meta: Meta{ 431 testingOverrides: metaOverridesForProvider(p), 432 Ui: ui, 433 }, 434 } 435 436 args := []string{ 437 "-state", statePath, 438 "-var", "foo=foovalue", 439 testFixturePath("apply-input-partial"), 440 } 441 if code := c.Run(args); code != 0 { 442 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 443 } 444 445 expected := strings.TrimSpace(` 446 <no state> 447 Outputs: 448 449 bar = one 450 foo = foovalue 451 `) 452 testStateOutput(t, statePath, expected) 453 } 454 455 func TestApply_noArgs(t *testing.T) { 456 cwd, err := os.Getwd() 457 if err != nil { 458 t.Fatalf("err: %s", err) 459 } 460 if err := os.Chdir(testFixturePath("plan")); err != nil { 461 t.Fatalf("err: %s", err) 462 } 463 defer os.Chdir(cwd) 464 465 statePath := testTempFile(t) 466 467 p := testProvider() 468 ui := new(cli.MockUi) 469 c := &ApplyCommand{ 470 Meta: Meta{ 471 testingOverrides: metaOverridesForProvider(p), 472 Ui: ui, 473 }, 474 } 475 476 args := []string{ 477 "-state", statePath, 478 } 479 if code := c.Run(args); code != 0 { 480 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 481 } 482 483 if _, err := os.Stat(statePath); err != nil { 484 t.Fatalf("err: %s", err) 485 } 486 487 f, err := os.Open(statePath) 488 if err != nil { 489 t.Fatalf("err: %s", err) 490 } 491 defer f.Close() 492 493 state, err := terraform.ReadState(f) 494 if err != nil { 495 t.Fatalf("err: %s", err) 496 } 497 if state == nil { 498 t.Fatal("state should not be nil") 499 } 500 } 501 502 func TestApply_plan(t *testing.T) { 503 // Disable test mode so input would be asked 504 test = false 505 defer func() { test = true }() 506 507 // Set some default reader/writers for the inputs 508 defaultInputReader = new(bytes.Buffer) 509 defaultInputWriter = new(bytes.Buffer) 510 511 planPath := testPlanFile(t, &terraform.Plan{ 512 Module: testModule(t, "apply"), 513 }) 514 statePath := testTempFile(t) 515 516 p := testProvider() 517 ui := new(cli.MockUi) 518 c := &ApplyCommand{ 519 Meta: Meta{ 520 testingOverrides: metaOverridesForProvider(p), 521 Ui: ui, 522 }, 523 } 524 525 args := []string{ 526 "-state-out", statePath, 527 planPath, 528 } 529 if code := c.Run(args); code != 0 { 530 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 531 } 532 533 if p.InputCalled { 534 t.Fatalf("input should not be called for plans") 535 } 536 537 if _, err := os.Stat(statePath); err != nil { 538 t.Fatalf("err: %s", err) 539 } 540 541 f, err := os.Open(statePath) 542 if err != nil { 543 t.Fatalf("err: %s", err) 544 } 545 defer f.Close() 546 547 state, err := terraform.ReadState(f) 548 if err != nil { 549 t.Fatalf("err: %s", err) 550 } 551 if state == nil { 552 t.Fatal("state should not be nil") 553 } 554 } 555 556 func TestApply_plan_backup(t *testing.T) { 557 plan := testPlan(t) 558 planPath := testPlanFile(t, plan) 559 statePath := testTempFile(t) 560 backupPath := testTempFile(t) 561 562 p := testProvider() 563 ui := new(cli.MockUi) 564 c := &ApplyCommand{ 565 Meta: Meta{ 566 testingOverrides: metaOverridesForProvider(p), 567 Ui: ui, 568 }, 569 } 570 571 // create a state file that needs to be backed up 572 err := (&state.LocalState{Path: statePath}).WriteState(plan.State) 573 if err != nil { 574 t.Fatal(err) 575 } 576 args := []string{ 577 "-state-out", statePath, 578 "-backup", backupPath, 579 planPath, 580 } 581 if code := c.Run(args); code != 0 { 582 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 583 } 584 585 { 586 // Should have a backup file 587 f, err := os.Open(backupPath) 588 if err != nil { 589 t.Fatalf("err: %s", err) 590 } 591 592 _, err = terraform.ReadState(f) 593 f.Close() 594 if err != nil { 595 t.Fatalf("err: %s", err) 596 } 597 } 598 } 599 600 func TestApply_plan_noBackup(t *testing.T) { 601 planPath := testPlanFile(t, testPlan(t)) 602 statePath := testTempFile(t) 603 604 p := testProvider() 605 ui := new(cli.MockUi) 606 c := &ApplyCommand{ 607 Meta: Meta{ 608 testingOverrides: metaOverridesForProvider(p), 609 Ui: ui, 610 }, 611 } 612 613 args := []string{ 614 "-state-out", statePath, 615 "-backup", "-", 616 planPath, 617 } 618 if code := c.Run(args); code != 0 { 619 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 620 } 621 622 // Ensure there is no backup 623 _, err := os.Stat(statePath + DefaultBackupExtension) 624 if err == nil || !os.IsNotExist(err) { 625 t.Fatalf("backup should not exist") 626 } 627 628 // Ensure there is no literal "-" 629 _, err = os.Stat("-") 630 if err == nil || !os.IsNotExist(err) { 631 t.Fatalf("backup should not exist") 632 } 633 } 634 635 func TestApply_plan_remoteState(t *testing.T) { 636 // Disable test mode so input would be asked 637 test = false 638 defer func() { test = true }() 639 tmp, cwd := testCwd(t) 640 defer testFixCwd(t, tmp, cwd) 641 remoteStatePath := filepath.Join(tmp, DefaultDataDir, DefaultStateFilename) 642 if err := os.MkdirAll(filepath.Dir(remoteStatePath), 0755); err != nil { 643 t.Fatalf("err: %s", err) 644 } 645 646 // Set some default reader/writers for the inputs 647 defaultInputReader = new(bytes.Buffer) 648 defaultInputWriter = new(bytes.Buffer) 649 650 // Create a remote state 651 state := testState() 652 conf, srv := testRemoteState(t, state, 200) 653 defer srv.Close() 654 state.Remote = conf 655 656 planPath := testPlanFile(t, &terraform.Plan{ 657 Module: testModule(t, "apply"), 658 State: state, 659 }) 660 661 p := testProvider() 662 ui := new(cli.MockUi) 663 c := &ApplyCommand{ 664 Meta: Meta{ 665 testingOverrides: metaOverridesForProvider(p), 666 Ui: ui, 667 }, 668 } 669 670 args := []string{ 671 planPath, 672 } 673 if code := c.Run(args); code != 0 { 674 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 675 } 676 677 if p.InputCalled { 678 t.Fatalf("input should not be called for plans") 679 } 680 681 // State file should be not be installed 682 if _, err := os.Stat(filepath.Join(tmp, DefaultStateFilename)); err == nil { 683 data, _ := ioutil.ReadFile(DefaultStateFilename) 684 t.Fatalf("State path should not exist: %s", string(data)) 685 } 686 687 // Check that there is no remote state config 688 if _, err := os.Stat(remoteStatePath); err == nil { 689 t.Fatalf("has remote state config") 690 } 691 } 692 693 func TestApply_planWithVarFile(t *testing.T) { 694 varFileDir := testTempDir(t) 695 varFilePath := filepath.Join(varFileDir, "terraform.tfvars") 696 if err := ioutil.WriteFile(varFilePath, []byte(applyVarFile), 0644); err != nil { 697 t.Fatalf("err: %s", err) 698 } 699 700 planPath := testPlanFile(t, &terraform.Plan{ 701 Module: testModule(t, "apply"), 702 }) 703 statePath := testTempFile(t) 704 705 cwd, err := os.Getwd() 706 if err != nil { 707 t.Fatalf("err: %s", err) 708 } 709 if err := os.Chdir(varFileDir); err != nil { 710 t.Fatalf("err: %s", err) 711 } 712 defer os.Chdir(cwd) 713 714 p := testProvider() 715 ui := new(cli.MockUi) 716 c := &ApplyCommand{ 717 Meta: Meta{ 718 testingOverrides: metaOverridesForProvider(p), 719 Ui: ui, 720 }, 721 } 722 723 args := []string{ 724 "-state-out", statePath, 725 planPath, 726 } 727 if code := c.Run(args); code != 0 { 728 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 729 } 730 731 if _, err := os.Stat(statePath); err != nil { 732 t.Fatalf("err: %s", err) 733 } 734 735 f, err := os.Open(statePath) 736 if err != nil { 737 t.Fatalf("err: %s", err) 738 } 739 defer f.Close() 740 741 state, err := terraform.ReadState(f) 742 if err != nil { 743 t.Fatalf("err: %s", err) 744 } 745 if state == nil { 746 t.Fatal("state should not be nil") 747 } 748 } 749 750 func TestApply_planVars(t *testing.T) { 751 planPath := testPlanFile(t, &terraform.Plan{ 752 Module: testModule(t, "apply"), 753 }) 754 statePath := testTempFile(t) 755 756 p := testProvider() 757 ui := new(cli.MockUi) 758 c := &ApplyCommand{ 759 Meta: Meta{ 760 testingOverrides: metaOverridesForProvider(p), 761 Ui: ui, 762 }, 763 } 764 765 args := []string{ 766 "-state", statePath, 767 "-var", "foo=bar", 768 planPath, 769 } 770 if code := c.Run(args); code == 0 { 771 t.Fatal("should've failed") 772 } 773 } 774 775 // we should be able to apply a plan file with no other file dependencies 776 func TestApply_planNoModuleFiles(t *testing.T) { 777 // temporary data directory which we can remove between commands 778 td, err := ioutil.TempDir("", "tf") 779 if err != nil { 780 t.Fatal(err) 781 } 782 defer os.RemoveAll(td) 783 784 defer testChdir(t, td)() 785 786 p := testProvider() 787 planFile := testPlanFile(t, &terraform.Plan{ 788 Module: testModule(t, "apply-plan-no-module"), 789 }) 790 791 apply := &ApplyCommand{ 792 Meta: Meta{ 793 testingOverrides: metaOverridesForProvider(p), 794 Ui: new(cli.MockUi), 795 }, 796 } 797 args := []string{ 798 planFile, 799 } 800 apply.Run(args) 801 if p.ValidateCalled { 802 t.Fatal("Validate should not be called with a plan") 803 } 804 } 805 806 func TestApply_refresh(t *testing.T) { 807 originalState := &terraform.State{ 808 Modules: []*terraform.ModuleState{ 809 &terraform.ModuleState{ 810 Path: []string{"root"}, 811 Resources: map[string]*terraform.ResourceState{ 812 "test_instance.foo": &terraform.ResourceState{ 813 Type: "test_instance", 814 Primary: &terraform.InstanceState{ 815 ID: "bar", 816 }, 817 }, 818 }, 819 }, 820 }, 821 } 822 823 statePath := testStateFile(t, originalState) 824 825 p := testProvider() 826 ui := new(cli.MockUi) 827 c := &ApplyCommand{ 828 Meta: Meta{ 829 testingOverrides: metaOverridesForProvider(p), 830 Ui: ui, 831 }, 832 } 833 834 args := []string{ 835 "-state", statePath, 836 testFixturePath("apply"), 837 } 838 if code := c.Run(args); code != 0 { 839 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 840 } 841 842 if !p.RefreshCalled { 843 t.Fatal("should call refresh") 844 } 845 846 if _, err := os.Stat(statePath); err != nil { 847 t.Fatalf("err: %s", err) 848 } 849 850 f, err := os.Open(statePath) 851 if err != nil { 852 t.Fatalf("err: %s", err) 853 } 854 defer f.Close() 855 856 state, err := terraform.ReadState(f) 857 if err != nil { 858 t.Fatalf("err: %s", err) 859 } 860 if state == nil { 861 t.Fatal("state should not be nil") 862 } 863 864 // Should have a backup file 865 f, err = os.Open(statePath + DefaultBackupExtension) 866 if err != nil { 867 t.Fatalf("err: %s", err) 868 } 869 870 backupState, err := terraform.ReadState(f) 871 f.Close() 872 if err != nil { 873 t.Fatalf("err: %s", err) 874 } 875 876 actualStr := strings.TrimSpace(backupState.String()) 877 expectedStr := strings.TrimSpace(originalState.String()) 878 if actualStr != expectedStr { 879 t.Fatalf("bad:\n\n%s\n\n%s", actualStr, expectedStr) 880 } 881 } 882 883 func TestApply_shutdown(t *testing.T) { 884 stopped := false 885 stopCh := make(chan struct{}) 886 stopReplyCh := make(chan struct{}) 887 888 statePath := testTempFile(t) 889 890 p := testProvider() 891 shutdownCh := make(chan struct{}) 892 ui := new(cli.MockUi) 893 c := &ApplyCommand{ 894 Meta: Meta{ 895 testingOverrides: metaOverridesForProvider(p), 896 Ui: ui, 897 }, 898 899 ShutdownCh: shutdownCh, 900 } 901 902 p.DiffFn = func( 903 *terraform.InstanceInfo, 904 *terraform.InstanceState, 905 *terraform.ResourceConfig) (*terraform.InstanceDiff, error) { 906 return &terraform.InstanceDiff{ 907 Attributes: map[string]*terraform.ResourceAttrDiff{ 908 "ami": &terraform.ResourceAttrDiff{ 909 New: "bar", 910 }, 911 }, 912 }, nil 913 } 914 p.ApplyFn = func( 915 *terraform.InstanceInfo, 916 *terraform.InstanceState, 917 *terraform.InstanceDiff) (*terraform.InstanceState, error) { 918 if !stopped { 919 stopped = true 920 close(stopCh) 921 <-stopReplyCh 922 } 923 924 return &terraform.InstanceState{ 925 ID: "foo", 926 Attributes: map[string]string{ 927 "ami": "2", 928 }, 929 }, nil 930 } 931 932 go func() { 933 <-stopCh 934 shutdownCh <- struct{}{} 935 936 // This is really dirty, but we have no other way to assure that 937 // tf.Stop() has been called. This doesn't assure it either, but 938 // it makes it much more certain. 939 time.Sleep(50 * time.Millisecond) 940 941 close(stopReplyCh) 942 }() 943 944 args := []string{ 945 "-state", statePath, 946 testFixturePath("apply-shutdown"), 947 } 948 if code := c.Run(args); code != 0 { 949 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 950 } 951 952 if _, err := os.Stat(statePath); err != nil { 953 t.Fatalf("err: %s", err) 954 } 955 956 f, err := os.Open(statePath) 957 if err != nil { 958 t.Fatalf("err: %s", err) 959 } 960 defer f.Close() 961 962 state, err := terraform.ReadState(f) 963 if err != nil { 964 t.Fatalf("err: %s", err) 965 } 966 if state == nil { 967 t.Fatal("state should not be nil") 968 } 969 970 if len(state.RootModule().Resources) != 1 { 971 t.Fatalf("bad: %d", len(state.RootModule().Resources)) 972 } 973 } 974 975 func TestApply_state(t *testing.T) { 976 originalState := &terraform.State{ 977 Modules: []*terraform.ModuleState{ 978 &terraform.ModuleState{ 979 Path: []string{"root"}, 980 Resources: map[string]*terraform.ResourceState{ 981 "test_instance.foo": &terraform.ResourceState{ 982 Type: "test_instance", 983 Primary: &terraform.InstanceState{ 984 ID: "bar", 985 }, 986 }, 987 }, 988 }, 989 }, 990 } 991 992 statePath := testStateFile(t, originalState) 993 994 p := testProvider() 995 p.DiffReturn = &terraform.InstanceDiff{ 996 Attributes: map[string]*terraform.ResourceAttrDiff{ 997 "ami": &terraform.ResourceAttrDiff{ 998 New: "bar", 999 }, 1000 }, 1001 } 1002 1003 ui := new(cli.MockUi) 1004 c := &ApplyCommand{ 1005 Meta: Meta{ 1006 testingOverrides: metaOverridesForProvider(p), 1007 Ui: ui, 1008 }, 1009 } 1010 1011 // Run the apply command pointing to our existing state 1012 args := []string{ 1013 "-state", statePath, 1014 testFixturePath("apply"), 1015 } 1016 if code := c.Run(args); code != 0 { 1017 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 1018 } 1019 1020 // Verify that the provider was called with the existing state 1021 actual := strings.TrimSpace(p.DiffState.String()) 1022 expected := strings.TrimSpace(testApplyStateDiffStr) 1023 if actual != expected { 1024 t.Fatalf("bad:\n\n%s", actual) 1025 } 1026 1027 actual = strings.TrimSpace(p.ApplyState.String()) 1028 expected = strings.TrimSpace(testApplyStateStr) 1029 if actual != expected { 1030 t.Fatalf("bad:\n\n%s", actual) 1031 } 1032 1033 // Verify a new state exists 1034 if _, err := os.Stat(statePath); err != nil { 1035 t.Fatalf("err: %s", err) 1036 } 1037 1038 f, err := os.Open(statePath) 1039 if err != nil { 1040 t.Fatalf("err: %s", err) 1041 } 1042 defer f.Close() 1043 1044 state, err := terraform.ReadState(f) 1045 if err != nil { 1046 t.Fatalf("err: %s", err) 1047 } 1048 if state == nil { 1049 t.Fatal("state should not be nil") 1050 } 1051 1052 // Should have a backup file 1053 f, err = os.Open(statePath + DefaultBackupExtension) 1054 if err != nil { 1055 t.Fatalf("err: %s", err) 1056 } 1057 1058 backupState, err := terraform.ReadState(f) 1059 f.Close() 1060 if err != nil { 1061 t.Fatalf("err: %s", err) 1062 } 1063 1064 // nil out the ConnInfo since that should not be restored 1065 originalState.RootModule().Resources["test_instance.foo"].Primary.Ephemeral.ConnInfo = nil 1066 1067 actualStr := strings.TrimSpace(backupState.String()) 1068 expectedStr := strings.TrimSpace(originalState.String()) 1069 if actualStr != expectedStr { 1070 t.Fatalf("bad:\n\n%s\n\n%s", actualStr, expectedStr) 1071 } 1072 } 1073 1074 func TestApply_stateNoExist(t *testing.T) { 1075 p := testProvider() 1076 ui := new(cli.MockUi) 1077 c := &ApplyCommand{ 1078 Meta: Meta{ 1079 testingOverrides: metaOverridesForProvider(p), 1080 Ui: ui, 1081 }, 1082 } 1083 1084 args := []string{ 1085 "idontexist.tfstate", 1086 testFixturePath("apply"), 1087 } 1088 if code := c.Run(args); code != 1 { 1089 t.Fatalf("bad: \n%s", ui.OutputWriter.String()) 1090 } 1091 } 1092 1093 func TestApply_sensitiveOutput(t *testing.T) { 1094 p := testProvider() 1095 ui := new(cli.MockUi) 1096 c := &ApplyCommand{ 1097 Meta: Meta{ 1098 testingOverrides: metaOverridesForProvider(p), 1099 Ui: ui, 1100 }, 1101 } 1102 1103 statePath := testTempFile(t) 1104 1105 args := []string{ 1106 "-state", statePath, 1107 testFixturePath("apply-sensitive-output"), 1108 } 1109 1110 if code := c.Run(args); code != 0 { 1111 t.Fatalf("bad: \n%s", ui.OutputWriter.String()) 1112 } 1113 1114 output := ui.OutputWriter.String() 1115 if !strings.Contains(output, "notsensitive = Hello world") { 1116 t.Fatalf("bad: output should contain 'notsensitive' output\n%s", output) 1117 } 1118 if !strings.Contains(output, "sensitive = <sensitive>") { 1119 t.Fatalf("bad: output should contain 'sensitive' output\n%s", output) 1120 } 1121 } 1122 1123 func TestApply_stateFuture(t *testing.T) { 1124 originalState := testState() 1125 originalState.TFVersion = "99.99.99" 1126 statePath := testStateFile(t, originalState) 1127 1128 p := testProvider() 1129 ui := new(cli.MockUi) 1130 c := &ApplyCommand{ 1131 Meta: Meta{ 1132 testingOverrides: metaOverridesForProvider(p), 1133 Ui: ui, 1134 }, 1135 } 1136 1137 args := []string{ 1138 "-state", statePath, 1139 testFixturePath("apply"), 1140 } 1141 if code := c.Run(args); code == 0 { 1142 t.Fatal("should fail") 1143 } 1144 1145 f, err := os.Open(statePath) 1146 if err != nil { 1147 t.Fatalf("err: %s", err) 1148 } 1149 1150 newState, err := terraform.ReadState(f) 1151 f.Close() 1152 if err != nil { 1153 t.Fatalf("err: %s", err) 1154 } 1155 1156 if !newState.Equal(originalState) { 1157 t.Fatalf("bad: %#v", newState) 1158 } 1159 if newState.TFVersion != originalState.TFVersion { 1160 t.Fatalf("bad: %#v", newState) 1161 } 1162 } 1163 1164 func TestApply_statePast(t *testing.T) { 1165 originalState := testState() 1166 originalState.TFVersion = "0.1.0" 1167 statePath := testStateFile(t, originalState) 1168 1169 p := testProvider() 1170 ui := new(cli.MockUi) 1171 c := &ApplyCommand{ 1172 Meta: Meta{ 1173 testingOverrides: metaOverridesForProvider(p), 1174 Ui: ui, 1175 }, 1176 } 1177 1178 args := []string{ 1179 "-state", statePath, 1180 testFixturePath("apply"), 1181 } 1182 if code := c.Run(args); code != 0 { 1183 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 1184 } 1185 } 1186 1187 func TestApply_vars(t *testing.T) { 1188 statePath := testTempFile(t) 1189 1190 p := testProvider() 1191 ui := new(cli.MockUi) 1192 c := &ApplyCommand{ 1193 Meta: Meta{ 1194 testingOverrides: metaOverridesForProvider(p), 1195 Ui: ui, 1196 }, 1197 } 1198 1199 actual := "" 1200 p.DiffFn = func( 1201 info *terraform.InstanceInfo, 1202 s *terraform.InstanceState, 1203 c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) { 1204 if v, ok := c.Config["value"]; ok { 1205 actual = v.(string) 1206 } 1207 1208 return &terraform.InstanceDiff{}, nil 1209 } 1210 1211 args := []string{ 1212 "-var", "foo=bar", 1213 "-state", statePath, 1214 testFixturePath("apply-vars"), 1215 } 1216 if code := c.Run(args); code != 0 { 1217 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 1218 } 1219 1220 if actual != "bar" { 1221 t.Fatal("didn't work") 1222 } 1223 } 1224 1225 func TestApply_varFile(t *testing.T) { 1226 varFilePath := testTempFile(t) 1227 if err := ioutil.WriteFile(varFilePath, []byte(applyVarFile), 0644); err != nil { 1228 t.Fatalf("err: %s", err) 1229 } 1230 1231 statePath := testTempFile(t) 1232 1233 p := testProvider() 1234 ui := new(cli.MockUi) 1235 c := &ApplyCommand{ 1236 Meta: Meta{ 1237 testingOverrides: metaOverridesForProvider(p), 1238 Ui: ui, 1239 }, 1240 } 1241 1242 actual := "" 1243 p.DiffFn = func( 1244 info *terraform.InstanceInfo, 1245 s *terraform.InstanceState, 1246 c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) { 1247 if v, ok := c.Config["value"]; ok { 1248 actual = v.(string) 1249 } 1250 1251 return &terraform.InstanceDiff{}, nil 1252 } 1253 1254 args := []string{ 1255 "-var-file", varFilePath, 1256 "-state", statePath, 1257 testFixturePath("apply-vars"), 1258 } 1259 if code := c.Run(args); code != 0 { 1260 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 1261 } 1262 1263 if actual != "bar" { 1264 t.Fatal("didn't work") 1265 } 1266 } 1267 1268 func TestApply_varFileDefault(t *testing.T) { 1269 varFileDir := testTempDir(t) 1270 varFilePath := filepath.Join(varFileDir, "terraform.tfvars") 1271 if err := ioutil.WriteFile(varFilePath, []byte(applyVarFile), 0644); err != nil { 1272 t.Fatalf("err: %s", err) 1273 } 1274 1275 statePath := testTempFile(t) 1276 1277 cwd, err := os.Getwd() 1278 if err != nil { 1279 t.Fatalf("err: %s", err) 1280 } 1281 if err := os.Chdir(varFileDir); err != nil { 1282 t.Fatalf("err: %s", err) 1283 } 1284 defer os.Chdir(cwd) 1285 1286 p := testProvider() 1287 ui := new(cli.MockUi) 1288 c := &ApplyCommand{ 1289 Meta: Meta{ 1290 testingOverrides: metaOverridesForProvider(p), 1291 Ui: ui, 1292 }, 1293 } 1294 1295 actual := "" 1296 p.DiffFn = func( 1297 info *terraform.InstanceInfo, 1298 s *terraform.InstanceState, 1299 c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) { 1300 if v, ok := c.Config["value"]; ok { 1301 actual = v.(string) 1302 } 1303 1304 return &terraform.InstanceDiff{}, nil 1305 } 1306 1307 args := []string{ 1308 "-state", statePath, 1309 testFixturePath("apply-vars"), 1310 } 1311 if code := c.Run(args); code != 0 { 1312 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 1313 } 1314 1315 if actual != "bar" { 1316 t.Fatal("didn't work") 1317 } 1318 } 1319 1320 func TestApply_varFileDefaultJSON(t *testing.T) { 1321 varFileDir := testTempDir(t) 1322 varFilePath := filepath.Join(varFileDir, "terraform.tfvars.json") 1323 if err := ioutil.WriteFile(varFilePath, []byte(applyVarFileJSON), 0644); err != nil { 1324 t.Fatalf("err: %s", err) 1325 } 1326 1327 statePath := testTempFile(t) 1328 1329 cwd, err := os.Getwd() 1330 if err != nil { 1331 t.Fatalf("err: %s", err) 1332 } 1333 if err := os.Chdir(varFileDir); err != nil { 1334 t.Fatalf("err: %s", err) 1335 } 1336 defer os.Chdir(cwd) 1337 1338 p := testProvider() 1339 ui := new(cli.MockUi) 1340 c := &ApplyCommand{ 1341 Meta: Meta{ 1342 testingOverrides: metaOverridesForProvider(p), 1343 Ui: ui, 1344 }, 1345 } 1346 1347 actual := "" 1348 p.DiffFn = func( 1349 info *terraform.InstanceInfo, 1350 s *terraform.InstanceState, 1351 c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) { 1352 if v, ok := c.Config["value"]; ok { 1353 actual = v.(string) 1354 } 1355 1356 return &terraform.InstanceDiff{}, nil 1357 } 1358 1359 args := []string{ 1360 "-state", statePath, 1361 testFixturePath("apply-vars"), 1362 } 1363 if code := c.Run(args); code != 0 { 1364 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 1365 } 1366 1367 if actual != "bar" { 1368 t.Fatal("didn't work") 1369 } 1370 } 1371 1372 func TestApply_backup(t *testing.T) { 1373 originalState := &terraform.State{ 1374 Modules: []*terraform.ModuleState{ 1375 &terraform.ModuleState{ 1376 Path: []string{"root"}, 1377 Resources: map[string]*terraform.ResourceState{ 1378 "test_instance.foo": &terraform.ResourceState{ 1379 Type: "test_instance", 1380 Primary: &terraform.InstanceState{ 1381 ID: "bar", 1382 }, 1383 }, 1384 }, 1385 }, 1386 }, 1387 } 1388 originalState.Init() 1389 1390 statePath := testStateFile(t, originalState) 1391 backupPath := testTempFile(t) 1392 1393 p := testProvider() 1394 p.DiffReturn = &terraform.InstanceDiff{ 1395 Attributes: map[string]*terraform.ResourceAttrDiff{ 1396 "ami": &terraform.ResourceAttrDiff{ 1397 New: "bar", 1398 }, 1399 }, 1400 } 1401 1402 ui := new(cli.MockUi) 1403 c := &ApplyCommand{ 1404 Meta: Meta{ 1405 testingOverrides: metaOverridesForProvider(p), 1406 Ui: ui, 1407 }, 1408 } 1409 1410 // Run the apply command pointing to our existing state 1411 args := []string{ 1412 "-state", statePath, 1413 "-backup", backupPath, 1414 testFixturePath("apply"), 1415 } 1416 if code := c.Run(args); code != 0 { 1417 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 1418 } 1419 1420 // Verify a new state exists 1421 if _, err := os.Stat(statePath); err != nil { 1422 t.Fatalf("err: %s", err) 1423 } 1424 1425 f, err := os.Open(statePath) 1426 if err != nil { 1427 t.Fatalf("err: %s", err) 1428 } 1429 defer f.Close() 1430 1431 state, err := terraform.ReadState(f) 1432 if err != nil { 1433 t.Fatalf("err: %s", err) 1434 } 1435 if state == nil { 1436 t.Fatal("state should not be nil") 1437 } 1438 1439 // Should have a backup file 1440 f, err = os.Open(backupPath) 1441 if err != nil { 1442 t.Fatalf("err: %s", err) 1443 } 1444 1445 backupState, err := terraform.ReadState(f) 1446 f.Close() 1447 if err != nil { 1448 t.Fatalf("err: %s", err) 1449 } 1450 1451 actual := backupState.RootModule().Resources["test_instance.foo"] 1452 expected := originalState.RootModule().Resources["test_instance.foo"] 1453 if !reflect.DeepEqual(actual, expected) { 1454 t.Fatalf("bad: %#v %#v", actual, expected) 1455 } 1456 } 1457 1458 func TestApply_disableBackup(t *testing.T) { 1459 originalState := testState() 1460 statePath := testStateFile(t, originalState) 1461 1462 p := testProvider() 1463 p.DiffReturn = &terraform.InstanceDiff{ 1464 Attributes: map[string]*terraform.ResourceAttrDiff{ 1465 "ami": &terraform.ResourceAttrDiff{ 1466 New: "bar", 1467 }, 1468 }, 1469 } 1470 1471 ui := new(cli.MockUi) 1472 c := &ApplyCommand{ 1473 Meta: Meta{ 1474 testingOverrides: metaOverridesForProvider(p), 1475 Ui: ui, 1476 }, 1477 } 1478 1479 // Run the apply command pointing to our existing state 1480 args := []string{ 1481 "-state", statePath, 1482 "-backup", "-", 1483 testFixturePath("apply"), 1484 } 1485 if code := c.Run(args); code != 0 { 1486 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 1487 } 1488 1489 // Verify that the provider was called with the existing state 1490 actual := strings.TrimSpace(p.DiffState.String()) 1491 expected := strings.TrimSpace(testApplyDisableBackupStr) 1492 if actual != expected { 1493 t.Fatalf("bad:\n\n%s", actual) 1494 } 1495 1496 actual = strings.TrimSpace(p.ApplyState.String()) 1497 expected = strings.TrimSpace(testApplyDisableBackupStateStr) 1498 if actual != expected { 1499 t.Fatalf("bad:\n\n%s", actual) 1500 } 1501 1502 // Verify a new state exists 1503 if _, err := os.Stat(statePath); err != nil { 1504 t.Fatalf("err: %s", err) 1505 } 1506 1507 f, err := os.Open(statePath) 1508 if err != nil { 1509 t.Fatalf("err: %s", err) 1510 } 1511 defer f.Close() 1512 1513 state, err := terraform.ReadState(f) 1514 if err != nil { 1515 t.Fatalf("err: %s", err) 1516 } 1517 if state == nil { 1518 t.Fatal("state should not be nil") 1519 } 1520 1521 // Ensure there is no backup 1522 _, err = os.Stat(statePath + DefaultBackupExtension) 1523 if err == nil || !os.IsNotExist(err) { 1524 t.Fatalf("backup should not exist") 1525 } 1526 1527 // Ensure there is no literal "-" 1528 _, err = os.Stat("-") 1529 if err == nil || !os.IsNotExist(err) { 1530 t.Fatalf("backup should not exist") 1531 } 1532 } 1533 1534 // Test that the Terraform env is passed through 1535 func TestApply_terraformEnv(t *testing.T) { 1536 statePath := testTempFile(t) 1537 1538 p := testProvider() 1539 ui := new(cli.MockUi) 1540 c := &ApplyCommand{ 1541 Meta: Meta{ 1542 testingOverrides: metaOverridesForProvider(p), 1543 Ui: ui, 1544 }, 1545 } 1546 1547 args := []string{ 1548 "-state", statePath, 1549 testFixturePath("apply-terraform-env"), 1550 } 1551 if code := c.Run(args); code != 0 { 1552 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 1553 } 1554 1555 expected := strings.TrimSpace(` 1556 <no state> 1557 Outputs: 1558 1559 output = default 1560 `) 1561 testStateOutput(t, statePath, expected) 1562 } 1563 1564 // Test that the Terraform env is passed through 1565 func TestApply_terraformEnvNonDefault(t *testing.T) { 1566 // Create a temporary working directory that is empty 1567 td := tempDir(t) 1568 os.MkdirAll(td, 0755) 1569 defer os.RemoveAll(td) 1570 defer testChdir(t, td)() 1571 1572 // Create new env 1573 { 1574 ui := new(cli.MockUi) 1575 newCmd := &WorkspaceNewCommand{} 1576 newCmd.Meta = Meta{Ui: ui} 1577 if code := newCmd.Run([]string{"test"}); code != 0 { 1578 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter) 1579 } 1580 } 1581 1582 // Switch to it 1583 { 1584 args := []string{"test"} 1585 ui := new(cli.MockUi) 1586 selCmd := &WorkspaceSelectCommand{} 1587 selCmd.Meta = Meta{Ui: ui} 1588 if code := selCmd.Run(args); code != 0 { 1589 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter) 1590 } 1591 } 1592 1593 p := testProvider() 1594 ui := new(cli.MockUi) 1595 c := &ApplyCommand{ 1596 Meta: Meta{ 1597 testingOverrides: metaOverridesForProvider(p), 1598 Ui: ui, 1599 }, 1600 } 1601 1602 args := []string{ 1603 testFixturePath("apply-terraform-env"), 1604 } 1605 if code := c.Run(args); code != 0 { 1606 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 1607 } 1608 1609 statePath := filepath.Join("terraform.tfstate.d", "test", "terraform.tfstate") 1610 expected := strings.TrimSpace(` 1611 <no state> 1612 Outputs: 1613 1614 output = test 1615 `) 1616 testStateOutput(t, statePath, expected) 1617 } 1618 1619 func testHttpServer(t *testing.T) net.Listener { 1620 ln, err := net.Listen("tcp", "127.0.0.1:0") 1621 if err != nil { 1622 t.Fatalf("err: %s", err) 1623 } 1624 1625 mux := http.NewServeMux() 1626 mux.HandleFunc("/header", testHttpHandlerHeader) 1627 1628 var server http.Server 1629 server.Handler = mux 1630 go server.Serve(ln) 1631 1632 return ln 1633 } 1634 1635 func testHttpHandlerHeader(w http.ResponseWriter, r *http.Request) { 1636 var url url.URL 1637 url.Scheme = "file" 1638 url.Path = filepath.ToSlash(testFixturePath("init")) 1639 1640 w.Header().Add("X-Terraform-Get", url.String()) 1641 w.WriteHeader(200) 1642 } 1643 1644 const applyVarFile = ` 1645 foo = "bar" 1646 ` 1647 1648 const applyVarFileJSON = ` 1649 { "foo": "bar" } 1650 ` 1651 1652 const testApplyDisableBackupStr = ` 1653 ID = bar 1654 Tainted = false 1655 ` 1656 1657 const testApplyDisableBackupStateStr = ` 1658 ID = bar 1659 Tainted = false 1660 ` 1661 1662 const testApplyStateStr = ` 1663 ID = bar 1664 Tainted = false 1665 ` 1666 1667 const testApplyStateDiffStr = ` 1668 ID = bar 1669 Tainted = false 1670 `