func (f *fakeAtlas) handler(resp http.ResponseWriter, req *http.Request) { switch req.Method { case "GET": // Respond with the current stored state. resp.Header().Set("Content-Type", "application/json") resp.Write(f.state) case "PUT": var buf bytes.Buffer buf.ReadFrom(req.Body) sum := md5.Sum(buf.Bytes()) state, err := terraform.ReadState(&buf) if err != nil { f.t.Fatalf("err: %s", err) } conflict := f.CurrentSerial() == state.Serial && f.CurrentSum() != sum conflict = conflict || f.alwaysConflict if conflict { if f.noConflictAllowed { f.t.Fatal("Got conflict when NoConflictAllowed was set.") } http.Error(resp, "Conflict", 409) } else { f.state = buf.Bytes() resp.WriteHeader(200) } } }
func (f *fakeAtlas) CurrentState() *terraform.State { currentState, err := terraform.ReadState(bytes.NewReader(f.state)) if err != nil { f.t.Fatalf("err: %s", err) } return currentState }
func TestAtlasClient_LegitimateConflict(t *testing.T) { fakeAtlas := newFakeAtlas(t, testStateSimple) srv := fakeAtlas.Server() defer srv.Close() client, err := atlasFactory(map[string]string{ "access_token": "sometoken", "name": "someuser/some-test-remote-state", "address": srv.URL, }) if err != nil { t.Fatalf("err: %s", err) } state, err := terraform.ReadState(bytes.NewReader(testStateSimple)) if err != nil { t.Fatalf("err: %s", err) } // Changing the state but not the serial. Should generate a conflict. state.RootModule().Outputs["drift"] = "happens" var stateJson bytes.Buffer if err := terraform.WriteState(state, &stateJson); err != nil { t.Fatalf("err: %s", err) } if err := client.Put(stateJson.Bytes()); err == nil { t.Fatal("Expected error from state conflict, got none.") } }
func TestAtlasClient_NoConflict(t *testing.T) { fakeAtlas := newFakeAtlas(t, testStateSimple) srv := fakeAtlas.Server() defer srv.Close() client, err := atlasFactory(map[string]string{ "access_token": "sometoken", "name": "someuser/some-test-remote-state", "address": srv.URL, }) if err != nil { t.Fatalf("err: %s", err) } state, err := terraform.ReadState(bytes.NewReader(testStateSimple)) if err != nil { t.Fatalf("err: %s", err) } fakeAtlas.NoConflictAllowed(true) var stateJson bytes.Buffer if err := terraform.WriteState(state, &stateJson); err != nil { t.Fatalf("err: %s", err) } if err := client.Put(stateJson.Bytes()); err != nil { t.Fatalf("err: %s", err) } }
func TestApply_planWithVarFile(t *testing.T) { varFileDir := testTempDir(t) varFilePath := filepath.Join(varFileDir, "terraform.tfvars") if err := ioutil.WriteFile(varFilePath, []byte(applyVarFile), 0644); err != nil { t.Fatalf("err: %s", err) } planPath := testPlanFile(t, &terraform.Plan{ Module: testModule(t, "apply"), }) statePath := testTempFile(t) cwd, err := os.Getwd() if err != nil { t.Fatalf("err: %s", err) } if err := os.Chdir(varFileDir); err != nil { t.Fatalf("err: %s", err) } defer os.Chdir(cwd) p := testProvider() ui := new(cli.MockUi) c := &ApplyCommand{ Meta: Meta{ ContextOpts: testCtxConfig(p), Ui: ui, }, } args := []string{ "-state", statePath, planPath, } if code := c.Run(args); code != 0 { t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) } if _, err := os.Stat(statePath); err != nil { t.Fatalf("err: %s", err) } f, err := os.Open(statePath) if err != nil { t.Fatalf("err: %s", err) } defer f.Close() state, err := terraform.ReadState(f) if err != nil { t.Fatalf("err: %s", err) } if state == nil { t.Fatal("state should not be nil") } }
// Test disabling remote management func TestRemoteConfig_disable(t *testing.T) { tmp, cwd := testCwd(t) defer testFixCwd(t, tmp, cwd) // Create remote state file, this should be pulled s := terraform.NewState() s.Serial = 10 conf, srv := testRemoteState(t, s, 200) defer srv.Close() // Persist local remote state s = terraform.NewState() s.Serial = 5 s.Remote = conf // Write the state statePath := filepath.Join(tmp, DefaultDataDir, DefaultStateFilename) state := &state.LocalState{Path: statePath} if err := state.WriteState(s); err != nil { t.Fatalf("err: %s", err) } if err := state.PersistState(); err != nil { t.Fatalf("err: %s", err) } ui := new(cli.MockUi) c := &RemoteConfigCommand{ Meta: Meta{ ContextOpts: testCtxConfig(testProvider()), Ui: ui, }, } args := []string{"-disable"} if code := c.Run(args); code != 0 { t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) } // Local state file should be removed and the local cache should exist testRemoteLocal(t, true) testRemoteLocalCache(t, false) // Check that the state file was updated raw, _ := ioutil.ReadFile(DefaultStateFilename) newState, err := terraform.ReadState(bytes.NewReader(raw)) if err != nil { t.Fatalf("err: %v", err) } // Ensure we updated if newState.Remote != nil { t.Fatalf("remote configuration not removed") } }
func TestApply_plan(t *testing.T) { // Disable test mode so input would be asked test = false defer func() { test = true }() // Set some default reader/writers for the inputs defaultInputReader = new(bytes.Buffer) defaultInputWriter = new(bytes.Buffer) planPath := testPlanFile(t, &terraform.Plan{ Module: testModule(t, "apply"), }) statePath := testTempFile(t) p := testProvider() ui := new(cli.MockUi) c := &ApplyCommand{ Meta: Meta{ ContextOpts: testCtxConfig(p), Ui: ui, }, } args := []string{ "-state", statePath, planPath, } if code := c.Run(args); code != 0 { t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) } if p.InputCalled { t.Fatalf("input should not be called for plans") } if _, err := os.Stat(statePath); err != nil { t.Fatalf("err: %s", err) } f, err := os.Open(statePath) if err != nil { t.Fatalf("err: %s", err) } defer f.Close() state, err := terraform.ReadState(f) if err != nil { t.Fatalf("err: %s", err) } if state == nil { t.Fatal("state should not be nil") } }
func TestRefresh_cwd(t *testing.T) { cwd, err := os.Getwd() if err != nil { t.Fatalf("err: %s", err) } if err := os.Chdir(testFixturePath("refresh")); err != nil { t.Fatalf("err: %s", err) } defer os.Chdir(cwd) state := testState() statePath := testStateFile(t, state) p := testProvider() ui := new(cli.MockUi) c := &RefreshCommand{ Meta: Meta{ ContextOpts: testCtxConfig(p), Ui: ui, }, } p.RefreshFn = nil p.RefreshReturn = &terraform.InstanceState{ID: "yes"} args := []string{ "-state", statePath, } if code := c.Run(args); code != 0 { t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) } if !p.RefreshCalled { t.Fatal("refresh should be called") } f, err := os.Open(statePath) if err != nil { t.Fatalf("err: %s", err) } newState, err := terraform.ReadState(f) f.Close() if err != nil { t.Fatalf("err: %s", err) } actual := strings.TrimSpace(newState.String()) expected := strings.TrimSpace(testRefreshCwdStr) if actual != expected { t.Fatalf("bad:\n\n%s", actual) } }
// Atlas returns an HTTP 409 - Conflict if the pushed state reports the same // Serial number but the checksum of the raw content differs. This can // sometimes happen when Terraform changes state representation internally // between versions in a way that's semantically neutral but affects the JSON // output and therefore the checksum. // // Here we detect and handle this situation by ticking the serial and retrying // iff for the previous state and the proposed state: // // * the serials match // * the parsed states are Equal (semantically equivalent) // // In other words, in this situation Terraform can override Atlas's detected // conflict by asserting that the state it is pushing is indeed correct. func (c *AtlasClient) handleConflict(msg string, state []byte) error { log.Printf("[DEBUG] Handling Atlas conflict response: %s", msg) if c.conflictHandlingAttempted { log.Printf("[DEBUG] Already attempted conflict resolution; returning conflict.") } else { c.conflictHandlingAttempted = true log.Printf("[DEBUG] Atlas reported conflict, checking for equivalent states.") payload, err := c.Get() if err != nil { return conflictHandlingError(err) } currentState, err := terraform.ReadState(bytes.NewReader(payload.Data)) if err != nil { return conflictHandlingError(err) } proposedState, err := terraform.ReadState(bytes.NewReader(state)) if err != nil { return conflictHandlingError(err) } if statesAreEquivalent(currentState, proposedState) { log.Printf("[DEBUG] States are equivalent, incrementing serial and retrying.") proposedState.Serial++ var buf bytes.Buffer if err := terraform.WriteState(proposedState, &buf); err != nil { return conflictHandlingError(err) } return c.Put(buf.Bytes()) } else { log.Printf("[DEBUG] States are not equivalent, returning conflict.") } } return fmt.Errorf( "Atlas detected a remote state conflict.\n\nMessage: %s", msg) }
func TestApply_defaultState(t *testing.T) { td, err := ioutil.TempDir("", "tf") if err != nil { t.Fatalf("err: %s", err) } statePath := filepath.Join(td, DefaultStateFilename) // Change to the temporary directory cwd, err := os.Getwd() if err != nil { t.Fatalf("err: %s", err) } if err := os.Chdir(filepath.Dir(statePath)); err != nil { t.Fatalf("err: %s", err) } defer os.Chdir(cwd) p := testProvider() ui := new(cli.MockUi) c := &ApplyCommand{ Meta: Meta{ ContextOpts: testCtxConfig(p), Ui: ui, }, } args := []string{ testFixturePath("apply"), } if code := c.Run(args); code != 0 { t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) } if _, err := os.Stat(statePath); err != nil { t.Fatalf("err: %s", err) } f, err := os.Open(statePath) if err != nil { t.Fatalf("err: %s", err) } defer f.Close() state, err := terraform.ReadState(f) if err != nil { t.Fatalf("err: %s", err) } if state == nil { t.Fatal("state should not be nil") } }
func (s *Server) newContext( rConf json.RawMessage, rDestroy bool, rPlan []byte, rState json.RawMessage, rParallelism int32, hooks []terraform.Hook) (*terraform.Context, error) { conf, err := config.LoadJSON(rConf) if err != nil { return nil, err } mod := module.NewTree("", conf) err = mod.Load(nil, module.GetModeNone) if err != nil { return nil, fmt.Errorf("Error loading module: %s", err) } ctxOpts := &terraform.ContextOpts{ Destroy: rDestroy, Hooks: hooks, Module: mod, Parallelism: int(rParallelism), Providers: s.providers, Provisioners: s.provisioners, } if rState != nil { b := bytes.NewBuffer(rState) state, err := terraform.ReadState(b) if err != nil { return nil, fmt.Errorf("Error reading state: %s", err) } ctxOpts.State = state } if rPlan != nil { b := bytes.NewBuffer(rPlan) plan, err := terraform.ReadPlan(b) if err != nil { return nil, fmt.Errorf("Error reading plan: %s", err) } return plan.Context(ctxOpts), nil } return terraform.NewContext(ctxOpts), nil }
func TestApply_noArgs(t *testing.T) { cwd, err := os.Getwd() if err != nil { t.Fatalf("err: %s", err) } if err := os.Chdir(testFixturePath("plan")); err != nil { t.Fatalf("err: %s", err) } defer os.Chdir(cwd) statePath := testTempFile(t) p := testProvider() ui := new(cli.MockUi) c := &ApplyCommand{ Meta: Meta{ ContextOpts: testCtxConfig(p), Ui: ui, }, } args := []string{ "-state", statePath, } if code := c.Run(args); code != 0 { t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) } if _, err := os.Stat(statePath); err != nil { t.Fatalf("err: %s", err) } f, err := os.Open(statePath) if err != nil { t.Fatalf("err: %s", err) } defer f.Close() state, err := terraform.ReadState(f) if err != nil { t.Fatalf("err: %s", err) } if state == nil { t.Fatal("state should not be nil") } }
// StateRefresher impl. func (s *State) RefreshState() error { payload, err := s.Client.Get() if err != nil { return err } var state *terraform.State if payload != nil { state, err = terraform.ReadState(bytes.NewReader(payload.Data)) if err != nil { return err } } s.state = state s.readState = state return nil }
// testStateOutput tests that the state at the given path contains // the expected state string. func testStateOutput(t *testing.T, path string, expected string) { f, err := os.Open(path) if err != nil { t.Fatalf("err: %s", err) } newState, err := terraform.ReadState(f) f.Close() if err != nil { t.Fatalf("err: %s", err) } actual := strings.TrimSpace(newState.String()) expected = strings.TrimSpace(expected) if actual != expected { t.Fatalf("bad:\n\n%s", actual) } }
// Taint implements the TerraformServer interface func (s *Server) Taint(c context.Context, req *pb.TaintRequest) (*pb.TaintResponse, error) { resp := new(pb.TaintResponse) if req.Module == "" { req.Module = "root" } else { req.Module = "root." + req.Module } b := bytes.NewBuffer(req.State) state, err := terraform.ReadState(b) if err != nil { return nil, fmt.Errorf("Error reading state: %v", err) } // Get the proper module we want to taint modPath := strings.Split(req.Module, ".") mod := state.ModuleByPath(modPath) if mod == nil { return nil, fmt.Errorf("Module %s could not be found", req.Module) } // If there are no resources in this module, it is an error if len(mod.Resources) == 0 { return nil, fmt.Errorf("Module %s has no resources", req.Module) } // Get the resource we're looking for rs, ok := mod.Resources[req.Resource] if !ok { return nil, fmt.Errorf("Resource %s couldn't be found in the module %s", req.Resource, req.Module) } // Taint the resource and save the updated state rs.Taint() resp.State, err = json.Marshal(state) if err != nil { return nil, fmt.Errorf("Error marshalling tainted state: %v", err) } return resp, nil }
func TestAtlasClient_UnresolvableConflict(t *testing.T) { fakeAtlas := newFakeAtlas(t, testStateSimple) // Something unexpected causes Atlas to conflict in a way that we can't fix. fakeAtlas.AlwaysConflict(true) srv := fakeAtlas.Server() defer srv.Close() client, err := atlasFactory(map[string]string{ "access_token": "sometoken", "name": "someuser/some-test-remote-state", "address": srv.URL, }) if err != nil { t.Fatalf("err: %s", err) } state, err := terraform.ReadState(bytes.NewReader(testStateSimple)) if err != nil { t.Fatalf("err: %s", err) } var stateJson bytes.Buffer if err := terraform.WriteState(state, &stateJson); err != nil { t.Fatalf("err: %s", err) } doneCh := make(chan struct{}) go func() { defer close(doneCh) if err := client.Put(stateJson.Bytes()); err == nil { t.Fatal("Expected error from state conflict, got none.") } }() select { case <-doneCh: // OK case <-time.After(50 * time.Millisecond): t.Fatalf("Timed out after 50ms, probably because retrying infinitely.") } }
func TestRefresh_disableBackup(t *testing.T) { state := testState() statePath := testStateFile(t, state) // Output path outf, err := ioutil.TempFile("", "tf") if err != nil { t.Fatalf("err: %s", err) } outPath := outf.Name() outf.Close() os.Remove(outPath) p := testProvider() ui := new(cli.MockUi) c := &RefreshCommand{ Meta: Meta{ ContextOpts: testCtxConfig(p), Ui: ui, }, } p.RefreshFn = nil p.RefreshReturn = &terraform.InstanceState{ID: "yes"} args := []string{ "-state", statePath, "-state-out", outPath, "-backup", "-", testFixturePath("refresh"), } if code := c.Run(args); code != 0 { t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) } f, err := os.Open(statePath) if err != nil { t.Fatalf("err: %s", err) } newState, err := terraform.ReadState(f) f.Close() if err != nil { t.Fatalf("err: %s", err) } if !reflect.DeepEqual(newState, state) { t.Fatalf("bad: %#v", newState) } f, err = os.Open(outPath) if err != nil { t.Fatalf("err: %s", err) } newState, err = terraform.ReadState(f) f.Close() if err != nil { t.Fatalf("err: %s", err) } actual := newState.RootModule().Resources["test_instance.foo"].Primary expected := p.RefreshReturn if !reflect.DeepEqual(actual, expected) { t.Fatalf("bad: %#v", actual) } // Ensure there is no backup _, err = os.Stat(outPath + DefaultBackupExtension) if err == nil || !os.IsNotExist(err) { t.Fatalf("backup should not exist") } }
func TestRefresh_backup(t *testing.T) { state := testState() statePath := testStateFile(t, state) // Output path outf, err := ioutil.TempFile("", "tf") if err != nil { t.Fatalf("err: %s", err) } outPath := outf.Name() outf.Close() os.Remove(outPath) // Backup path backupf, err := ioutil.TempFile("", "tf") if err != nil { t.Fatalf("err: %s", err) } backupPath := backupf.Name() backupf.Close() os.Remove(backupPath) p := testProvider() ui := new(cli.MockUi) c := &RefreshCommand{ Meta: Meta{ ContextOpts: testCtxConfig(p), Ui: ui, }, } p.RefreshFn = nil p.RefreshReturn = &terraform.InstanceState{ID: "yes"} args := []string{ "-state", statePath, "-state-out", outPath, "-backup", backupPath, testFixturePath("refresh"), } if code := c.Run(args); code != 0 { t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) } f, err := os.Open(statePath) if err != nil { t.Fatalf("err: %s", err) } newState, err := terraform.ReadState(f) f.Close() if err != nil { t.Fatalf("err: %s", err) } if !reflect.DeepEqual(newState, state) { t.Fatalf("bad: %#v", newState) } f, err = os.Open(outPath) if err != nil { t.Fatalf("err: %s", err) } newState, err = terraform.ReadState(f) f.Close() if err != nil { t.Fatalf("err: %s", err) } actual := newState.RootModule().Resources["test_instance.foo"].Primary expected := p.RefreshReturn if !reflect.DeepEqual(actual, expected) { t.Fatalf("bad: %#v", actual) } f, err = os.Open(backupPath) if err != nil { t.Fatalf("err: %s", err) } backupState, err := terraform.ReadState(f) f.Close() if err != nil { t.Fatalf("err: %s", err) } actualStr := strings.TrimSpace(backupState.String()) expectedStr := strings.TrimSpace(state.String()) if actualStr != expectedStr { t.Fatalf("bad:\n\n%s\n\n%s", actualStr, expectedStr) } }
func TestRefresh_defaultState(t *testing.T) { originalState := testState() // Write the state file in a temporary directory with the // default filename. td, err := ioutil.TempDir("", "tf") if err != nil { t.Fatalf("err: %s", err) } statePath := filepath.Join(td, DefaultStateFilename) f, err := os.Create(statePath) if err != nil { t.Fatalf("err: %s", err) } err = terraform.WriteState(originalState, f) f.Close() if err != nil { t.Fatalf("err: %s", err) } // Change to that directory cwd, err := os.Getwd() if err != nil { t.Fatalf("err: %s", err) } if err := os.Chdir(filepath.Dir(statePath)); err != nil { t.Fatalf("err: %s", err) } defer os.Chdir(cwd) p := testProvider() ui := new(cli.MockUi) c := &RefreshCommand{ Meta: Meta{ ContextOpts: testCtxConfig(p), Ui: ui, }, } p.RefreshFn = nil p.RefreshReturn = &terraform.InstanceState{ID: "yes"} args := []string{ testFixturePath("refresh"), } if code := c.Run(args); code != 0 { t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) } if !p.RefreshCalled { t.Fatal("refresh should be called") } f, err = os.Open(statePath) if err != nil { t.Fatalf("err: %s", err) } newState, err := terraform.ReadState(f) f.Close() if err != nil { t.Fatalf("err: %s", err) } actual := newState.RootModule().Resources["test_instance.foo"].Primary expected := p.RefreshReturn if !reflect.DeepEqual(actual, expected) { t.Fatalf("bad: %#v", actual) } f, err = os.Open(statePath + DefaultBackupExtension) if err != nil { t.Fatalf("err: %s", err) } backupState, err := terraform.ReadState(f) f.Close() if err != nil { t.Fatalf("err: %s", err) } actual = backupState.RootModule().Resources["test_instance.foo"].Primary expected = originalState.RootModule().Resources["test_instance.foo"].Primary if !reflect.DeepEqual(actual, expected) { t.Fatalf("bad: %#v", actual) } }
func TestApply_destroyTargeted(t *testing.T) { originalState := &terraform.State{ Modules: []*terraform.ModuleState{ &terraform.ModuleState{ Path: []string{"root"}, Resources: map[string]*terraform.ResourceState{ "test_instance.foo": &terraform.ResourceState{ Type: "test_instance", Primary: &terraform.InstanceState{ ID: "i-ab123", }, }, "test_load_balancer.foo": &terraform.ResourceState{ Type: "test_load_balancer", Primary: &terraform.InstanceState{ ID: "lb-abc123", }, }, }, }, }, } statePath := testStateFile(t, originalState) p := testProvider() ui := new(cli.MockUi) c := &ApplyCommand{ Destroy: true, Meta: Meta{ ContextOpts: testCtxConfig(p), Ui: ui, }, } // Run the apply command pointing to our existing state args := []string{ "-force", "-target", "test_instance.foo", "-state", statePath, testFixturePath("apply-destroy-targeted"), } if code := c.Run(args); code != 0 { t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) } // Verify a new state exists if _, err := os.Stat(statePath); err != nil { t.Fatalf("err: %s", err) } f, err := os.Open(statePath) if err != nil { t.Fatalf("err: %s", err) } defer f.Close() state, err := terraform.ReadState(f) if err != nil { t.Fatalf("err: %s", err) } if state == nil { t.Fatal("state should not be nil") } actualStr := strings.TrimSpace(state.String()) expectedStr := strings.TrimSpace(testApplyDestroyStr) if actualStr != expectedStr { t.Fatalf("bad:\n\n%s\n\n%s", actualStr, expectedStr) } // Should have a backup file f, err = os.Open(statePath + DefaultBackupExtension) if err != nil { t.Fatalf("err: %s", err) } backupState, err := terraform.ReadState(f) f.Close() if err != nil { t.Fatalf("err: %s", err) } actualStr = strings.TrimSpace(backupState.String()) expectedStr = strings.TrimSpace(originalState.String()) if actualStr != expectedStr { t.Fatalf("bad:\n\nactual:\n%s\n\nexpected:\nb%s", actualStr, expectedStr) } }
func TestPlan_backup(t *testing.T) { // Write out some prior state tf, err := ioutil.TempFile("", "tf") if err != nil { t.Fatalf("err: %s", err) } statePath := tf.Name() defer os.Remove(tf.Name()) // Write out some prior state backupf, err := ioutil.TempFile("", "tf") if err != nil { t.Fatalf("err: %s", err) } backupPath := backupf.Name() backupf.Close() os.Remove(backupPath) defer os.Remove(backupPath) originalState := &terraform.State{ Modules: []*terraform.ModuleState{ &terraform.ModuleState{ Path: []string{"root"}, Resources: map[string]*terraform.ResourceState{ "test_instance.foo": &terraform.ResourceState{ Type: "test_instance", Primary: &terraform.InstanceState{ ID: "bar", }, }, }, }, }, } err = terraform.WriteState(originalState, tf) tf.Close() if err != nil { t.Fatalf("err: %s", err) } p := testProvider() ui := new(cli.MockUi) c := &PlanCommand{ Meta: Meta{ ContextOpts: testCtxConfig(p), Ui: ui, }, } args := []string{ "-state", statePath, "-backup", backupPath, testFixturePath("plan"), } if code := c.Run(args); code != 0 { t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) } // Verify that the provider was called with the existing state actual := strings.TrimSpace(p.DiffState.String()) expected := strings.TrimSpace(testPlanBackupStr) if actual != expected { t.Fatalf("bad:\n\n%s", actual) } // Verify the backup exist f, err := os.Open(backupPath) if err != nil { t.Fatalf("err: %s", err) } backupState, err := terraform.ReadState(f) f.Close() if err != nil { t.Fatalf("err: %s", err) } actualStr := strings.TrimSpace(backupState.String()) expectedStr := strings.TrimSpace(originalState.String()) if actualStr != expectedStr { t.Fatalf("bad:\n\n%s\n\n%s", actualStr, expectedStr) } }
func TestPlan_destroy(t *testing.T) { originalState := &terraform.State{ Modules: []*terraform.ModuleState{ &terraform.ModuleState{ Path: []string{"root"}, Resources: map[string]*terraform.ResourceState{ "test_instance.foo": &terraform.ResourceState{ Type: "test_instance", Primary: &terraform.InstanceState{ ID: "bar", }, }, }, }, }, } outPath := testTempFile(t) statePath := testStateFile(t, originalState) p := testProvider() ui := new(cli.MockUi) c := &PlanCommand{ Meta: Meta{ ContextOpts: testCtxConfig(p), Ui: ui, }, } args := []string{ "-destroy", "-out", outPath, "-state", statePath, testFixturePath("plan"), } if code := c.Run(args); code != 0 { t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) } if !p.RefreshCalled { t.Fatal("refresh should be called") } plan := testReadPlan(t, outPath) for _, m := range plan.Diff.Modules { for _, r := range m.Resources { if !r.Destroy { t.Fatalf("bad: %#v", r) } } } f, err := os.Open(statePath + DefaultBackupExtension) if err != nil { t.Fatalf("err: %s", err) } backupState, err := terraform.ReadState(f) f.Close() if err != nil { t.Fatalf("err: %s", err) } actualStr := strings.TrimSpace(backupState.String()) expectedStr := strings.TrimSpace(originalState.String()) if actualStr != expectedStr { t.Fatalf("bad:\n\n%s\n\n%s", actualStr, expectedStr) } }
func TestApply_init(t *testing.T) { // Change to the temporary directory cwd, err := os.Getwd() if err != nil { t.Fatalf("err: %s", err) } dir := tempDir(t) if err := os.MkdirAll(dir, 0755); err != nil { t.Fatalf("err: %s", err) } if err := os.Chdir(dir); err != nil { t.Fatalf("err: %s", err) } defer os.Chdir(cwd) // Create the test fixtures statePath := testTempFile(t) ln := testHttpServer(t) defer ln.Close() // Initialize the command p := testProvider() ui := new(cli.MockUi) c := &ApplyCommand{ Meta: Meta{ ContextOpts: testCtxConfig(p), Ui: ui, }, } // Build the URL to the init var u url.URL u.Scheme = "http" u.Host = ln.Addr().String() u.Path = "/header" args := []string{ "-state", statePath, u.String(), } if code := c.Run(args); code != 0 { t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) } if _, err := os.Stat("hello.tf"); err != nil { t.Fatalf("err: %s", err) } if _, err := os.Stat(statePath); err != nil { t.Fatalf("err: %s", err) } f, err := os.Open(statePath) if err != nil { t.Fatalf("err: %s", err) } defer f.Close() state, err := terraform.ReadState(f) if err != nil { t.Fatalf("err: %s", err) } if state == nil { t.Fatal("state should not be nil") } }
func TestApply_refresh(t *testing.T) { originalState := &terraform.State{ Modules: []*terraform.ModuleState{ &terraform.ModuleState{ Path: []string{"root"}, Resources: map[string]*terraform.ResourceState{ "test_instance.foo": &terraform.ResourceState{ Type: "test_instance", Primary: &terraform.InstanceState{ ID: "bar", }, }, }, }, }, } statePath := testStateFile(t, originalState) p := testProvider() ui := new(cli.MockUi) c := &ApplyCommand{ Meta: Meta{ ContextOpts: testCtxConfig(p), Ui: ui, }, } args := []string{ "-state", statePath, testFixturePath("apply"), } if code := c.Run(args); code != 0 { t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) } if !p.RefreshCalled { t.Fatal("should call refresh") } if _, err := os.Stat(statePath); err != nil { t.Fatalf("err: %s", err) } f, err := os.Open(statePath) if err != nil { t.Fatalf("err: %s", err) } defer f.Close() state, err := terraform.ReadState(f) if err != nil { t.Fatalf("err: %s", err) } if state == nil { t.Fatal("state should not be nil") } // Should have a backup file f, err = os.Open(statePath + DefaultBackupExtension) if err != nil { t.Fatalf("err: %s", err) } backupState, err := terraform.ReadState(f) f.Close() if err != nil { t.Fatalf("err: %s", err) } actualStr := strings.TrimSpace(backupState.String()) expectedStr := strings.TrimSpace(originalState.String()) if actualStr != expectedStr { t.Fatalf("bad:\n\n%s\n\n%s", actualStr, expectedStr) } }
func TestApply_shutdown(t *testing.T) { stopped := false stopCh := make(chan struct{}) stopReplyCh := make(chan struct{}) statePath := testTempFile(t) p := testProvider() shutdownCh := make(chan struct{}) ui := new(cli.MockUi) c := &ApplyCommand{ Meta: Meta{ ContextOpts: testCtxConfig(p), Ui: ui, }, ShutdownCh: shutdownCh, } p.DiffFn = func( *terraform.InstanceInfo, *terraform.InstanceState, *terraform.ResourceConfig) (*terraform.InstanceDiff, error) { return &terraform.InstanceDiff{ Attributes: map[string]*terraform.ResourceAttrDiff{ "ami": &terraform.ResourceAttrDiff{ New: "bar", }, }, }, nil } p.ApplyFn = func( *terraform.InstanceInfo, *terraform.InstanceState, *terraform.InstanceDiff) (*terraform.InstanceState, error) { if !stopped { stopped = true close(stopCh) <-stopReplyCh } return &terraform.InstanceState{ ID: "foo", Attributes: map[string]string{ "ami": "2", }, }, nil } go func() { <-stopCh shutdownCh <- struct{}{} // This is really dirty, but we have no other way to assure that // tf.Stop() has been called. This doesn't assure it either, but // it makes it much more certain. time.Sleep(50 * time.Millisecond) close(stopReplyCh) }() args := []string{ "-state", statePath, testFixturePath("apply-shutdown"), } if code := c.Run(args); code != 0 { t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) } if _, err := os.Stat(statePath); err != nil { t.Fatalf("err: %s", err) } f, err := os.Open(statePath) if err != nil { t.Fatalf("err: %s", err) } defer f.Close() state, err := terraform.ReadState(f) if err != nil { t.Fatalf("err: %s", err) } if state == nil { t.Fatal("state should not be nil") } if len(state.RootModule().Resources) != 1 { t.Fatalf("bad: %d", len(state.RootModule().Resources)) } }
func TestApply_state(t *testing.T) { originalState := &terraform.State{ Modules: []*terraform.ModuleState{ &terraform.ModuleState{ Path: []string{"root"}, Resources: map[string]*terraform.ResourceState{ "test_instance.foo": &terraform.ResourceState{ Type: "test_instance", Primary: &terraform.InstanceState{ ID: "bar", }, }, }, }, }, } statePath := testStateFile(t, originalState) p := testProvider() p.DiffReturn = &terraform.InstanceDiff{ Attributes: map[string]*terraform.ResourceAttrDiff{ "ami": &terraform.ResourceAttrDiff{ New: "bar", }, }, } ui := new(cli.MockUi) c := &ApplyCommand{ Meta: Meta{ ContextOpts: testCtxConfig(p), Ui: ui, }, } // Run the apply command pointing to our existing state args := []string{ "-state", statePath, testFixturePath("apply"), } if code := c.Run(args); code != 0 { t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) } // Verify that the provider was called with the existing state actual := strings.TrimSpace(p.DiffState.String()) expected := strings.TrimSpace(testApplyStateDiffStr) if actual != expected { t.Fatalf("bad:\n\n%s", actual) } actual = strings.TrimSpace(p.ApplyState.String()) expected = strings.TrimSpace(testApplyStateStr) if actual != expected { t.Fatalf("bad:\n\n%s", actual) } // Verify a new state exists if _, err := os.Stat(statePath); err != nil { t.Fatalf("err: %s", err) } f, err := os.Open(statePath) if err != nil { t.Fatalf("err: %s", err) } defer f.Close() state, err := terraform.ReadState(f) if err != nil { t.Fatalf("err: %s", err) } if state == nil { t.Fatal("state should not be nil") } // Should have a backup file f, err = os.Open(statePath + DefaultBackupExtension) if err != nil { t.Fatalf("err: %s", err) } backupState, err := terraform.ReadState(f) f.Close() if err != nil { t.Fatalf("err: %s", err) } // nil out the ConnInfo since that should not be restored originalState.RootModule().Resources["test_instance.foo"].Primary.Ephemeral.ConnInfo = nil actualStr := strings.TrimSpace(backupState.String()) expectedStr := strings.TrimSpace(originalState.String()) if actualStr != expectedStr { t.Fatalf("bad:\n\n%s\n\n%s", actualStr, expectedStr) } }
func TestApply_disableBackup(t *testing.T) { originalState := testState() statePath := testStateFile(t, originalState) p := testProvider() p.DiffReturn = &terraform.InstanceDiff{ Attributes: map[string]*terraform.ResourceAttrDiff{ "ami": &terraform.ResourceAttrDiff{ New: "bar", }, }, } ui := new(cli.MockUi) c := &ApplyCommand{ Meta: Meta{ ContextOpts: testCtxConfig(p), Ui: ui, }, } // Run the apply command pointing to our existing state args := []string{ "-state", statePath, "-backup", "-", testFixturePath("apply"), } if code := c.Run(args); code != 0 { t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) } // Verify that the provider was called with the existing state actual := strings.TrimSpace(p.DiffState.String()) expected := strings.TrimSpace(testApplyDisableBackupStr) if actual != expected { t.Fatalf("bad:\n\n%s", actual) } actual = strings.TrimSpace(p.ApplyState.String()) expected = strings.TrimSpace(testApplyDisableBackupStateStr) if actual != expected { t.Fatalf("bad:\n\n%s", actual) } // Verify a new state exists if _, err := os.Stat(statePath); err != nil { t.Fatalf("err: %s", err) } f, err := os.Open(statePath) if err != nil { t.Fatalf("err: %s", err) } defer f.Close() state, err := terraform.ReadState(f) if err != nil { t.Fatalf("err: %s", err) } if state == nil { t.Fatal("state should not be nil") } // Ensure there is no backup _, err = os.Stat(statePath + DefaultBackupExtension) if err == nil || !os.IsNotExist(err) { t.Fatalf("backup should not exist") } // Ensure there is no literal "-" _, err = os.Stat("-") if err == nil || !os.IsNotExist(err) { t.Fatalf("backup should not exist") } }
func TestApply_error(t *testing.T) { statePath := testTempFile(t) p := testProvider() ui := new(cli.MockUi) c := &ApplyCommand{ Meta: Meta{ ContextOpts: testCtxConfig(p), Ui: ui, }, } var lock sync.Mutex errored := false p.ApplyFn = func( info *terraform.InstanceInfo, s *terraform.InstanceState, d *terraform.InstanceDiff) (*terraform.InstanceState, error) { lock.Lock() defer lock.Unlock() if !errored { errored = true return nil, fmt.Errorf("error") } return &terraform.InstanceState{ID: "foo"}, nil } p.DiffFn = func( *terraform.InstanceInfo, *terraform.InstanceState, *terraform.ResourceConfig) (*terraform.InstanceDiff, error) { return &terraform.InstanceDiff{ Attributes: map[string]*terraform.ResourceAttrDiff{ "ami": &terraform.ResourceAttrDiff{ New: "bar", }, }, }, nil } args := []string{ "-state", statePath, testFixturePath("apply-error"), } if code := c.Run(args); code != 1 { t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) } if _, err := os.Stat(statePath); err != nil { t.Fatalf("err: %s", err) } f, err := os.Open(statePath) if err != nil { t.Fatalf("err: %s", err) } defer f.Close() state, err := terraform.ReadState(f) if err != nil { t.Fatalf("err: %s", err) } if state == nil { t.Fatal("state should not be nil") } if len(state.RootModule().Resources) == 0 { t.Fatal("no resources in state") } }
func TestApply_backup(t *testing.T) { originalState := &terraform.State{ Modules: []*terraform.ModuleState{ &terraform.ModuleState{ Path: []string{"root"}, Resources: map[string]*terraform.ResourceState{ "test_instance.foo": &terraform.ResourceState{ Type: "test_instance", Primary: &terraform.InstanceState{ ID: "bar", }, }, }, }, }, } statePath := testStateFile(t, originalState) backupPath := testTempFile(t) p := testProvider() p.DiffReturn = &terraform.InstanceDiff{ Attributes: map[string]*terraform.ResourceAttrDiff{ "ami": &terraform.ResourceAttrDiff{ New: "bar", }, }, } ui := new(cli.MockUi) c := &ApplyCommand{ Meta: Meta{ ContextOpts: testCtxConfig(p), Ui: ui, }, } // Run the apply command pointing to our existing state args := []string{ "-state", statePath, "-backup", backupPath, testFixturePath("apply"), } if code := c.Run(args); code != 0 { t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) } // Verify a new state exists if _, err := os.Stat(statePath); err != nil { t.Fatalf("err: %s", err) } f, err := os.Open(statePath) if err != nil { t.Fatalf("err: %s", err) } defer f.Close() state, err := terraform.ReadState(f) if err != nil { t.Fatalf("err: %s", err) } if state == nil { t.Fatal("state should not be nil") } // Should have a backup file f, err = os.Open(backupPath) if err != nil { t.Fatalf("err: %s", err) } backupState, err := terraform.ReadState(f) f.Close() if err != nil { t.Fatalf("err: %s", err) } actual := backupState.RootModule().Resources["test_instance.foo"] expected := originalState.RootModule().Resources["test_instance.foo"] if !reflect.DeepEqual(actual, expected) { t.Fatalf("bad: %#v %#v", actual, expected) } }
func (c *ShowCommand) Run(args []string) int { var moduleDepth int args = c.Meta.process(args, false) cmdFlags := flag.NewFlagSet("show", flag.ContinueOnError) c.addModuleDepthFlag(cmdFlags, &moduleDepth) cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } if err := cmdFlags.Parse(args); err != nil { return 1 } args = cmdFlags.Args() if len(args) > 1 { c.Ui.Error( "The show command expects at most one argument with the path\n" + "to a Terraform state or plan file.\n") cmdFlags.Usage() return 1 } var planErr, stateErr error var path string var plan *terraform.Plan var state *terraform.State if len(args) > 0 { path = args[0] f, err := os.Open(path) if err != nil { c.Ui.Error(fmt.Sprintf("Error loading file: %s", err)) return 1 } defer f.Close() plan, err = terraform.ReadPlan(f) if err != nil { if _, err := f.Seek(0, 0); err != nil { c.Ui.Error(fmt.Sprintf("Error reading file: %s", err)) return 1 } plan = nil planErr = err } if plan == nil { state, err = terraform.ReadState(f) if err != nil { stateErr = err } } } else { stateOpts := c.StateOpts() stateOpts.RemoteCacheOnly = true result, err := State(stateOpts) if err != nil { c.Ui.Error(fmt.Sprintf("Error reading state: %s", err)) return 1 } state = result.State.State() if state == nil { c.Ui.Output("No state.") return 0 } } if plan == nil && state == nil { c.Ui.Error(fmt.Sprintf( "Terraform couldn't read the given file as a state or plan file.\n"+ "The errors while attempting to read the file as each format are\n"+ "shown below.\n\n"+ "State read error: %s\n\nPlan read error: %s", stateErr, planErr)) return 1 } if plan != nil { c.Ui.Output(FormatPlan(&FormatPlanOpts{ Plan: plan, Color: c.Colorize(), ModuleDepth: moduleDepth, })) return 0 } c.Ui.Output(FormatState(&FormatStateOpts{ State: state, Color: c.Colorize(), ModuleDepth: moduleDepth, })) return 0 }