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 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"] = &terraform.OutputState{ Type: "string", Sensitive: false, Value: "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 (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 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 := &terraform.State{ Resources: map[string]*terraform.ResourceState{ "test_instance.foo": &terraform.ResourceState{ ID: "bar", Type: "test_instance", }, }, } 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.ResourceState{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 := newState.Resources["test_instance.foo"] expected := p.RefreshReturn if !reflect.DeepEqual(actual, expected) { t.Fatalf("bad: %#v", actual) } }
// StateRefresher impl. func (s *LocalState) RefreshState() error { // If we've never loaded before, read from Path, otherwise we // read from PathOut. path := s.Path if s.written && s.PathOut != "" { path = s.PathOut } f, err := os.Open(path) if err != nil { // It is okay if the file doesn't exist, we treat that as a nil state if !os.IsNotExist(err) { return err } f = nil } var state *terraform.State if f != nil { defer f.Close() state, err = terraform.ReadState(f) if err != nil { return err } } s.state = state s.readState = state return nil }
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 TestPlan_destroy(t *testing.T) { originalState := &terraform.State{ Resources: map[string]*terraform.ResourceState{ "test_instance.foo": &terraform.ResourceState{ ID: "bar", Type: "test_instance", }, }, } 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 _, r := range plan.Diff.Resources { if !r.Destroy { t.Fatalf("bad: %#v", r) } } f, err := os.Open(statePath + DefaultBackupExtention) if err != nil { t.Fatalf("err: %s", err) } backupState, err := terraform.ReadState(f) f.Close() if err != nil { t.Fatalf("err: %s", err) } if !reflect.DeepEqual(backupState, originalState) { t.Fatalf("bad: %#v", backupState) } }
func ReadState(path string) (*terraform.State, error) { if f, err := os.Open(path); err != nil { return nil, err } else { defer f.Close() state, _ := terraform.ReadState(f) return state, nil } }
func readState(path string) (*terraform.State, error) { f, err := os.Open(path) if err != nil { return nil, err } defer f.Close() return terraform.ReadState(f) }
func readState(path string) *terraform.State { f, _ := os.Open(path) if f != nil { defer f.Close() state, _ := terraform.ReadState(f) return state } return nil }
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") } }
// Context returns a Terraform Context taking into account the context // options used to initialize this meta configuration. func (m *Meta) Context(path, statePath string) (*terraform.Context, bool, error) { opts := m.contextOpts() // First try to just read the plan directly from the path given. f, err := os.Open(path) if err == nil { plan, err := terraform.ReadPlan(f) f.Close() if err == nil { if len(m.variables) > 0 { return nil, false, fmt.Errorf( "You can't set variables with the '-var' or '-var-file' flag\n" + "when you're applying a plan file. The variables used when\n" + "the plan was created will be used. If you wish to use different\n" + "variable values, create a new plan file.") } return plan.Context(opts), true, nil } } // Load up the state var state *terraform.State if statePath != "" { f, err := os.Open(statePath) if err != nil && os.IsNotExist(err) { // If the state file doesn't exist, it is okay, since it // is probably a new infrastructure. err = nil } else if err == nil { state, err = terraform.ReadState(f) f.Close() } if err != nil { return nil, false, fmt.Errorf("Error loading state: %s", err) } } // Store the loaded state m.state = state config, err := config.LoadDir(path) if err != nil { return nil, false, fmt.Errorf("Error loading config: %s", err) } if err := config.Validate(); err != nil { return nil, false, fmt.Errorf("Error validating config: %s", err) } opts.Config = config opts.State = state ctx := terraform.NewContext(opts) return ctx, false, nil }
func (c *OutputCommand) Run(args []string) int { var statePath string args = c.Meta.process(args, false) cmdFlags := flag.NewFlagSet("output", flag.ContinueOnError) cmdFlags.StringVar(&statePath, "state", DefaultStateFilename, "path") cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } if err := cmdFlags.Parse(args); err != nil { return 1 } args = cmdFlags.Args() if len(args) != 1 || args[0] == "" { c.Ui.Error( "The output command expects exactly one argument with the name\n" + "of an output variable.\n") cmdFlags.Usage() return 1 } name := args[0] f, err := os.Open(statePath) if err != nil { c.Ui.Error(fmt.Sprintf("Error loading file: %s", err)) return 1 } state, err := terraform.ReadState(f) f.Close() if err != nil { c.Ui.Error(fmt.Sprintf("Error reading state: %s", err)) return 1 } if len(state.Outputs) == 0 { c.Ui.Error(fmt.Sprintf( "The state file has no outputs defined. Define an output\n" + "in your configuration with the `output` directive and re-run\n" + "`terraform apply` for it to become available.")) return 1 } v, ok := state.Outputs[name] if !ok { c.Ui.Error(fmt.Sprintf( "The output variable requested could not be found in the state\n" + "file. If you recently added this to your configuration, be\n" + "sure to run `terraform apply`, since the state won't be updated\n" + "with new output variables until that command is run.")) return 1 } c.Ui.Output(v) return 0 }
// laodState is used to load the Terraform state. We give precedence // to a remote state if enabled, and then check the normal state path. func (m *Meta) loadState() (*terraform.State, error) { // Check if we remote state is enabled localCache, _, err := remote.ReadLocalState() if err != nil { return nil, fmt.Errorf("Error loading state: %s", err) } // Set the state if enabled var state *terraform.State if localCache != nil { // Refresh the state log.Printf("[INFO] Refreshing local state...") changes, err := remote.RefreshState(localCache.Remote) if err != nil { return nil, fmt.Errorf("Failed to refresh state: %v", err) } switch changes { case remote.StateChangeNoop: case remote.StateChangeInit: case remote.StateChangeLocalNewer: case remote.StateChangeUpdateLocal: // Reload the state since we've udpated localCache, _, err = remote.ReadLocalState() if err != nil { return nil, fmt.Errorf("Error loading state: %s", err) } default: return nil, fmt.Errorf("%s", changes) } state = localCache m.useRemoteState = true } // Load up the state if m.statePath != "" { f, err := os.Open(m.statePath) if err != nil && os.IsNotExist(err) { // If the state file doesn't exist, it is okay, since it // is probably a new infrastructure. err = nil } else if m.useRemoteState && err == nil { err = fmt.Errorf("Remote state enabled, but state file '%s' also present.", m.statePath) f.Close() } else if err == nil { state, err = terraform.ReadState(f) f.Close() } if err != nil { return nil, fmt.Errorf("Error loading state: %s", err) } } return state, nil }
// 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 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) } }
// 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") } }
// Function fetchState takes a directory name (a string) and returns // an instance of *terraform.State based on what it finds in that // directory and an error. func fetchState(dir string) (*terraform.State, error) { src, err := openState(dir) if err != nil { return nil, err } defer src.Close() state, err := terraform.ReadState(src) if err != nil { return nil, errors.New(fmt.Sprintf("Unable to read state from src")) } return state, nil }
func TestRefresh_futureState(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() state.TFVersion = "99.99.99" statePath := testStateFile(t, state) p := testProvider() ui := new(cli.MockUi) c := &RefreshCommand{ Meta: Meta{ ContextOpts: testCtxConfig(p), Ui: ui, }, } args := []string{ "-state", statePath, } if code := c.Run(args); code == 0 { t.Fatal("should fail") } if p.RefreshCalled { t.Fatal("refresh should not 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(state.String()) if actual != expected { t.Fatalf("bad:\n\n%s", actual) } }
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 TestPersist(t *testing.T) { tmp, cwd := testDir(t) defer testFixCwd(tmp, cwd) EnsureDirectory() // Place old state file, should backup old := filepath.Join(tmp, LocalDirectory, HiddenStateFile) ioutil.WriteFile(old, []byte("test"), 0777) remote := &terraform.RemoteState{ Type: "http", Config: map[string]string{ "address": "http://foo.com/", }, } blank, _ := blankState(remote) if err := Persist(bytes.NewReader(blank)); err != nil { t.Fatalf("err: %v", err) } // Check for backup backup := filepath.Join(tmp, LocalDirectory, BackupHiddenStateFile) out, err := ioutil.ReadFile(backup) if err != nil { t.Fatalf("Err: %v", err) } if string(out) != "test" { t.Fatalf("bad: %v", out) } // Read the state out, err = ioutil.ReadFile(old) if err != nil { t.Fatalf("Err: %v", err) } s, err := terraform.ReadState(bytes.NewReader(out)) if err != nil { t.Fatalf("Err: %v", err) } // Check the remote if !remote.Equals(s.Remote) { t.Fatalf("remote mismatch") } }
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("expected:\n%s\nactual:\n%s", expected, actual) } }
// testReadLocal is used to just get the local state func testReadLocal(t *testing.T) *terraform.State { path, err := HiddenStatePath() if err != nil { t.Fatalf("err: %v", err) } raw, err := ioutil.ReadFile(path) if err != nil && !os.IsNotExist(err) { t.Fatalf("err: %v", err) } if raw == nil { return nil } s, err := terraform.ReadState(bytes.NewReader(raw)) if err != nil { t.Fatalf("err: %v", err) } return s }
func TestBlankState(t *testing.T) { remote := &terraform.RemoteState{ Type: "http", Config: map[string]string{ "address": "http://foo.com/", }, } r, err := blankState(remote) if err != nil { t.Fatalf("err: %v", err) } s, err := terraform.ReadState(bytes.NewReader(r)) if err != nil { t.Fatalf("err: %v", err) } if !remote.Equals(s.Remote) { t.Fatalf("remote mismatch") } }
// StateRefresher impl. func (s *State) RefreshState() error { payload, err := s.Client.Get() if err != nil { return err } // no remote state is OK if payload == nil { return nil } state, err := terraform.ReadState(bytes.NewReader(payload.Data)) if err != nil { return err } s.state = state s.readState = state return nil }
func TestApply_plan(t *testing.T) { planPath := testPlanFile(t, &terraform.Plan{ Config: new(config.Config), }) 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 _, 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 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(500 * time.Millisecond): t.Fatalf("Timed out after 500ms, probably because retrying infinitely.") } }