Merge pull request #13008 from hashicorp/jbardin/s3-backend
Fix S3 named state creation and backend tests
This commit is contained in:
commit
ba736c3531
|
@ -21,7 +21,8 @@ func TestLocal_impl(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLocal_backend(t *testing.T) {
|
func TestLocal_backend(t *testing.T) {
|
||||||
b := TestLocal(t)
|
defer testTmpDir(t)()
|
||||||
|
b := &Local{}
|
||||||
backend.TestBackend(t, b, b)
|
backend.TestBackend(t, b, b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"github.com/hashicorp/terraform/backend"
|
"github.com/hashicorp/terraform/backend"
|
||||||
"github.com/hashicorp/terraform/state"
|
"github.com/hashicorp/terraform/state"
|
||||||
"github.com/hashicorp/terraform/state/remote"
|
"github.com/hashicorp/terraform/state/remote"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -88,15 +89,53 @@ func (b *Backend) State(name string) (state.State, error) {
|
||||||
lockTable: b.lockTable,
|
lockTable: b.lockTable,
|
||||||
}
|
}
|
||||||
|
|
||||||
// if this isn't the default state name, we need to create the object so
|
stateMgr := &remote.State{Client: client}
|
||||||
// it's listed by States.
|
|
||||||
|
//if this isn't the default state name, we need to create the object so
|
||||||
|
//it's listed by States.
|
||||||
if name != backend.DefaultStateName {
|
if name != backend.DefaultStateName {
|
||||||
if err := client.Put([]byte{}); err != nil {
|
// take a lock on this state while we write it
|
||||||
|
lockInfo := state.NewLockInfo()
|
||||||
|
lockInfo.Operation = "init"
|
||||||
|
lockId, err := client.Lock(lockInfo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to lock s3 state: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Local helper function so we can call it multiple places
|
||||||
|
lockUnlock := func(parent error) error {
|
||||||
|
if err := stateMgr.Unlock(lockId); err != nil {
|
||||||
|
return fmt.Errorf(strings.TrimSpace(errStateUnlock), lockId, err)
|
||||||
|
}
|
||||||
|
return parent
|
||||||
|
}
|
||||||
|
|
||||||
|
// Grab the value
|
||||||
|
if err := stateMgr.RefreshState(); err != nil {
|
||||||
|
err = lockUnlock(err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have no state, we have to create an empty state
|
||||||
|
if v := stateMgr.State(); v == nil {
|
||||||
|
if err := stateMgr.WriteState(terraform.NewState()); err != nil {
|
||||||
|
err = lockUnlock(err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := stateMgr.PersistState(); err != nil {
|
||||||
|
err = lockUnlock(err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &remote.State{Client: client}, nil
|
// Unlock, the state should now be initialized
|
||||||
|
if err := lockUnlock(nil); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return stateMgr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Backend) client() *RemoteClient {
|
func (b *Backend) client() *RemoteClient {
|
||||||
|
@ -110,3 +149,11 @@ func (b *Backend) path(name string) string {
|
||||||
|
|
||||||
return strings.Join([]string{keyEnvPrefix, name, b.keyName}, "/")
|
return strings.Join([]string{keyEnvPrefix, name, b.keyName}, "/")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const errStateUnlock = `
|
||||||
|
Error unlocking S3 state. Lock ID: %s
|
||||||
|
|
||||||
|
Error: %s
|
||||||
|
|
||||||
|
You may have to force-unlock this state in order to use it again.
|
||||||
|
`
|
||||||
|
|
|
@ -65,57 +65,109 @@ func testBackendStates(t *testing.T, b Backend) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a couple states
|
// Create a couple states
|
||||||
fooState, err := b.State("foo")
|
foo, err := b.State("foo")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("error: %s", err)
|
t.Fatalf("error: %s", err)
|
||||||
}
|
}
|
||||||
if err := fooState.RefreshState(); err != nil {
|
if err := foo.RefreshState(); err != nil {
|
||||||
t.Fatalf("bad: %s", err)
|
t.Fatalf("bad: %s", err)
|
||||||
}
|
}
|
||||||
if v := fooState.State(); v.HasResources() {
|
if v := foo.State(); v.HasResources() {
|
||||||
t.Fatalf("should be empty: %s", v)
|
t.Fatalf("should be empty: %s", v)
|
||||||
}
|
}
|
||||||
|
|
||||||
barState, err := b.State("bar")
|
bar, err := b.State("bar")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("error: %s", err)
|
t.Fatalf("error: %s", err)
|
||||||
}
|
}
|
||||||
if err := barState.RefreshState(); err != nil {
|
if err := bar.RefreshState(); err != nil {
|
||||||
t.Fatalf("bad: %s", err)
|
t.Fatalf("bad: %s", err)
|
||||||
}
|
}
|
||||||
if v := barState.State(); v.HasResources() {
|
if v := bar.State(); v.HasResources() {
|
||||||
t.Fatalf("should be empty: %s", v)
|
t.Fatalf("should be empty: %s", v)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify they are distinct states
|
// Verify they are distinct states that can be read back from storage
|
||||||
{
|
{
|
||||||
s := barState.State()
|
// start with a fresh state, and record the lineage being
|
||||||
if s == nil {
|
// written to "bar"
|
||||||
s = terraform.NewState()
|
barState := terraform.NewState()
|
||||||
|
barLineage := barState.Lineage
|
||||||
|
|
||||||
|
// the foo lineage should be distinct from bar, and unchanged after
|
||||||
|
// modifying bar
|
||||||
|
fooState := terraform.NewState()
|
||||||
|
fooLineage := fooState.Lineage
|
||||||
|
|
||||||
|
// write a known state to foo
|
||||||
|
if err := foo.WriteState(fooState); err != nil {
|
||||||
|
t.Fatal("error writing foo state:", err)
|
||||||
|
}
|
||||||
|
if err := foo.PersistState(); err != nil {
|
||||||
|
t.Fatal("error persisting foo state:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
s.Lineage = "bar"
|
// write a distinct known state to bar
|
||||||
if err := barState.WriteState(s); err != nil {
|
if err := bar.WriteState(barState); err != nil {
|
||||||
t.Fatalf("bad: %s", err)
|
t.Fatalf("bad: %s", err)
|
||||||
}
|
}
|
||||||
if err := barState.PersistState(); err != nil {
|
if err := bar.PersistState(); err != nil {
|
||||||
t.Fatalf("bad: %s", err)
|
t.Fatalf("bad: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := fooState.RefreshState(); err != nil {
|
// verify that foo is unchanged with the existing state manager
|
||||||
t.Fatalf("bad: %s", err)
|
if err := foo.RefreshState(); err != nil {
|
||||||
|
t.Fatal("error refreshing foo:", err)
|
||||||
}
|
}
|
||||||
if v := fooState.State(); v != nil && v.Lineage == "bar" {
|
fooState = foo.State()
|
||||||
t.Fatalf("bad: %#v", v)
|
switch {
|
||||||
|
case fooState == nil:
|
||||||
|
t.Fatal("nil state read from foo")
|
||||||
|
case fooState.Lineage == barLineage:
|
||||||
|
t.Fatalf("bar lineage read from foo: %#v", fooState)
|
||||||
|
case fooState.Lineage != fooLineage:
|
||||||
|
t.Fatal("foo lineage alterred")
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch foo again from the backend
|
||||||
|
foo, err = b.State("foo")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("error re-fetching state:", err)
|
||||||
|
}
|
||||||
|
if err := foo.RefreshState(); err != nil {
|
||||||
|
t.Fatal("error refreshing foo:", err)
|
||||||
|
}
|
||||||
|
fooState = foo.State()
|
||||||
|
switch {
|
||||||
|
case fooState == nil:
|
||||||
|
t.Fatal("nil state read from foo")
|
||||||
|
case fooState.Lineage != fooLineage:
|
||||||
|
t.Fatal("incorrect state returned from backend")
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch the bar again from the backend
|
||||||
|
bar, err = b.State("bar")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("error re-fetching state:", err)
|
||||||
|
}
|
||||||
|
if err := bar.RefreshState(); err != nil {
|
||||||
|
t.Fatal("error refreshing bar:", err)
|
||||||
|
}
|
||||||
|
barState = bar.State()
|
||||||
|
switch {
|
||||||
|
case barState == nil:
|
||||||
|
t.Fatal("nil state read from bar")
|
||||||
|
case barState.Lineage != barLineage:
|
||||||
|
t.Fatal("incorrect state returned from backend")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify we can now list them
|
// Verify we can now list them
|
||||||
{
|
{
|
||||||
|
// we determined that named stated are supported earlier
|
||||||
states, err := b.States()
|
states, err := b.States()
|
||||||
if err == ErrNamedStatesNotSupported {
|
if err != nil {
|
||||||
t.Logf("TestBackend: named states not supported in %T, skipping", b)
|
t.Fatal(err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Strings(states)
|
sort.Strings(states)
|
||||||
|
|
Loading…
Reference in New Issue