Merge pull request #11187 from hashicorp/jbardin/state-locking
State Locking initial implementations
This commit is contained in:
commit
9acb86a182
|
@ -1,6 +1,7 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
@ -37,21 +38,35 @@ func TestMetaBackend_emptyDir(t *testing.T) {
|
|||
}
|
||||
|
||||
// Verify it exists where we expect it to
|
||||
if _, err := os.Stat(DefaultStateFilename); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
if isEmptyState(DefaultStateFilename) {
|
||||
t.Fatalf("no state was written")
|
||||
}
|
||||
|
||||
// Verify no backup since it was empty to start
|
||||
if _, err := os.Stat(DefaultStateFilename + DefaultBackupExtension); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
if !isEmptyState(DefaultStateFilename + DefaultBackupExtension) {
|
||||
t.Fatal("backup state should be empty")
|
||||
}
|
||||
|
||||
// Verify no backend state was made
|
||||
if _, err := os.Stat(filepath.Join(m.DataDir(), DefaultStateFilename)); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
if !isEmptyState(filepath.Join(m.DataDir(), DefaultStateFilename)) {
|
||||
t.Fatal("backend state should be empty")
|
||||
}
|
||||
}
|
||||
|
||||
// check for no state. Either the file doesn't exist, or is empty
|
||||
func isEmptyState(path string) bool {
|
||||
fi, err := os.Stat(path)
|
||||
if os.IsNotExist(err) {
|
||||
return true
|
||||
}
|
||||
|
||||
if fi.Size() == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Test a directory with a legacy state and no config continues to
|
||||
// use the legacy state.
|
||||
func TestMetaBackend_emptyWithDefaultState(t *testing.T) {
|
||||
|
@ -95,8 +110,10 @@ func TestMetaBackend_emptyWithDefaultState(t *testing.T) {
|
|||
if _, err := os.Stat(DefaultStateFilename); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(m.DataDir(), DefaultStateFilename)); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
|
||||
stateName := filepath.Join(m.DataDir(), DefaultStateFilename)
|
||||
if !isEmptyState(stateName) {
|
||||
t.Fatal("expected no state at", stateName)
|
||||
}
|
||||
|
||||
// Write some state
|
||||
|
@ -108,8 +125,8 @@ func TestMetaBackend_emptyWithDefaultState(t *testing.T) {
|
|||
}
|
||||
|
||||
// Verify a backup was made since we're modifying a pre-existing state
|
||||
if _, err := os.Stat(DefaultStateFilename + DefaultBackupExtension); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
if isEmptyState(DefaultStateFilename + DefaultBackupExtension) {
|
||||
t.Fatal("backup state should not be empty")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -161,10 +178,12 @@ func TestMetaBackend_emptyWithExplicitState(t *testing.T) {
|
|||
|
||||
// Verify neither defaults exist
|
||||
if _, err := os.Stat(DefaultStateFilename); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
t.Fatal("file should not exist")
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(m.DataDir(), DefaultStateFilename)); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
|
||||
stateName := filepath.Join(m.DataDir(), DefaultStateFilename)
|
||||
if !isEmptyState(stateName) {
|
||||
t.Fatal("expected no state at", stateName)
|
||||
}
|
||||
|
||||
// Write some state
|
||||
|
@ -176,8 +195,8 @@ func TestMetaBackend_emptyWithExplicitState(t *testing.T) {
|
|||
}
|
||||
|
||||
// Verify a backup was made since we're modifying a pre-existing state
|
||||
if _, err := os.Stat(statePath + DefaultBackupExtension); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
if isEmptyState(statePath + DefaultBackupExtension) {
|
||||
t.Fatal("backup state should not be empty")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -224,15 +243,15 @@ func TestMetaBackend_emptyLegacyRemote(t *testing.T) {
|
|||
|
||||
// Verify the default paths don't exist
|
||||
if _, err := os.Stat(DefaultStateFilename); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
t.Fatal("file should not exist")
|
||||
}
|
||||
|
||||
// Verify a backup doesn't exist
|
||||
if _, err := os.Stat(DefaultStateFilename + DefaultBackupExtension); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
t.Fatal("file should not exist")
|
||||
}
|
||||
if _, err := os.Stat(statePath + DefaultBackupExtension); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
t.Fatal("file should not exist")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -293,12 +312,12 @@ func TestMetaBackend_configureNew(t *testing.T) {
|
|||
|
||||
// Verify the default paths don't exist
|
||||
if _, err := os.Stat(DefaultStateFilename); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
t.Fatal("file should not exist")
|
||||
}
|
||||
|
||||
// Verify a backup doesn't exist
|
||||
if _, err := os.Stat(DefaultStateFilename + DefaultBackupExtension); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
t.Fatal("file should not exist")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -364,13 +383,15 @@ func TestMetaBackend_configureNewWithState(t *testing.T) {
|
|||
}
|
||||
|
||||
// Verify the default paths don't exist
|
||||
if _, err := os.Stat(DefaultStateFilename); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
if !isEmptyState(DefaultStateFilename) {
|
||||
data, _ := ioutil.ReadFile(DefaultStateFilename)
|
||||
|
||||
t.Fatal("state should not exist, but contains:\n", string(data))
|
||||
}
|
||||
|
||||
// Verify a backup does exist
|
||||
if _, err := os.Stat(DefaultStateFilename + DefaultBackupExtension); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
if isEmptyState(DefaultStateFilename + DefaultBackupExtension) {
|
||||
t.Fatal("backup state is empty or missing")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -408,13 +429,15 @@ func TestMetaBackend_configureNewWithStateNoMigrate(t *testing.T) {
|
|||
}
|
||||
|
||||
// Verify the default paths don't exist
|
||||
if _, err := os.Stat(DefaultStateFilename); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
if !isEmptyState(DefaultStateFilename) {
|
||||
data, _ := ioutil.ReadFile(DefaultStateFilename)
|
||||
|
||||
t.Fatal("state should not exist, but contains:\n", string(data))
|
||||
}
|
||||
|
||||
// Verify a backup does exist
|
||||
if _, err := os.Stat(DefaultStateFilename + DefaultBackupExtension); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
if isEmptyState(DefaultStateFilename + DefaultBackupExtension) {
|
||||
t.Fatal("backup state is empty or missing")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -480,13 +503,15 @@ func TestMetaBackend_configureNewWithStateExisting(t *testing.T) {
|
|||
}
|
||||
|
||||
// Verify the default paths don't exist
|
||||
if _, err := os.Stat(DefaultStateFilename); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
if !isEmptyState(DefaultStateFilename) {
|
||||
data, _ := ioutil.ReadFile(DefaultStateFilename)
|
||||
|
||||
t.Fatal("state should not exist, but contains:\n", string(data))
|
||||
}
|
||||
|
||||
// Verify a backup does exist
|
||||
if _, err := os.Stat(DefaultStateFilename + DefaultBackupExtension); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
if isEmptyState(DefaultStateFilename + DefaultBackupExtension) {
|
||||
t.Fatal("backup state is empty or missing")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -552,13 +577,15 @@ func TestMetaBackend_configureNewWithStateExistingNoMigrate(t *testing.T) {
|
|||
}
|
||||
|
||||
// Verify the default paths don't exist
|
||||
if _, err := os.Stat(DefaultStateFilename); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
if !isEmptyState(DefaultStateFilename) {
|
||||
data, _ := ioutil.ReadFile(DefaultStateFilename)
|
||||
|
||||
t.Fatal("state should not exist, but contains:\n", string(data))
|
||||
}
|
||||
|
||||
// Verify a backup does exist
|
||||
if _, err := os.Stat(DefaultStateFilename + DefaultBackupExtension); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
if isEmptyState(DefaultStateFilename + DefaultBackupExtension) {
|
||||
t.Fatal("backup state is empty or missing")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -642,13 +669,17 @@ func TestMetaBackend_configureNewLegacy(t *testing.T) {
|
|||
}
|
||||
|
||||
// Verify the default paths don't exist
|
||||
if _, err := os.Stat(DefaultStateFilename); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
if !isEmptyState(DefaultStateFilename) {
|
||||
data, _ := ioutil.ReadFile(DefaultStateFilename)
|
||||
|
||||
t.Fatal("state should not exist, but contains:\n", string(data))
|
||||
}
|
||||
|
||||
// Verify a backup doesn't exist
|
||||
if _, err := os.Stat(DefaultStateFilename + DefaultBackupExtension); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
if !isEmptyState(DefaultStateFilename + DefaultBackupExtension) {
|
||||
data, _ := ioutil.ReadFile(DefaultStateFilename)
|
||||
|
||||
t.Fatal("backup should be empty, but contains:\n", string(data))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -736,12 +767,12 @@ func TestMetaBackend_configureNewLegacyCopy(t *testing.T) {
|
|||
|
||||
// Verify the default paths don't exist
|
||||
if _, err := os.Stat(DefaultStateFilename); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
t.Fatal("file should not exist")
|
||||
}
|
||||
|
||||
// Verify a backup doesn't exist
|
||||
if _, err := os.Stat(DefaultStateFilename + DefaultBackupExtension); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
t.Fatal("file should not exist")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -776,12 +807,12 @@ func TestMetaBackend_configuredUnchanged(t *testing.T) {
|
|||
|
||||
// Verify the default paths don't exist
|
||||
if _, err := os.Stat(DefaultStateFilename); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
t.Fatal("file should not exist")
|
||||
}
|
||||
|
||||
// Verify a backup doesn't exist
|
||||
if _, err := os.Stat(DefaultStateFilename + DefaultBackupExtension); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
t.Fatal("file should not exist")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -820,12 +851,12 @@ func TestMetaBackend_configuredChange(t *testing.T) {
|
|||
|
||||
// Verify the default paths don't exist
|
||||
if _, err := os.Stat(DefaultStateFilename); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
t.Fatal("file should not exist")
|
||||
}
|
||||
|
||||
// Verify a backup doesn't exist
|
||||
if _, err := os.Stat(DefaultStateFilename + DefaultBackupExtension); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
t.Fatal("file should not exist")
|
||||
}
|
||||
|
||||
// Write some state
|
||||
|
@ -855,12 +886,12 @@ func TestMetaBackend_configuredChange(t *testing.T) {
|
|||
|
||||
// Verify no local state
|
||||
if _, err := os.Stat(DefaultStateFilename); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
t.Fatal("file should not exist")
|
||||
}
|
||||
|
||||
// Verify no local backup
|
||||
if _, err := os.Stat(DefaultStateFilename + DefaultBackupExtension); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
t.Fatal("file should not exist")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -902,12 +933,12 @@ func TestMetaBackend_configuredChangeCopy(t *testing.T) {
|
|||
|
||||
// Verify no local state
|
||||
if _, err := os.Stat(DefaultStateFilename); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
t.Fatal("file should not exist")
|
||||
}
|
||||
|
||||
// Verify no local backup
|
||||
if _, err := os.Stat(DefaultStateFilename + DefaultBackupExtension); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
t.Fatal("file should not exist")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -945,13 +976,15 @@ func TestMetaBackend_configuredUnset(t *testing.T) {
|
|||
}
|
||||
|
||||
// Verify the default paths don't exist
|
||||
if _, err := os.Stat(DefaultStateFilename); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
if !isEmptyState(DefaultStateFilename) {
|
||||
data, _ := ioutil.ReadFile(DefaultStateFilename)
|
||||
t.Fatal("state should not exist, but contains:\n", string(data))
|
||||
}
|
||||
|
||||
// Verify a backup doesn't exist
|
||||
if _, err := os.Stat(DefaultStateFilename + DefaultBackupExtension); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
if !isEmptyState(DefaultStateFilename + DefaultBackupExtension) {
|
||||
data, _ := ioutil.ReadFile(DefaultStateFilename + DefaultBackupExtension)
|
||||
t.Fatal("backup should not exist, but contains:\n", string(data))
|
||||
}
|
||||
|
||||
// Verify we have no configured backend/legacy
|
||||
|
@ -982,13 +1015,14 @@ func TestMetaBackend_configuredUnset(t *testing.T) {
|
|||
}
|
||||
|
||||
// Verify it exists where we expect it to
|
||||
if _, err := os.Stat(DefaultStateFilename); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
if isEmptyState(DefaultStateFilename) {
|
||||
t.Fatal(DefaultStateFilename, "is empty")
|
||||
}
|
||||
|
||||
// Verify no backup since it was empty to start
|
||||
if _, err := os.Stat(DefaultStateFilename + DefaultBackupExtension); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
if !isEmptyState(DefaultStateFilename + DefaultBackupExtension) {
|
||||
data, _ := ioutil.ReadFile(DefaultStateFilename + DefaultBackupExtension)
|
||||
t.Fatal("backup state should be empty, but contains:\n", string(data))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1029,8 +1063,8 @@ func TestMetaBackend_configuredUnsetCopy(t *testing.T) {
|
|||
}
|
||||
|
||||
// Verify a backup doesn't exist
|
||||
if _, err := os.Stat(DefaultStateFilename + DefaultBackupExtension); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
if !isEmptyState(DefaultStateFilename + DefaultBackupExtension) {
|
||||
t.Fatalf("backup state should be empty")
|
||||
}
|
||||
|
||||
// Verify we have no configured backend/legacy
|
||||
|
@ -1066,8 +1100,8 @@ func TestMetaBackend_configuredUnsetCopy(t *testing.T) {
|
|||
}
|
||||
|
||||
// Verify a backup since it wasn't empty to start
|
||||
if _, err := os.Stat(DefaultStateFilename + DefaultBackupExtension); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
if isEmptyState(DefaultStateFilename + DefaultBackupExtension) {
|
||||
t.Fatal("backup is empty")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1114,7 +1148,7 @@ func TestMetaBackend_configuredUnchangedLegacy(t *testing.T) {
|
|||
|
||||
// Verify a backup doesn't exist
|
||||
if _, err := os.Stat(DefaultStateFilename + DefaultBackupExtension); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
t.Fatal("file should not exist")
|
||||
}
|
||||
|
||||
// Verify we have no configured legacy
|
||||
|
@ -1165,12 +1199,12 @@ func TestMetaBackend_configuredUnchangedLegacy(t *testing.T) {
|
|||
|
||||
// Verify no local state
|
||||
if _, err := os.Stat(DefaultStateFilename); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
t.Fatal("file should not exist")
|
||||
}
|
||||
|
||||
// Verify no local backup
|
||||
if _, err := os.Stat(DefaultStateFilename + DefaultBackupExtension); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
t.Fatal("file should not exist")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1212,12 +1246,12 @@ func TestMetaBackend_configuredUnchangedLegacyCopy(t *testing.T) {
|
|||
|
||||
// Verify the default paths don't exist
|
||||
if _, err := os.Stat(DefaultStateFilename); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
t.Fatal("file should not exist")
|
||||
}
|
||||
|
||||
// Verify a backup doesn't exist
|
||||
if _, err := os.Stat(DefaultStateFilename + DefaultBackupExtension); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
t.Fatal("file should not exist")
|
||||
}
|
||||
|
||||
// Verify we have no configured legacy
|
||||
|
@ -1268,12 +1302,12 @@ func TestMetaBackend_configuredUnchangedLegacyCopy(t *testing.T) {
|
|||
|
||||
// Verify no local state
|
||||
if _, err := os.Stat(DefaultStateFilename); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
t.Fatal("file should not exist")
|
||||
}
|
||||
|
||||
// Verify no local backup
|
||||
if _, err := os.Stat(DefaultStateFilename + DefaultBackupExtension); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
t.Fatal("file should not exist")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1312,12 +1346,12 @@ func TestMetaBackend_configuredChangedLegacy(t *testing.T) {
|
|||
|
||||
// Verify the default paths don't exist
|
||||
if _, err := os.Stat(DefaultStateFilename); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
t.Fatal("file should not exist")
|
||||
}
|
||||
|
||||
// Verify a backup doesn't exist
|
||||
if _, err := os.Stat(DefaultStateFilename + DefaultBackupExtension); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
t.Fatal("file should not exist")
|
||||
}
|
||||
|
||||
// Verify we have no configured legacy
|
||||
|
@ -1368,12 +1402,12 @@ func TestMetaBackend_configuredChangedLegacy(t *testing.T) {
|
|||
|
||||
// Verify no local state
|
||||
if _, err := os.Stat(DefaultStateFilename); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
t.Fatal("file should not exist")
|
||||
}
|
||||
|
||||
// Verify no local backup
|
||||
if _, err := os.Stat(DefaultStateFilename + DefaultBackupExtension); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
t.Fatal("file should not exist")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1415,12 +1449,12 @@ func TestMetaBackend_configuredChangedLegacyCopyBackend(t *testing.T) {
|
|||
|
||||
// Verify the default paths don't exist
|
||||
if _, err := os.Stat(DefaultStateFilename); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
t.Fatal("file should not exist")
|
||||
}
|
||||
|
||||
// Verify a backup doesn't exist
|
||||
if _, err := os.Stat(DefaultStateFilename + DefaultBackupExtension); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
t.Fatal("file should not exist")
|
||||
}
|
||||
|
||||
// Verify we have no configured legacy
|
||||
|
@ -1471,12 +1505,12 @@ func TestMetaBackend_configuredChangedLegacyCopyBackend(t *testing.T) {
|
|||
|
||||
// Verify no local state
|
||||
if _, err := os.Stat(DefaultStateFilename); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
t.Fatal("file should not exist")
|
||||
}
|
||||
|
||||
// Verify no local backup
|
||||
if _, err := os.Stat(DefaultStateFilename + DefaultBackupExtension); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
t.Fatal("file should not exist")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1518,12 +1552,12 @@ func TestMetaBackend_configuredChangedLegacyCopyLegacy(t *testing.T) {
|
|||
|
||||
// Verify the default paths don't exist
|
||||
if _, err := os.Stat(DefaultStateFilename); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
t.Fatal("file should not exist")
|
||||
}
|
||||
|
||||
// Verify a backup doesn't exist
|
||||
if _, err := os.Stat(DefaultStateFilename + DefaultBackupExtension); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
t.Fatal("file should not exist")
|
||||
}
|
||||
|
||||
// Verify we have no configured legacy
|
||||
|
@ -1574,12 +1608,12 @@ func TestMetaBackend_configuredChangedLegacyCopyLegacy(t *testing.T) {
|
|||
|
||||
// Verify no local state
|
||||
if _, err := os.Stat(DefaultStateFilename); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
t.Fatal("file should not exist")
|
||||
}
|
||||
|
||||
// Verify no local backup
|
||||
if _, err := os.Stat(DefaultStateFilename + DefaultBackupExtension); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
t.Fatal("file should not exist")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1621,12 +1655,12 @@ func TestMetaBackend_configuredChangedLegacyCopyBoth(t *testing.T) {
|
|||
|
||||
// Verify the default paths don't exist
|
||||
if _, err := os.Stat(DefaultStateFilename); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
t.Fatal("file should not exist")
|
||||
}
|
||||
|
||||
// Verify a backup doesn't exist
|
||||
if _, err := os.Stat(DefaultStateFilename + DefaultBackupExtension); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
t.Fatal("file should not exist")
|
||||
}
|
||||
|
||||
// Verify we have no configured legacy
|
||||
|
@ -1677,12 +1711,12 @@ func TestMetaBackend_configuredChangedLegacyCopyBoth(t *testing.T) {
|
|||
|
||||
// Verify no local state
|
||||
if _, err := os.Stat(DefaultStateFilename); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
t.Fatal("file should not exist")
|
||||
}
|
||||
|
||||
// Verify no local backup
|
||||
if _, err := os.Stat(DefaultStateFilename + DefaultBackupExtension); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
t.Fatal("file should not exist")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1720,13 +1754,13 @@ func TestMetaBackend_configuredUnsetWithLegacyNoCopy(t *testing.T) {
|
|||
}
|
||||
|
||||
// Verify the default paths dont exist since we had no state
|
||||
if _, err := os.Stat(DefaultStateFilename); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
if !isEmptyState(DefaultStateFilename) {
|
||||
t.Fatal("state should be empty")
|
||||
}
|
||||
|
||||
// Verify a backup doesn't exist
|
||||
if _, err := os.Stat(DefaultStateFilename + DefaultBackupExtension); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
if !isEmptyState(DefaultStateFilename + DefaultBackupExtension) {
|
||||
t.Fatal("backup should be empty")
|
||||
}
|
||||
|
||||
// Verify we have no configured backend/legacy
|
||||
|
@ -1813,13 +1847,13 @@ func TestMetaBackend_configuredUnsetWithLegacyCopyBackend(t *testing.T) {
|
|||
}
|
||||
|
||||
// Verify the default paths exist
|
||||
if _, err := os.Stat(DefaultStateFilename); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
if isEmptyState(DefaultStateFilename) {
|
||||
t.Fatalf("default state was empty")
|
||||
}
|
||||
|
||||
// Verify a backup doesn't exist
|
||||
if _, err := os.Stat(DefaultStateFilename + DefaultBackupExtension); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
if !isEmptyState(DefaultStateFilename + DefaultBackupExtension) {
|
||||
t.Fatal("backupstate should be empty")
|
||||
}
|
||||
|
||||
// Verify we have no configured backend/legacy
|
||||
|
@ -1869,8 +1903,8 @@ func TestMetaBackend_configuredUnsetWithLegacyCopyBackend(t *testing.T) {
|
|||
}
|
||||
|
||||
// Verify a local backup
|
||||
if _, err := os.Stat(DefaultStateFilename + DefaultBackupExtension); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
if isEmptyState(DefaultStateFilename + DefaultBackupExtension) {
|
||||
t.Fatal("backup is empty")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1911,13 +1945,13 @@ func TestMetaBackend_configuredUnsetWithLegacyCopyLegacy(t *testing.T) {
|
|||
}
|
||||
|
||||
// Verify the default paths exist
|
||||
if _, err := os.Stat(DefaultStateFilename); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
if isEmptyState(DefaultStateFilename) {
|
||||
t.Fatalf("default state was empty")
|
||||
}
|
||||
|
||||
// Verify a backup doesn't exist
|
||||
if _, err := os.Stat(DefaultStateFilename + DefaultBackupExtension); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
if !isEmptyState(DefaultStateFilename + DefaultBackupExtension) {
|
||||
t.Fatal("backupstate should be empty")
|
||||
}
|
||||
|
||||
// Verify we have no configured backend/legacy
|
||||
|
@ -1967,8 +2001,8 @@ func TestMetaBackend_configuredUnsetWithLegacyCopyLegacy(t *testing.T) {
|
|||
}
|
||||
|
||||
// Verify a local backup
|
||||
if _, err := os.Stat(DefaultStateFilename + DefaultBackupExtension); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
if isEmptyState(DefaultStateFilename + DefaultBackupExtension) {
|
||||
t.Fatal("backup is empty")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2009,13 +2043,13 @@ func TestMetaBackend_configuredUnsetWithLegacyCopyBoth(t *testing.T) {
|
|||
}
|
||||
|
||||
// Verify the default paths exist
|
||||
if _, err := os.Stat(DefaultStateFilename); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
if isEmptyState(DefaultStateFilename) {
|
||||
t.Fatal("state is empty")
|
||||
}
|
||||
|
||||
// Verify a backup exists
|
||||
if _, err := os.Stat(DefaultStateFilename + DefaultBackupExtension); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
if isEmptyState(DefaultStateFilename + DefaultBackupExtension) {
|
||||
t.Fatal("backup is empty")
|
||||
}
|
||||
|
||||
// Verify we have no configured backend/legacy
|
||||
|
@ -2065,8 +2099,8 @@ func TestMetaBackend_configuredUnsetWithLegacyCopyBoth(t *testing.T) {
|
|||
}
|
||||
|
||||
// Verify a local backup
|
||||
if _, err := os.Stat(DefaultStateFilename + DefaultBackupExtension); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
if isEmptyState(DefaultStateFilename + DefaultBackupExtension) {
|
||||
t.Fatal("backup is empty")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2106,14 +2140,14 @@ func TestMetaBackend_planLocal(t *testing.T) {
|
|||
t.Fatalf("state should be nil: %#v", state)
|
||||
}
|
||||
|
||||
// Verify the default path
|
||||
if _, err := os.Stat(DefaultStateFilename); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
// Verify the default path doens't exist
|
||||
if !isEmptyState(DefaultStateFilename) {
|
||||
t.Fatal("expected empty state")
|
||||
}
|
||||
|
||||
// Verify a backup doesn't exists
|
||||
if _, err := os.Stat(DefaultStateFilename + DefaultBackupExtension); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
if !isEmptyState(DefaultStateFilename + DefaultBackupExtension) {
|
||||
t.Fatal("expected empty backup")
|
||||
}
|
||||
|
||||
// Verify we have no configured backend/legacy
|
||||
|
@ -2148,8 +2182,8 @@ func TestMetaBackend_planLocal(t *testing.T) {
|
|||
}
|
||||
|
||||
// Verify no local backup
|
||||
if _, err := os.Stat(DefaultStateFilename + DefaultBackupExtension); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
if !isEmptyState(DefaultStateFilename + DefaultBackupExtension) {
|
||||
t.Fatalf("backup state should be empty")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2207,7 +2241,7 @@ func TestMetaBackend_planLocalStatePath(t *testing.T) {
|
|||
|
||||
// Verify a backup doesn't exists
|
||||
if _, err := os.Stat(DefaultStateFilename + DefaultBackupExtension); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
t.Fatal("file should not exist")
|
||||
}
|
||||
|
||||
// Verify we have no configured backend/legacy
|
||||
|
@ -2242,8 +2276,8 @@ func TestMetaBackend_planLocalStatePath(t *testing.T) {
|
|||
}
|
||||
|
||||
// Verify we have a backup
|
||||
if _, err := os.Stat(statePath + DefaultBackupExtension); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
if isEmptyState(statePath + DefaultBackupExtension) {
|
||||
t.Fatal("backup is empty")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2287,8 +2321,8 @@ func TestMetaBackend_planLocalMatch(t *testing.T) {
|
|||
}
|
||||
|
||||
// Verify the default path
|
||||
if _, err := os.Stat(DefaultStateFilename); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
if isEmptyState(DefaultStateFilename) {
|
||||
t.Fatal("state is empty")
|
||||
}
|
||||
|
||||
// Verify a backup exists
|
||||
|
@ -2328,8 +2362,8 @@ func TestMetaBackend_planLocalMatch(t *testing.T) {
|
|||
}
|
||||
|
||||
// Verify local backup
|
||||
if _, err := os.Stat(DefaultStateFilename + DefaultBackupExtension); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
if isEmptyState(DefaultStateFilename + DefaultBackupExtension) {
|
||||
t.Fatal("backup is empty")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2374,7 +2408,7 @@ func TestMetaBackend_planLocalMismatchLineage(t *testing.T) {
|
|||
|
||||
// Verify a backup doesn't exists
|
||||
if _, err := os.Stat(DefaultStateFilename + DefaultBackupExtension); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
t.Fatal("file should not exist")
|
||||
}
|
||||
|
||||
// Verify we have no configured backend/legacy
|
||||
|
@ -2426,7 +2460,7 @@ func TestMetaBackend_planLocalNewer(t *testing.T) {
|
|||
|
||||
// Verify a backup doesn't exists
|
||||
if _, err := os.Stat(DefaultStateFilename + DefaultBackupExtension); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
t.Fatal("file should not exist")
|
||||
}
|
||||
|
||||
// Verify we have no configured backend/legacy
|
||||
|
@ -2486,14 +2520,14 @@ func TestMetaBackend_planBackendEmptyDir(t *testing.T) {
|
|||
t.Fatalf("bad: %#v", state)
|
||||
}
|
||||
|
||||
// Verify the default path
|
||||
if _, err := os.Stat(DefaultStateFilename); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
// Verify the default path doesn't exist
|
||||
if !isEmptyState(DefaultStateFilename) {
|
||||
t.Fatal("state is not empty")
|
||||
}
|
||||
|
||||
// Verify a backup exists
|
||||
if _, err := os.Stat(DefaultStateFilename + DefaultBackupExtension); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
// Verify a backup doesn't exist
|
||||
if !isEmptyState(DefaultStateFilename + DefaultBackupExtension) {
|
||||
t.Fatal("backup is not empty")
|
||||
}
|
||||
|
||||
// Verify we have no configured backend/legacy
|
||||
|
@ -2529,12 +2563,12 @@ func TestMetaBackend_planBackendEmptyDir(t *testing.T) {
|
|||
|
||||
// Verify no default path
|
||||
if _, err := os.Stat(DefaultStateFilename); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
t.Fatal("file should not exist")
|
||||
}
|
||||
|
||||
// Verify no local backup
|
||||
if _, err := os.Stat(DefaultStateFilename + DefaultBackupExtension); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
t.Fatal("file should not exist")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2588,14 +2622,14 @@ func TestMetaBackend_planBackendMatch(t *testing.T) {
|
|||
t.Fatalf("bad: %#v", state)
|
||||
}
|
||||
|
||||
// Verify the default path
|
||||
// Verify the default path exists
|
||||
if _, err := os.Stat(DefaultStateFilename); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
t.Fatal("file should not exist")
|
||||
}
|
||||
|
||||
// Verify a backup exists
|
||||
// Verify a backup doesn't exist
|
||||
if _, err := os.Stat(DefaultStateFilename + DefaultBackupExtension); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
t.Fatal("file should not exist")
|
||||
}
|
||||
|
||||
// Verify we have no configured backend/legacy
|
||||
|
@ -2631,12 +2665,12 @@ func TestMetaBackend_planBackendMatch(t *testing.T) {
|
|||
|
||||
// Verify no default path
|
||||
if _, err := os.Stat(DefaultStateFilename); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
t.Fatal("file should not exist")
|
||||
}
|
||||
|
||||
// Verify no local backup
|
||||
if _, err := os.Stat(DefaultStateFilename + DefaultBackupExtension); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
t.Fatal("file should not exist")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2686,9 +2720,9 @@ func TestMetaBackend_planBackendMismatchLineage(t *testing.T) {
|
|||
t.Fatalf("bad: %#v", actual)
|
||||
}
|
||||
|
||||
// Verify a backup doesn't exists
|
||||
// Verify a backup doesn't exist
|
||||
if _, err := os.Stat(DefaultStateFilename + DefaultBackupExtension); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
t.Fatal("file should not exist")
|
||||
}
|
||||
|
||||
// Verify we have no configured backend/legacy
|
||||
|
@ -2699,7 +2733,7 @@ func TestMetaBackend_planBackendMismatchLineage(t *testing.T) {
|
|||
|
||||
// Verify we have no default state
|
||||
if _, err := os.Stat(DefaultStateFilename); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
t.Fatal("file should not exist")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2753,12 +2787,12 @@ func TestMetaBackend_planLegacy(t *testing.T) {
|
|||
|
||||
// Verify the default path
|
||||
if _, err := os.Stat(DefaultStateFilename); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
t.Fatal("file should not exist")
|
||||
}
|
||||
|
||||
// Verify a backup doesn't exist
|
||||
if _, err := os.Stat(DefaultStateFilename + DefaultBackupExtension); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
t.Fatal("file should not exist")
|
||||
}
|
||||
|
||||
// Verify we have no configured backend/legacy
|
||||
|
@ -2794,12 +2828,12 @@ func TestMetaBackend_planLegacy(t *testing.T) {
|
|||
|
||||
// Verify no default path
|
||||
if _, err := os.Stat(DefaultStateFilename); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
t.Fatal("file should not exist")
|
||||
}
|
||||
|
||||
// Verify no local backup
|
||||
if _, err := os.Stat(DefaultStateFilename + DefaultBackupExtension); err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
t.Fatal("file should not exist")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
package state
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
import "github.com/hashicorp/terraform/terraform"
|
||||
|
||||
// BackupState wraps a State that backs up the state on the first time that
|
||||
// a WriteState or PersistState is called.
|
||||
|
@ -43,6 +41,21 @@ func (s *BackupState) PersistState() error {
|
|||
return s.Real.PersistState()
|
||||
}
|
||||
|
||||
// all states get wrapped by BackupState, so it has to be a Locker
|
||||
func (s *BackupState) Lock(reason string) error {
|
||||
if s, ok := s.Real.(Locker); ok {
|
||||
return s.Lock(reason)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *BackupState) Unlock() error {
|
||||
if s, ok := s.Real.(Locker); ok {
|
||||
return s.Unlock()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *BackupState) backup() error {
|
||||
state := s.Real.State()
|
||||
if state == nil {
|
||||
|
@ -53,9 +66,14 @@ func (s *BackupState) backup() error {
|
|||
state = s.Real.State()
|
||||
}
|
||||
|
||||
ls := &LocalState{Path: s.Path}
|
||||
if err := ls.WriteState(state); err != nil {
|
||||
return err
|
||||
// LocalState.WriteState ensures that a file always exists for locking
|
||||
// purposes, but we don't need a backup or lock if the state is empty, so
|
||||
// skip this with a nil state.
|
||||
if state != nil {
|
||||
ls := &LocalState{Path: s.Path}
|
||||
if err := ls.WriteState(state); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
s.done = true
|
||||
|
|
|
@ -3,6 +3,7 @@ package state
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
multierror "github.com/hashicorp/go-multierror"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
|
@ -16,6 +17,56 @@ type CacheState struct {
|
|||
state *terraform.State
|
||||
}
|
||||
|
||||
// Locker implementation.
|
||||
// Since remote states are wrapped in a CacheState, we need to implement the
|
||||
// Lock/Unlock methods here to delegate them to the remote client.
|
||||
func (s *CacheState) Lock(reason string) error {
|
||||
durable, durableIsLocker := s.Durable.(Locker)
|
||||
cache, cacheIsLocker := s.Cache.(Locker)
|
||||
|
||||
if durableIsLocker {
|
||||
if err := durable.Lock(reason); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// We try to lock the Cache too, which is usually a local file. This also
|
||||
// protects against multiple local processes if the remote state doesn't
|
||||
// support locking.
|
||||
if cacheIsLocker {
|
||||
if err := cache.Lock(reason); err != nil {
|
||||
// try to unlock Durable if this failed
|
||||
if unlockErr := durable.Unlock(); unlockErr != nil {
|
||||
err = multierror.Append(err, unlockErr)
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unlock unlocks both the Durable and Cache states.
|
||||
func (s *CacheState) Unlock() error {
|
||||
durable, durableIsLocker := s.Durable.(Locker)
|
||||
cache, cacheIsLocker := s.Cache.(Locker)
|
||||
|
||||
var err error
|
||||
if durableIsLocker {
|
||||
if unlockErr := durable.Unlock(); unlockErr != nil {
|
||||
err = multierror.Append(err, unlockErr)
|
||||
}
|
||||
}
|
||||
|
||||
if cacheIsLocker {
|
||||
if unlockErr := cache.Unlock(); unlockErr != nil {
|
||||
err = multierror.Append(err, unlockErr)
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// StateReader impl.
|
||||
func (s *CacheState) State() *terraform.State {
|
||||
return s.state.DeepCopy()
|
||||
|
|
223
state/local.go
223
state/local.go
|
@ -1,12 +1,36 @@
|
|||
package state
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
// lock metadata structure for local locks
|
||||
type lockInfo struct {
|
||||
// Path to the state file
|
||||
Path string
|
||||
// The time the lock was taken
|
||||
Created time.Time
|
||||
// The time this lock expires
|
||||
Expires time.Time
|
||||
// The lock reason passed to State.Lock
|
||||
Reason string
|
||||
}
|
||||
|
||||
// return the lock info formatted in an error
|
||||
func (l *lockInfo) Err() error {
|
||||
return fmt.Errorf("state file %q locked. created:%s, expires:%s, reason:%s",
|
||||
l.Path, l.Created, l.Expires, l.Reason)
|
||||
}
|
||||
|
||||
// LocalState manages a state storage that is local to the filesystem.
|
||||
type LocalState struct {
|
||||
// Path is the path to read the state from. PathOut is the path to
|
||||
|
@ -15,6 +39,9 @@ type LocalState struct {
|
|||
Path string
|
||||
PathOut string
|
||||
|
||||
// the file handle corresponding to PathOut
|
||||
stateFileOut *os.File
|
||||
|
||||
state *terraform.State
|
||||
readState *terraform.State
|
||||
written bool
|
||||
|
@ -31,42 +58,89 @@ func (s *LocalState) State() *terraform.State {
|
|||
return s.state.DeepCopy()
|
||||
}
|
||||
|
||||
// WriteState for LocalState always persists the state as well.
|
||||
//
|
||||
// StateWriter impl.
|
||||
func (s *LocalState) WriteState(state *terraform.State) error {
|
||||
s.state = state
|
||||
|
||||
path := s.PathOut
|
||||
if path == "" {
|
||||
path = s.Path
|
||||
}
|
||||
|
||||
// If we don't have any state, we actually delete the file if it exists
|
||||
if state == nil {
|
||||
err := os.Remove(path)
|
||||
if err != nil && os.IsNotExist(err) {
|
||||
return nil
|
||||
// Lock implements a local filesystem state.Locker.
|
||||
func (s *LocalState) Lock(reason string) error {
|
||||
if s.stateFileOut == nil {
|
||||
if err := s.createStateFiles(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Create all the directories
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
|
||||
return err
|
||||
if err := s.lock(); err != nil {
|
||||
if info, err := s.lockInfo(); err == nil {
|
||||
return info.Err()
|
||||
}
|
||||
return fmt.Errorf("state file %q locked: %s", s.Path, err)
|
||||
}
|
||||
|
||||
f, err := os.Create(path)
|
||||
return s.writeLockInfo(reason)
|
||||
}
|
||||
|
||||
func (s *LocalState) Unlock() error {
|
||||
os.Remove(s.lockInfoPath())
|
||||
return s.unlock()
|
||||
}
|
||||
|
||||
// Open the state file, creating the directories and file as needed.
|
||||
func (s *LocalState) createStateFiles() error {
|
||||
if s.PathOut == "" {
|
||||
s.PathOut = s.Path
|
||||
}
|
||||
|
||||
f, err := createFileAndDirs(s.PathOut)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
s.stateFileOut = f
|
||||
return nil
|
||||
}
|
||||
|
||||
func createFileAndDirs(path string) (*os.File, error) {
|
||||
// Create all the directories
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0666)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// WriteState for LocalState always persists the state as well.
|
||||
// TODO: this should use a more robust method of writing state, by first
|
||||
// writing to a temp file on the same filesystem, and renaming the file over
|
||||
// the original.
|
||||
//
|
||||
// StateWriter impl.
|
||||
func (s *LocalState) WriteState(state *terraform.State) error {
|
||||
if s.stateFileOut == nil {
|
||||
if err := s.createStateFiles(); err != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
defer s.stateFileOut.Sync()
|
||||
|
||||
s.state = state
|
||||
|
||||
if _, err := s.stateFileOut.Seek(0, os.SEEK_SET); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.stateFileOut.Truncate(0); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if state == nil {
|
||||
// if we have no state, don't write anything else.
|
||||
return nil
|
||||
}
|
||||
|
||||
s.state.IncrementSerialMaybe(s.readState)
|
||||
s.readState = s.state
|
||||
|
||||
if err := terraform.WriteState(s.state, f); err != nil {
|
||||
if err := terraform.WriteState(s.state, s.stateFileOut); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -83,33 +157,90 @@ func (s *LocalState) PersistState() error {
|
|||
|
||||
// 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)
|
||||
var reader io.Reader
|
||||
if !s.written {
|
||||
// we haven't written a state file yet, so load from Path
|
||||
f, err := os.Open(s.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
// It is okay if the file doesn't exist, we treat that as a nil state
|
||||
if !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
// we need a non-nil reader for ReadState and an empty buffer works
|
||||
// to return EOF immediately
|
||||
reader = bytes.NewBuffer(nil)
|
||||
|
||||
} else {
|
||||
defer f.Close()
|
||||
reader = f
|
||||
}
|
||||
} else {
|
||||
// we have a state file, make sure we're at the start
|
||||
s.stateFileOut.Seek(0, os.SEEK_SET)
|
||||
reader = s.stateFileOut
|
||||
}
|
||||
|
||||
state, err := terraform.ReadState(reader)
|
||||
// if there's no state we just assign the nil return value
|
||||
if err != nil && err != terraform.ErrNoState {
|
||||
return err
|
||||
}
|
||||
|
||||
s.state = state
|
||||
s.readState = state
|
||||
return nil
|
||||
}
|
||||
|
||||
// return the path for the lockInfo metadata.
|
||||
func (s *LocalState) lockInfoPath() string {
|
||||
stateDir, stateName := filepath.Split(s.Path)
|
||||
if stateName == "" {
|
||||
panic("empty state file path")
|
||||
}
|
||||
|
||||
if stateName[0] == '.' {
|
||||
stateName = stateName[1:]
|
||||
}
|
||||
|
||||
return filepath.Join(stateDir, fmt.Sprintf(".%s.lock.info", stateName))
|
||||
}
|
||||
|
||||
// lockInfo returns the data in a lock info file
|
||||
func (s *LocalState) lockInfo() (*lockInfo, error) {
|
||||
path := s.lockInfoPath()
|
||||
infoData, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := lockInfo{}
|
||||
err = json.Unmarshal(infoData, &info)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("state file %q locked, but could not unmarshal lock info: %s", s.Path, err)
|
||||
}
|
||||
return &info, nil
|
||||
}
|
||||
|
||||
// write a new lock info file
|
||||
func (s *LocalState) writeLockInfo(reason string) error {
|
||||
path := s.lockInfoPath()
|
||||
|
||||
lockInfo := &lockInfo{
|
||||
Path: s.Path,
|
||||
Created: time.Now().UTC(),
|
||||
Expires: time.Now().Add(time.Hour).UTC(),
|
||||
Reason: reason,
|
||||
}
|
||||
|
||||
infoData, err := json.Marshal(lockInfo)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("could not marshal lock info: %#v", lockInfo))
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(path, infoData, 0600)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not write lock info for %q: %s", s.Path, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
// +build !windows
|
||||
|
||||
package state
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// use fcntl POSIX locks for the most consistent behavior across platforms, and
|
||||
// hopefully some campatibility over NFS and CIFS.
|
||||
func (s *LocalState) lock() error {
|
||||
flock := &syscall.Flock_t{
|
||||
Type: syscall.F_RDLCK | syscall.F_WRLCK,
|
||||
Whence: int16(os.SEEK_SET),
|
||||
Start: 0,
|
||||
Len: 0,
|
||||
}
|
||||
|
||||
fd := s.stateFileOut.Fd()
|
||||
return syscall.FcntlFlock(fd, syscall.F_SETLK, flock)
|
||||
}
|
||||
|
||||
func (s *LocalState) unlock() error {
|
||||
flock := &syscall.Flock_t{
|
||||
Type: syscall.F_UNLCK,
|
||||
Whence: int16(os.SEEK_SET),
|
||||
Start: 0,
|
||||
Len: 0,
|
||||
}
|
||||
|
||||
fd := s.stateFileOut.Fd()
|
||||
return syscall.FcntlFlock(fd, syscall.F_SETLK, flock)
|
||||
}
|
|
@ -0,0 +1,150 @@
|
|||
// +build windows
|
||||
|
||||
package state
|
||||
|
||||
import (
|
||||
"math"
|
||||
"os"
|
||||
"sync"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type stateLock struct {
|
||||
handle syscall.Handle
|
||||
}
|
||||
|
||||
var (
|
||||
modkernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||
procLockFileEx = modkernel32.NewProc("LockFileEx")
|
||||
procCreateEventW = modkernel32.NewProc("CreateEventW")
|
||||
|
||||
lockedFilesMu sync.Mutex
|
||||
lockedFiles = map[*os.File]syscall.Handle{}
|
||||
)
|
||||
|
||||
const (
|
||||
// dwFlags defined for LockFileEx
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa365203(v=vs.85).aspx
|
||||
_LOCKFILE_FAIL_IMMEDIATELY = 1
|
||||
_LOCKFILE_EXCLUSIVE_LOCK = 2
|
||||
)
|
||||
|
||||
func (s *LocalState) lock() error {
|
||||
lockedFilesMu.Lock()
|
||||
defer lockedFilesMu.Unlock()
|
||||
|
||||
name, err := syscall.UTF16PtrFromString(s.PathOut)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
handle, err := syscall.CreateFile(
|
||||
name,
|
||||
syscall.GENERIC_READ|syscall.GENERIC_WRITE,
|
||||
// since this file is already open in out process, we need shared
|
||||
// access here for this call.
|
||||
syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE,
|
||||
nil,
|
||||
syscall.OPEN_EXISTING,
|
||||
syscall.FILE_ATTRIBUTE_NORMAL,
|
||||
0,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
lockedFiles[s.stateFileOut] = handle
|
||||
|
||||
// even though we're failing immediately, an overlapped event structure is
|
||||
// required
|
||||
ol, err := newOverlapped()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer syscall.CloseHandle(ol.HEvent)
|
||||
|
||||
return lockFileEx(
|
||||
handle,
|
||||
_LOCKFILE_EXCLUSIVE_LOCK|_LOCKFILE_FAIL_IMMEDIATELY,
|
||||
0, // reserved
|
||||
0, // bytes low
|
||||
math.MaxUint32, // bytes high
|
||||
ol,
|
||||
)
|
||||
}
|
||||
|
||||
func (s *LocalState) unlock() error {
|
||||
lockedFilesMu.Lock()
|
||||
defer lockedFilesMu.Unlock()
|
||||
|
||||
handle, ok := lockedFiles[s.stateFileOut]
|
||||
if !ok {
|
||||
// we allow multiple Unlock calls
|
||||
return nil
|
||||
}
|
||||
delete(lockedFiles, s.stateFileOut)
|
||||
return syscall.Close(handle)
|
||||
}
|
||||
|
||||
func lockFileEx(h syscall.Handle, flags, reserved, locklow, lockhigh uint32, ol *syscall.Overlapped) (err error) {
|
||||
r1, _, e1 := syscall.Syscall6(
|
||||
procLockFileEx.Addr(),
|
||||
6,
|
||||
uintptr(h),
|
||||
uintptr(flags),
|
||||
uintptr(reserved),
|
||||
uintptr(locklow),
|
||||
uintptr(lockhigh),
|
||||
uintptr(unsafe.Pointer(ol)),
|
||||
)
|
||||
if r1 == 0 {
|
||||
if e1 != 0 {
|
||||
err = error(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// newOverlapped creates a structure used to track asynchronous
|
||||
// I/O requests that have been issued.
|
||||
func newOverlapped() (*syscall.Overlapped, error) {
|
||||
event, err := createEvent(nil, true, false, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &syscall.Overlapped{HEvent: event}, nil
|
||||
}
|
||||
|
||||
func createEvent(sa *syscall.SecurityAttributes, manualReset bool, initialState bool, name *uint16) (handle syscall.Handle, err error) {
|
||||
var _p0 uint32
|
||||
if manualReset {
|
||||
_p0 = 1
|
||||
}
|
||||
var _p1 uint32
|
||||
if initialState {
|
||||
_p1 = 1
|
||||
}
|
||||
|
||||
r0, _, e1 := syscall.Syscall6(
|
||||
procCreateEventW.Addr(),
|
||||
4,
|
||||
uintptr(unsafe.Pointer(sa)),
|
||||
uintptr(_p0),
|
||||
uintptr(_p1),
|
||||
uintptr(unsafe.Pointer(name)),
|
||||
0,
|
||||
0,
|
||||
)
|
||||
handle = syscall.Handle(r0)
|
||||
if handle == syscall.InvalidHandle {
|
||||
if e1 != 0 {
|
||||
err = error(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
|
@ -3,6 +3,7 @@ package state
|
|||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
|
@ -14,6 +15,61 @@ func TestLocalState(t *testing.T) {
|
|||
TestState(t, ls)
|
||||
}
|
||||
|
||||
func TestLocalStateLocks(t *testing.T) {
|
||||
s := testLocalState(t)
|
||||
defer os.Remove(s.Path)
|
||||
|
||||
// lock first
|
||||
if err := s.Lock("test"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
out, err := exec.Command("go", "run", "testdata/lockstate.go", s.Path).CombinedOutput()
|
||||
|
||||
if err != nil {
|
||||
t.Fatal("unexpected lock failure", err)
|
||||
}
|
||||
|
||||
if string(out) != "lock failed" {
|
||||
t.Fatal("expected 'locked failed', got", string(out))
|
||||
}
|
||||
|
||||
// check our lock info
|
||||
lockInfo, err := s.lockInfo()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if lockInfo.Reason != "test" {
|
||||
t.Fatalf("invalid lock info %#v\n", lockInfo)
|
||||
}
|
||||
|
||||
// a noop, since we unlock on exit
|
||||
if err := s.Unlock(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// local locks can re-lock
|
||||
if err := s.Lock("test"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Unlock should be repeatable
|
||||
if err := s.Unlock(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := s.Unlock(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// make sure lock info is gone
|
||||
lockInfoPath := s.lockInfoPath()
|
||||
if _, err := os.Stat(lockInfoPath); !os.IsNotExist(err) {
|
||||
t.Fatal("lock info not removed")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestLocalState_pathOut(t *testing.T) {
|
||||
f, err := ioutil.TempFile("", "tf")
|
||||
if err != nil {
|
||||
|
|
|
@ -44,6 +44,38 @@ func testClient(t *testing.T, c Client) {
|
|||
}
|
||||
}
|
||||
|
||||
func testClientLocks(t *testing.T, c Client) {
|
||||
s3Client := c.(*S3Client)
|
||||
|
||||
// initial lock
|
||||
if err := s3Client.Lock("test"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// second lock should fail
|
||||
if err := s3Client.Lock("test"); err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
|
||||
// unlock should work
|
||||
if err := s3Client.Unlock(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// now we should be able to lock again
|
||||
if err := s3Client.Lock("test"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// unlock should be idempotent
|
||||
if err := s3Client.Unlock(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := s3Client.Unlock(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoteClient_noPayload(t *testing.T) {
|
||||
s := &State{
|
||||
Client: nilClient{},
|
||||
|
|
|
@ -7,10 +7,12 @@ import (
|
|||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/dynamodb"
|
||||
"github.com/aws/aws-sdk-go/service/s3"
|
||||
"github.com/hashicorp/go-cleanhttp"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
|
@ -89,6 +91,7 @@ providing credentials for the AWS S3 remote`))
|
|||
}
|
||||
sess := session.New(awsConfig)
|
||||
nativeClient := s3.New(sess)
|
||||
dynClient := dynamodb.New(sess)
|
||||
|
||||
return &S3Client{
|
||||
nativeClient: nativeClient,
|
||||
|
@ -97,6 +100,8 @@ providing credentials for the AWS S3 remote`))
|
|||
serverSideEncryption: serverSideEncryption,
|
||||
acl: acl,
|
||||
kmsKeyID: kmsKeyID,
|
||||
dynClient: dynClient,
|
||||
lockTable: conf["lock_table"],
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -107,6 +112,8 @@ type S3Client struct {
|
|||
serverSideEncryption bool
|
||||
acl string
|
||||
kmsKeyID string
|
||||
dynClient *dynamodb.DynamoDB
|
||||
lockTable string
|
||||
}
|
||||
|
||||
func (c *S3Client) Get() (*Payload, error) {
|
||||
|
@ -188,3 +195,73 @@ func (c *S3Client) Delete() error {
|
|||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *S3Client) Lock(reason string) error {
|
||||
if c.lockTable == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
stateName := fmt.Sprintf("%s/%s", c.bucketName, c.keyName)
|
||||
|
||||
putParams := &dynamodb.PutItemInput{
|
||||
Item: map[string]*dynamodb.AttributeValue{
|
||||
"LockID": {S: aws.String(stateName)},
|
||||
"Created": {S: aws.String(time.Now().UTC().Format(time.RFC3339))},
|
||||
"Expires": {S: aws.String(time.Now().Add(time.Hour).UTC().Format(time.RFC3339))},
|
||||
"Info": {S: aws.String(reason)},
|
||||
},
|
||||
TableName: aws.String(c.lockTable),
|
||||
ConditionExpression: aws.String("attribute_not_exists(LockID)"),
|
||||
}
|
||||
_, err := c.dynClient.PutItem(putParams)
|
||||
|
||||
if err != nil {
|
||||
getParams := &dynamodb.GetItemInput{
|
||||
Key: map[string]*dynamodb.AttributeValue{
|
||||
"LockID": {S: aws.String(fmt.Sprintf("%s/%s", c.bucketName, c.keyName))},
|
||||
},
|
||||
ProjectionExpression: aws.String("LockID, Created, Expires, Info"),
|
||||
TableName: aws.String(c.lockTable),
|
||||
}
|
||||
|
||||
resp, err := c.dynClient.GetItem(getParams)
|
||||
if err != nil {
|
||||
return fmt.Errorf("s3 state file %q locked, cfailed to retrive info: %s", stateName, err)
|
||||
}
|
||||
|
||||
var created, expires, info string
|
||||
if v, ok := resp.Item["Created"]; ok && v.S != nil {
|
||||
created = *v.S
|
||||
}
|
||||
if v, ok := resp.Item["Expires"]; ok && v.S != nil {
|
||||
expires = *v.S
|
||||
}
|
||||
if v, ok := resp.Item["Info"]; ok && v.S != nil {
|
||||
info = *v.S
|
||||
}
|
||||
|
||||
return fmt.Errorf("state file %q locked. created:%s, expires:%s, reason:%s",
|
||||
stateName, created, expires, info)
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *S3Client) Unlock() error {
|
||||
if c.lockTable == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
params := &dynamodb.DeleteItemInput{
|
||||
Key: map[string]*dynamodb.AttributeValue{
|
||||
"LockID": {S: aws.String(fmt.Sprintf("%s/%s", c.bucketName, c.keyName))},
|
||||
},
|
||||
TableName: aws.String(c.lockTable),
|
||||
}
|
||||
_, err := c.dynClient.DeleteItem(params)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -6,6 +6,8 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/dynamodb"
|
||||
"github.com/aws/aws-sdk-go/service/s3"
|
||||
)
|
||||
|
||||
|
@ -123,9 +125,113 @@ func TestS3Client(t *testing.T) {
|
|||
|
||||
_, err := nativeClient.DeleteBucket(deleteBucketReq)
|
||||
if err != nil {
|
||||
t.Logf("WARNING: Failed to delete the test S3 bucket. It has been left in your AWS account and may incur storage charges. (error was %s)", err)
|
||||
t.Logf("WARNING: Failed to delete the test S3 bucket. It may have been left in your AWS account and may incur storage charges. (error was %s)", err)
|
||||
}
|
||||
}()
|
||||
|
||||
testClient(t, client)
|
||||
}
|
||||
|
||||
func TestS3ClientLocks(t *testing.T) {
|
||||
// This test creates a DynamoDB table.
|
||||
// It may incur costs, so it will only run if AWS credential environment
|
||||
// variables are present.
|
||||
|
||||
accessKeyId := os.Getenv("AWS_ACCESS_KEY_ID")
|
||||
if accessKeyId == "" {
|
||||
t.Skipf("skipping; AWS_ACCESS_KEY_ID must be set")
|
||||
}
|
||||
|
||||
regionName := os.Getenv("AWS_DEFAULT_REGION")
|
||||
if regionName == "" {
|
||||
regionName = "us-west-2"
|
||||
}
|
||||
|
||||
bucketName := fmt.Sprintf("terraform-remote-s3-lock-%x", time.Now().Unix())
|
||||
keyName := "testState"
|
||||
|
||||
config := make(map[string]string)
|
||||
config["region"] = regionName
|
||||
config["bucket"] = bucketName
|
||||
config["key"] = keyName
|
||||
config["encrypt"] = "1"
|
||||
config["lock_table"] = bucketName
|
||||
|
||||
client, err := s3Factory(config)
|
||||
if err != nil {
|
||||
t.Fatalf("Error for valid config")
|
||||
}
|
||||
|
||||
s3Client := client.(*S3Client)
|
||||
|
||||
// set this up before we try to crate the table, in case we timeout creating it.
|
||||
defer deleteDynaboDBTable(t, s3Client, bucketName)
|
||||
|
||||
createDynamoDBTable(t, s3Client, bucketName)
|
||||
|
||||
testClientLocks(t, client)
|
||||
}
|
||||
|
||||
// create the dynamoDB table, and wait until we can query it.
|
||||
func createDynamoDBTable(t *testing.T, c *S3Client, tableName string) {
|
||||
createInput := &dynamodb.CreateTableInput{
|
||||
AttributeDefinitions: []*dynamodb.AttributeDefinition{
|
||||
{
|
||||
AttributeName: aws.String("LockID"),
|
||||
AttributeType: aws.String("S"),
|
||||
},
|
||||
},
|
||||
KeySchema: []*dynamodb.KeySchemaElement{
|
||||
{
|
||||
AttributeName: aws.String("LockID"),
|
||||
KeyType: aws.String("HASH"),
|
||||
},
|
||||
},
|
||||
ProvisionedThroughput: &dynamodb.ProvisionedThroughput{
|
||||
ReadCapacityUnits: aws.Int64(5),
|
||||
WriteCapacityUnits: aws.Int64(5),
|
||||
},
|
||||
TableName: aws.String(tableName),
|
||||
}
|
||||
|
||||
_, err := c.dynClient.CreateTable(createInput)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// now wait until it's ACTIVE
|
||||
start := time.Now()
|
||||
time.Sleep(time.Second)
|
||||
|
||||
describeInput := &dynamodb.DescribeTableInput{
|
||||
TableName: aws.String(tableName),
|
||||
}
|
||||
|
||||
for {
|
||||
resp, err := c.dynClient.DescribeTable(describeInput)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if *resp.Table.TableStatus == "ACTIVE" {
|
||||
return
|
||||
}
|
||||
|
||||
if time.Since(start) > time.Minute {
|
||||
t.Fatalf("timed out creating DynamoDB table %s", tableName)
|
||||
}
|
||||
|
||||
time.Sleep(3 * time.Second)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func deleteDynaboDBTable(t *testing.T, c *S3Client, tableName string) {
|
||||
params := &dynamodb.DeleteTableInput{
|
||||
TableName: aws.String(tableName),
|
||||
}
|
||||
_, err := c.dynClient.DeleteTable(params)
|
||||
if err != nil {
|
||||
t.Logf("WARNING: Failed to delete the test DynamoDB table %q. It has been left in your AWS account and may incur charges. (error was %s)", tableName, err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,3 +60,26 @@ func (s *State) PersistState() error {
|
|||
|
||||
return s.Client.Put(buf.Bytes())
|
||||
}
|
||||
|
||||
// Lock calls the Client's Lock method if it's implemented.
|
||||
func (s *State) Lock(reason string) error {
|
||||
if c, ok := s.Client.(stateLocker); ok {
|
||||
return c.Lock(reason)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unlock calls the Client's Unlock method if it's implemented.
|
||||
func (s *State) Unlock() error {
|
||||
if c, ok := s.Client.(stateLocker); ok {
|
||||
return c.Unlock()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// stateLocker mirrors the state.Locker interface. This can be implemented by
|
||||
// Clients to provide methods for locking and unlocking remote state.
|
||||
type stateLocker interface {
|
||||
Lock(reason string) error
|
||||
Unlock() error
|
||||
}
|
||||
|
|
|
@ -40,3 +40,9 @@ type StateRefresher interface {
|
|||
type StatePersister interface {
|
||||
PersistState() error
|
||||
}
|
||||
|
||||
// Locker is implemented to lock state during command execution.
|
||||
type Locker interface {
|
||||
Lock(reason string) error
|
||||
Unlock() error
|
||||
}
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
package state
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/logging"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
flag.Parse()
|
||||
if testing.Verbose() {
|
||||
// if we're verbose, use the logging requested by TF_LOG
|
||||
logging.SetOutput()
|
||||
} else {
|
||||
// otherwise silence all logs
|
||||
log.SetOutput(ioutil.Discard)
|
||||
}
|
||||
os.Exit(m.Run())
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/hashicorp/terraform/state"
|
||||
)
|
||||
|
||||
// Attempt to open and lock a terraform state file.
|
||||
// Lock failure exits with 0 and writes "lock failed" to stderr.
|
||||
func main() {
|
||||
if len(os.Args) != 2 {
|
||||
log.Fatal(os.Args[0], "statefile")
|
||||
}
|
||||
|
||||
s := &state.LocalState{
|
||||
Path: os.Args[1],
|
||||
}
|
||||
|
||||
err := s.Lock("test")
|
||||
if err != nil {
|
||||
io.WriteString(os.Stderr, "lock failed")
|
||||
|
||||
}
|
||||
return
|
||||
}
|
|
@ -4,6 +4,7 @@ import (
|
|||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
@ -1800,10 +1801,16 @@ func testForV0State(buf *bufio.Reader) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// ErrNoState is returned by ReadState when the io.Reader contains no data
|
||||
var ErrNoState = errors.New("no state")
|
||||
|
||||
// ReadState reads a state structure out of a reader in the format that
|
||||
// was written by WriteState.
|
||||
func ReadState(src io.Reader) (*State, error) {
|
||||
buf := bufio.NewReader(src)
|
||||
if _, err := buf.Peek(1); err == io.EOF {
|
||||
return nil, ErrNoState
|
||||
}
|
||||
|
||||
if err := testForV0State(buf); err != nil {
|
||||
return nil, err
|
||||
|
|
Loading…
Reference in New Issue