add Rehash to terraform.BackendState
This method mirrors that of config.Backend, so we can compare the configration of a backend read from a config vs that of a backend read from a state. This will prevent init from reinitializing when using `-backend-config` options that match the existing state.
This commit is contained in:
parent
80a1539bca
commit
ff2d753062
|
@ -406,6 +406,64 @@ func TestInit_copyBackendDst(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestInit_backendReinitWithExtra(t *testing.T) {
|
||||
td := tempDir(t)
|
||||
copy.CopyDir(testFixturePath("init-backend-empty"), td)
|
||||
defer os.RemoveAll(td)
|
||||
defer testChdir(t, td)()
|
||||
|
||||
m := testMetaBackend(t, nil)
|
||||
opts := &BackendOpts{
|
||||
ConfigExtra: map[string]interface{}{"path": "hello"},
|
||||
Init: true,
|
||||
}
|
||||
|
||||
b, err := m.backendConfig(opts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ui := new(cli.MockUi)
|
||||
c := &InitCommand{
|
||||
Meta: Meta{
|
||||
ContextOpts: testCtxConfig(testProvider()),
|
||||
Ui: ui,
|
||||
},
|
||||
}
|
||||
|
||||
args := []string{"-backend-config", "path=hello"}
|
||||
if code := c.Run(args); code != 0 {
|
||||
t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
|
||||
}
|
||||
|
||||
// Read our saved backend config and verify we have our settings
|
||||
state := testStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename))
|
||||
if v := state.Backend.Config["path"]; v != "hello" {
|
||||
t.Fatalf("bad: %#v", v)
|
||||
}
|
||||
|
||||
if state.Backend.Hash != b.Hash {
|
||||
t.Fatal("mismatched state and config backend hashes")
|
||||
}
|
||||
|
||||
if state.Backend.Rehash() != b.Rehash() {
|
||||
t.Fatal("mismatched state and config re-hashes")
|
||||
}
|
||||
|
||||
// init again and make sure nothing changes
|
||||
if code := c.Run(args); code != 0 {
|
||||
t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
|
||||
}
|
||||
state = testStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename))
|
||||
if v := state.Backend.Config["path"]; v != "hello" {
|
||||
t.Fatalf("bad: %#v", v)
|
||||
}
|
||||
|
||||
if state.Backend.Hash != b.Hash {
|
||||
t.Fatal("mismatched state and config backend hashes")
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
func TestInit_remoteState(t *testing.T) {
|
||||
tmp, cwd := testCwd(t)
|
||||
|
|
|
@ -415,8 +415,16 @@ func (m *Meta) backendFromConfig(opts *BackendOpts) (backend.Backend, error) {
|
|||
case c != nil && s.Remote.Empty() && !s.Backend.Empty():
|
||||
// If our configuration is the same, then we're just initializing
|
||||
// a previously configured remote backend.
|
||||
if !s.Backend.Empty() && s.Backend.Hash == cHash {
|
||||
return m.backend_C_r_S_unchanged(c, sMgr)
|
||||
if !s.Backend.Empty() {
|
||||
hash := s.Backend.Hash
|
||||
// on init we need an updated hash containing any extra options
|
||||
// that were added after merging.
|
||||
if opts.Init {
|
||||
hash = s.Backend.Rehash()
|
||||
}
|
||||
if hash == cHash {
|
||||
return m.backend_C_r_S_unchanged(c, sMgr)
|
||||
}
|
||||
}
|
||||
|
||||
if !opts.Init {
|
||||
|
@ -451,7 +459,11 @@ func (m *Meta) backendFromConfig(opts *BackendOpts) (backend.Backend, error) {
|
|||
case c != nil && !s.Remote.Empty() && !s.Backend.Empty():
|
||||
// If the hashes are the same, we have a legacy remote state with
|
||||
// an unchanged stored backend state.
|
||||
if s.Backend.Hash == cHash {
|
||||
hash := s.Backend.Hash
|
||||
if opts.Init {
|
||||
hash = s.Backend.Rehash()
|
||||
}
|
||||
if hash == cHash {
|
||||
if !opts.Init {
|
||||
initReason := fmt.Sprintf(
|
||||
"Legacy remote state found with configured backend %q",
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
terraform {
|
||||
backend "local" {
|
||||
}
|
||||
}
|
|
@ -72,7 +72,7 @@ type Backend struct {
|
|||
Hash uint64
|
||||
}
|
||||
|
||||
// Hash returns a unique content hash for this backend's configuration
|
||||
// Rehash returns a unique content hash for this backend's configuration
|
||||
// as a uint64 value.
|
||||
func (b *Backend) Rehash() uint64 {
|
||||
// If we have no backend, the value is zero
|
||||
|
|
|
@ -20,6 +20,7 @@ import (
|
|||
"github.com/hashicorp/go-version"
|
||||
"github.com/hashicorp/terraform/config"
|
||||
"github.com/mitchellh/copystructure"
|
||||
"github.com/mitchellh/hashstructure"
|
||||
"github.com/satori/go.uuid"
|
||||
)
|
||||
|
||||
|
@ -801,6 +802,32 @@ func (s *BackendState) Empty() bool {
|
|||
return s == nil || s.Type == ""
|
||||
}
|
||||
|
||||
// Rehash returns a unique content hash for this backend's configuration
|
||||
// as a uint64 value.
|
||||
// The Hash stored in the backend state needs to match the config itself, but
|
||||
// we need to compare the backend config after it has been combined with all
|
||||
// options.
|
||||
// This function must match the implementation used by config.Backend.
|
||||
func (s *BackendState) Rehash() uint64 {
|
||||
if s == nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Use hashstructure to hash only our type with the config.
|
||||
code, err := hashstructure.Hash(map[string]interface{}{
|
||||
"type": s.Type,
|
||||
"config": s.Config,
|
||||
}, nil)
|
||||
|
||||
// This should never happen since we have just some basic primitives
|
||||
// so panic if there is an error.
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return code
|
||||
}
|
||||
|
||||
// RemoteState is used to track the information about a remote
|
||||
// state store that we push/pull state to.
|
||||
type RemoteState struct {
|
||||
|
|
Loading…
Reference in New Issue