2017-01-19 05:47:56 +01:00
|
|
|
package local
|
|
|
|
|
|
|
|
import (
|
2017-02-27 22:43:31 +01:00
|
|
|
"errors"
|
2017-02-22 01:07:27 +01:00
|
|
|
"io/ioutil"
|
2017-01-19 05:47:56 +01:00
|
|
|
"os"
|
2017-03-01 01:18:16 +01:00
|
|
|
"path/filepath"
|
2017-02-22 01:07:27 +01:00
|
|
|
"reflect"
|
2017-01-19 05:47:56 +01:00
|
|
|
"strings"
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
"github.com/hashicorp/terraform/backend"
|
2017-02-27 22:43:31 +01:00
|
|
|
"github.com/hashicorp/terraform/state"
|
2017-01-19 05:47:56 +01:00
|
|
|
"github.com/hashicorp/terraform/terraform"
|
|
|
|
)
|
|
|
|
|
|
|
|
func TestLocal_impl(t *testing.T) {
|
|
|
|
var _ backend.Enhanced = new(Local)
|
|
|
|
var _ backend.Local = new(Local)
|
2017-02-28 19:58:29 +01:00
|
|
|
var _ backend.CLI = new(Local)
|
2017-01-19 05:47:56 +01:00
|
|
|
}
|
|
|
|
|
2017-03-09 11:47:21 +01:00
|
|
|
func TestLocal_backend(t *testing.T) {
|
2017-03-23 16:14:13 +01:00
|
|
|
defer testTmpDir(t)()
|
|
|
|
b := &Local{}
|
2017-03-15 16:46:58 +01:00
|
|
|
backend.TestBackend(t, b, b)
|
2017-03-09 11:47:21 +01:00
|
|
|
}
|
|
|
|
|
2017-01-19 05:47:56 +01:00
|
|
|
func checkState(t *testing.T, path, expected string) {
|
|
|
|
// Read the state
|
|
|
|
f, err := os.Open(path)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
state, err := terraform.ReadState(f)
|
|
|
|
f.Close()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
actual := strings.TrimSpace(state.String())
|
|
|
|
expected = strings.TrimSpace(expected)
|
|
|
|
if actual != expected {
|
|
|
|
t.Fatalf("state does not match! actual:\n%s\n\nexpected:\n%s", actual, expected)
|
|
|
|
}
|
|
|
|
}
|
2017-02-22 01:07:27 +01:00
|
|
|
|
2017-03-01 01:18:16 +01:00
|
|
|
func TestLocal_StatePaths(t *testing.T) {
|
|
|
|
b := &Local{}
|
|
|
|
|
|
|
|
// Test the defaults
|
|
|
|
path, out, back := b.StatePaths("")
|
|
|
|
|
|
|
|
if path != DefaultStateFilename {
|
|
|
|
t.Fatalf("expected %q, got %q", DefaultStateFilename, path)
|
|
|
|
}
|
|
|
|
|
|
|
|
if out != DefaultStateFilename {
|
|
|
|
t.Fatalf("expected %q, got %q", DefaultStateFilename, out)
|
|
|
|
}
|
|
|
|
|
|
|
|
dfltBackup := DefaultStateFilename + DefaultBackupExtension
|
|
|
|
if back != dfltBackup {
|
|
|
|
t.Fatalf("expected %q, got %q", dfltBackup, back)
|
|
|
|
}
|
|
|
|
|
|
|
|
// check with env
|
|
|
|
testEnv := "test_env"
|
|
|
|
path, out, back = b.StatePaths(testEnv)
|
|
|
|
|
|
|
|
expectedPath := filepath.Join(DefaultEnvDir, testEnv, DefaultStateFilename)
|
|
|
|
expectedOut := expectedPath
|
|
|
|
expectedBackup := expectedPath + DefaultBackupExtension
|
|
|
|
|
|
|
|
if path != expectedPath {
|
|
|
|
t.Fatalf("expected %q, got %q", expectedPath, path)
|
|
|
|
}
|
|
|
|
|
|
|
|
if out != expectedOut {
|
|
|
|
t.Fatalf("expected %q, got %q", expectedOut, out)
|
|
|
|
}
|
|
|
|
|
|
|
|
if back != expectedBackup {
|
|
|
|
t.Fatalf("expected %q, got %q", expectedBackup, back)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2017-02-22 01:07:27 +01:00
|
|
|
func TestLocal_addAndRemoveStates(t *testing.T) {
|
|
|
|
defer testTmpDir(t)()
|
|
|
|
dflt := backend.DefaultStateName
|
|
|
|
expectedStates := []string{dflt}
|
|
|
|
|
|
|
|
b := &Local{}
|
2017-02-27 22:43:31 +01:00
|
|
|
states, err := b.States()
|
2017-02-22 01:07:27 +01:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if !reflect.DeepEqual(states, expectedStates) {
|
2017-02-22 20:53:43 +01:00
|
|
|
t.Fatalf("expected []string{%q}, got %q", dflt, states)
|
2017-02-22 01:07:27 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
expectedA := "test_A"
|
2017-02-27 22:43:31 +01:00
|
|
|
if _, err := b.State(expectedA); err != nil {
|
2017-02-22 01:07:27 +01:00
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2017-02-27 22:43:31 +01:00
|
|
|
states, err = b.States()
|
2017-02-22 20:53:43 +01:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2017-02-22 01:07:27 +01:00
|
|
|
|
|
|
|
expectedStates = append(expectedStates, expectedA)
|
|
|
|
if !reflect.DeepEqual(states, expectedStates) {
|
|
|
|
t.Fatalf("expected %q, got %q", expectedStates, states)
|
|
|
|
}
|
|
|
|
|
|
|
|
expectedB := "test_B"
|
2017-02-27 22:43:31 +01:00
|
|
|
if _, err := b.State(expectedB); err != nil {
|
2017-02-22 01:07:27 +01:00
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2017-02-27 22:43:31 +01:00
|
|
|
states, err = b.States()
|
2017-02-22 20:53:43 +01:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2017-02-22 01:07:27 +01:00
|
|
|
|
|
|
|
expectedStates = append(expectedStates, expectedB)
|
|
|
|
if !reflect.DeepEqual(states, expectedStates) {
|
|
|
|
t.Fatalf("expected %q, got %q", expectedStates, states)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := b.DeleteState(expectedA); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2017-02-27 22:43:31 +01:00
|
|
|
states, err = b.States()
|
2017-02-22 20:53:43 +01:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2017-02-22 01:07:27 +01:00
|
|
|
|
|
|
|
expectedStates = []string{dflt, expectedB}
|
|
|
|
if !reflect.DeepEqual(states, expectedStates) {
|
|
|
|
t.Fatalf("expected %q, got %q", expectedStates, states)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := b.DeleteState(expectedB); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2017-02-27 22:43:31 +01:00
|
|
|
states, err = b.States()
|
2017-02-22 20:53:43 +01:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2017-02-22 01:07:27 +01:00
|
|
|
|
|
|
|
expectedStates = []string{dflt}
|
|
|
|
if !reflect.DeepEqual(states, expectedStates) {
|
|
|
|
t.Fatalf("expected %q, got %q", expectedStates, states)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := b.DeleteState(dflt); err == nil {
|
|
|
|
t.Fatal("expected error deleting default state")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-01 01:18:16 +01:00
|
|
|
// a local backend which returns sentinel errors for NamedState methods to
|
2017-02-27 22:43:31 +01:00
|
|
|
// verify it's being called.
|
|
|
|
type testDelegateBackend struct {
|
|
|
|
*Local
|
2017-04-22 00:57:39 +02:00
|
|
|
|
|
|
|
// return a sentinel error on these calls
|
|
|
|
stateErr bool
|
|
|
|
statesErr bool
|
|
|
|
deleteErr bool
|
2017-02-27 22:43:31 +01:00
|
|
|
}
|
2017-02-22 20:53:43 +01:00
|
|
|
|
2017-02-27 22:43:31 +01:00
|
|
|
var errTestDelegateState = errors.New("State called")
|
|
|
|
var errTestDelegateStates = errors.New("States called")
|
|
|
|
var errTestDelegateDeleteState = errors.New("Delete called")
|
2017-02-22 20:53:43 +01:00
|
|
|
|
2017-02-27 22:43:31 +01:00
|
|
|
func (b *testDelegateBackend) State(name string) (state.State, error) {
|
2017-04-22 00:57:39 +02:00
|
|
|
if b.stateErr {
|
|
|
|
return nil, errTestDelegateState
|
|
|
|
}
|
|
|
|
s := &state.LocalState{
|
|
|
|
Path: "terraform.tfstate",
|
|
|
|
PathOut: "terraform.tfstate",
|
|
|
|
}
|
|
|
|
return s, nil
|
2017-02-27 22:43:31 +01:00
|
|
|
}
|
2017-02-22 20:53:43 +01:00
|
|
|
|
2017-02-27 22:43:31 +01:00
|
|
|
func (b *testDelegateBackend) States() ([]string, error) {
|
2017-04-22 00:57:39 +02:00
|
|
|
if b.statesErr {
|
|
|
|
return nil, errTestDelegateStates
|
|
|
|
}
|
|
|
|
return []string{"default"}, nil
|
2017-02-27 22:43:31 +01:00
|
|
|
}
|
2017-02-22 20:53:43 +01:00
|
|
|
|
2017-02-27 22:43:31 +01:00
|
|
|
func (b *testDelegateBackend) DeleteState(name string) error {
|
2017-04-22 00:57:39 +02:00
|
|
|
if b.deleteErr {
|
|
|
|
return errTestDelegateDeleteState
|
|
|
|
}
|
|
|
|
return nil
|
2017-02-22 20:53:43 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// verify that the MultiState methods are dispatched to the correct Backend.
|
|
|
|
func TestLocal_multiStateBackend(t *testing.T) {
|
2017-02-27 22:43:31 +01:00
|
|
|
// assign a separate backend where we can read the state
|
2017-02-22 20:53:43 +01:00
|
|
|
b := &Local{
|
2017-04-22 00:57:39 +02:00
|
|
|
Backend: &testDelegateBackend{
|
|
|
|
stateErr: true,
|
|
|
|
statesErr: true,
|
|
|
|
deleteErr: true,
|
|
|
|
},
|
2017-02-22 20:53:43 +01:00
|
|
|
}
|
|
|
|
|
2017-02-27 22:43:31 +01:00
|
|
|
if _, err := b.State("test"); err != errTestDelegateState {
|
|
|
|
t.Fatal("expected errTestDelegateState, got:", err)
|
2017-02-22 20:53:43 +01:00
|
|
|
}
|
|
|
|
|
2017-02-27 22:43:31 +01:00
|
|
|
if _, err := b.States(); err != errTestDelegateStates {
|
|
|
|
t.Fatal("expected errTestDelegateStates, got:", err)
|
2017-02-22 20:53:43 +01:00
|
|
|
}
|
|
|
|
|
2017-02-27 22:43:31 +01:00
|
|
|
if err := b.DeleteState("test"); err != errTestDelegateDeleteState {
|
|
|
|
t.Fatal("expected errTestDelegateDeleteState, got:", err)
|
2017-02-22 20:53:43 +01:00
|
|
|
}
|
2017-04-22 00:57:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// verify that a remote state backend is always wrapped in a BackupState
|
|
|
|
func TestLocal_remoteStateBackup(t *testing.T) {
|
|
|
|
// assign a separate backend to mock a remote state backend
|
|
|
|
b := &Local{
|
|
|
|
Backend: &testDelegateBackend{},
|
|
|
|
}
|
|
|
|
|
|
|
|
s, err := b.State("default")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
bs, ok := s.(*state.BackupState)
|
|
|
|
if !ok {
|
|
|
|
t.Fatal("remote state is not backed up")
|
|
|
|
}
|
2017-02-22 20:53:43 +01:00
|
|
|
|
2017-04-22 00:57:39 +02:00
|
|
|
if bs.Path != DefaultStateFilename+DefaultBackupExtension {
|
|
|
|
t.Fatal("bad backup location:", bs.Path)
|
|
|
|
}
|
|
|
|
|
|
|
|
// do the same with a named state, which should use the local env directories
|
|
|
|
s, err = b.State("test")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
bs, ok = s.(*state.BackupState)
|
|
|
|
if !ok {
|
|
|
|
t.Fatal("remote state is not backed up")
|
|
|
|
}
|
|
|
|
|
|
|
|
if bs.Path != filepath.Join(DefaultEnvDir, "test", DefaultStateFilename+DefaultBackupExtension) {
|
|
|
|
t.Fatal("bad backup location:", bs.Path)
|
|
|
|
}
|
2017-02-22 20:53:43 +01:00
|
|
|
}
|
|
|
|
|
2017-02-22 01:07:27 +01:00
|
|
|
// change into a tmp dir and return a deferable func to change back and cleanup
|
|
|
|
func testTmpDir(t *testing.T) func() {
|
|
|
|
tmp, err := ioutil.TempDir("", "tf")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
old, err := os.Getwd()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := os.Chdir(tmp); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return func() {
|
|
|
|
// ignore errors and try to clean up
|
|
|
|
os.Chdir(old)
|
|
|
|
os.RemoveAll(tmp)
|
|
|
|
}
|
|
|
|
}
|