terraform/backend/local/backend_test.go

291 lines
6.3 KiB
Go

package local
import (
"errors"
"io/ioutil"
"os"
"path/filepath"
"reflect"
"strings"
"testing"
"github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/state"
"github.com/hashicorp/terraform/terraform"
)
func TestLocal_impl(t *testing.T) {
var _ backend.Enhanced = new(Local)
var _ backend.Local = new(Local)
var _ backend.CLI = new(Local)
}
func TestLocal_backend(t *testing.T) {
defer testTmpDir(t)()
b := &Local{}
backend.TestBackend(t, b, b)
}
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)
}
}
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)
}
}
func TestLocal_addAndRemoveStates(t *testing.T) {
defer testTmpDir(t)()
dflt := backend.DefaultStateName
expectedStates := []string{dflt}
b := &Local{}
states, err := b.States()
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(states, expectedStates) {
t.Fatalf("expected []string{%q}, got %q", dflt, states)
}
expectedA := "test_A"
if _, err := b.State(expectedA); err != nil {
t.Fatal(err)
}
states, err = b.States()
if err != nil {
t.Fatal(err)
}
expectedStates = append(expectedStates, expectedA)
if !reflect.DeepEqual(states, expectedStates) {
t.Fatalf("expected %q, got %q", expectedStates, states)
}
expectedB := "test_B"
if _, err := b.State(expectedB); err != nil {
t.Fatal(err)
}
states, err = b.States()
if err != nil {
t.Fatal(err)
}
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)
}
states, err = b.States()
if err != nil {
t.Fatal(err)
}
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)
}
states, err = b.States()
if err != nil {
t.Fatal(err)
}
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")
}
}
// a local backend which returns sentinel errors for NamedState methods to
// verify it's being called.
type testDelegateBackend struct {
*Local
// return a sentinel error on these calls
stateErr bool
statesErr bool
deleteErr bool
}
var errTestDelegateState = errors.New("State called")
var errTestDelegateStates = errors.New("States called")
var errTestDelegateDeleteState = errors.New("Delete called")
func (b *testDelegateBackend) State(name string) (state.State, error) {
if b.stateErr {
return nil, errTestDelegateState
}
s := &state.LocalState{
Path: "terraform.tfstate",
PathOut: "terraform.tfstate",
}
return s, nil
}
func (b *testDelegateBackend) States() ([]string, error) {
if b.statesErr {
return nil, errTestDelegateStates
}
return []string{"default"}, nil
}
func (b *testDelegateBackend) DeleteState(name string) error {
if b.deleteErr {
return errTestDelegateDeleteState
}
return nil
}
// verify that the MultiState methods are dispatched to the correct Backend.
func TestLocal_multiStateBackend(t *testing.T) {
// assign a separate backend where we can read the state
b := &Local{
Backend: &testDelegateBackend{
stateErr: true,
statesErr: true,
deleteErr: true,
},
}
if _, err := b.State("test"); err != errTestDelegateState {
t.Fatal("expected errTestDelegateState, got:", err)
}
if _, err := b.States(); err != errTestDelegateStates {
t.Fatal("expected errTestDelegateStates, got:", err)
}
if err := b.DeleteState("test"); err != errTestDelegateDeleteState {
t.Fatal("expected errTestDelegateDeleteState, got:", err)
}
}
// 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")
}
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)
}
}
// 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)
}
}