Merge pull request #13165 from hashicorp/jbardin/init
add Rehash to terraform.BackendState
This commit is contained in:
commit
0276614020
|
@ -406,6 +406,108 @@ 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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// move option from config to -backend-config args
|
||||||
|
func TestInit_backendReinitConfigToExtra(t *testing.T) {
|
||||||
|
td := tempDir(t)
|
||||||
|
copy.CopyDir(testFixturePath("init-backend"), td)
|
||||||
|
defer os.RemoveAll(td)
|
||||||
|
defer testChdir(t, td)()
|
||||||
|
|
||||||
|
ui := new(cli.MockUi)
|
||||||
|
c := &InitCommand{
|
||||||
|
Meta: Meta{
|
||||||
|
ContextOpts: testCtxConfig(testProvider()),
|
||||||
|
Ui: ui,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if code := c.Run([]string{"-input=false"}); 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 != "foo" {
|
||||||
|
t.Fatalf("bad: %#v", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
backendHash := state.Backend.Hash
|
||||||
|
|
||||||
|
// init again but remove the path option from the config
|
||||||
|
cfg := "terraform {\n backend \"local\" {}\n}\n"
|
||||||
|
if err := ioutil.WriteFile("main.tf", []byte(cfg), 0644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
args := []string{"-input=false", "-backend-config=path=foo"}
|
||||||
|
if code := c.Run(args); code != 0 {
|
||||||
|
t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
|
||||||
|
}
|
||||||
|
state = testStateRead(t, filepath.Join(DefaultDataDir, DefaultStateFilename))
|
||||||
|
|
||||||
|
if state.Backend.Hash == backendHash {
|
||||||
|
t.Fatal("state.Backend.Hash was not updated")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
func TestInit_remoteState(t *testing.T) {
|
func TestInit_remoteState(t *testing.T) {
|
||||||
tmp, cwd := testCwd(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():
|
case c != nil && s.Remote.Empty() && !s.Backend.Empty():
|
||||||
// If our configuration is the same, then we're just initializing
|
// If our configuration is the same, then we're just initializing
|
||||||
// a previously configured remote backend.
|
// a previously configured remote backend.
|
||||||
if !s.Backend.Empty() && s.Backend.Hash == cHash {
|
if !s.Backend.Empty() {
|
||||||
return m.backend_C_r_S_unchanged(c, sMgr)
|
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 {
|
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():
|
case c != nil && !s.Remote.Empty() && !s.Backend.Empty():
|
||||||
// If the hashes are the same, we have a legacy remote state with
|
// If the hashes are the same, we have a legacy remote state with
|
||||||
// an unchanged stored backend state.
|
// 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 {
|
if !opts.Init {
|
||||||
initReason := fmt.Sprintf(
|
initReason := fmt.Sprintf(
|
||||||
"Legacy remote state found with configured backend %q",
|
"Legacy remote state found with configured backend %q",
|
||||||
|
@ -1146,6 +1158,16 @@ func (m *Meta) backend_C_r_S_unchanged(
|
||||||
c *config.Backend, sMgr state.State) (backend.Backend, error) {
|
c *config.Backend, sMgr state.State) (backend.Backend, error) {
|
||||||
s := sMgr.State()
|
s := sMgr.State()
|
||||||
|
|
||||||
|
// it's possible for a backend to be unchanged, and the config itself to
|
||||||
|
// have changed by moving a paramter from the config to `-backend-config`
|
||||||
|
// In this case we only need to update the Hash.
|
||||||
|
if c != nil && s.Backend.Hash != c.Hash {
|
||||||
|
s.Backend.Hash = c.Hash
|
||||||
|
if err := sMgr.WriteState(s); err != nil {
|
||||||
|
return nil, fmt.Errorf(errBackendWriteSaved, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create the config. We do this from the backend state since this
|
// Create the config. We do this from the backend state since this
|
||||||
// has the complete configuration data whereas the config itself
|
// has the complete configuration data whereas the config itself
|
||||||
// may require input.
|
// may require input.
|
||||||
|
|
|
@ -3217,6 +3217,110 @@ func TestMetaBackend_planLegacy(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// init a backend using -backend-config options multiple times
|
||||||
|
func TestMetaBackend_configureWithExtra(t *testing.T) {
|
||||||
|
// Create a temporary working directory that is empty
|
||||||
|
td := tempDir(t)
|
||||||
|
copy.CopyDir(testFixturePath("init-backend-empty"), td)
|
||||||
|
defer os.RemoveAll(td)
|
||||||
|
defer testChdir(t, td)()
|
||||||
|
|
||||||
|
extras := map[string]interface{}{"path": "hello"}
|
||||||
|
m := testMetaBackend(t, nil)
|
||||||
|
opts := &BackendOpts{
|
||||||
|
ConfigExtra: extras,
|
||||||
|
Init: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
backendCfg, err := m.backendConfig(opts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// init the backend
|
||||||
|
_, err = m.Backend(&BackendOpts{
|
||||||
|
ConfigExtra: extras,
|
||||||
|
Init: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("bad: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the state
|
||||||
|
s := testStateRead(t, filepath.Join(DefaultDataDir, backendlocal.DefaultStateFilename))
|
||||||
|
if s.Backend.Hash != backendCfg.Hash {
|
||||||
|
t.Fatal("mismatched state and config backend hashes")
|
||||||
|
}
|
||||||
|
if s.Backend.Rehash() == s.Backend.Hash {
|
||||||
|
t.Fatal("saved hash should not match actual hash")
|
||||||
|
}
|
||||||
|
if s.Backend.Rehash() != backendCfg.Rehash() {
|
||||||
|
t.Fatal("mismatched state and config re-hashes")
|
||||||
|
}
|
||||||
|
|
||||||
|
// init the backend again with the same options
|
||||||
|
m = testMetaBackend(t, nil)
|
||||||
|
_, err = m.Backend(&BackendOpts{
|
||||||
|
ConfigExtra: extras,
|
||||||
|
Init: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("bad: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the state
|
||||||
|
s = testStateRead(t, filepath.Join(DefaultDataDir, backendlocal.DefaultStateFilename))
|
||||||
|
if s.Backend.Hash != backendCfg.Hash {
|
||||||
|
t.Fatal("mismatched state and config backend hashes")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// move options from config to -backend-config
|
||||||
|
func TestMetaBackend_configToExtra(t *testing.T) {
|
||||||
|
// Create a temporary working directory that is empty
|
||||||
|
td := tempDir(t)
|
||||||
|
copy.CopyDir(testFixturePath("init-backend"), td)
|
||||||
|
defer os.RemoveAll(td)
|
||||||
|
defer testChdir(t, td)()
|
||||||
|
|
||||||
|
// init the backend
|
||||||
|
m := testMetaBackend(t, nil)
|
||||||
|
_, err := m.Backend(&BackendOpts{
|
||||||
|
Init: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("bad: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the state
|
||||||
|
s := testStateRead(t, filepath.Join(DefaultDataDir, backendlocal.DefaultStateFilename))
|
||||||
|
backendHash := s.Backend.Hash
|
||||||
|
|
||||||
|
// init again but remove the path option from the config
|
||||||
|
cfg := "terraform {\n backend \"local\" {}\n}\n"
|
||||||
|
if err := ioutil.WriteFile("main.tf", []byte(cfg), 0644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// init the backend again with the options
|
||||||
|
extras := map[string]interface{}{"path": "hello"}
|
||||||
|
m = testMetaBackend(t, nil)
|
||||||
|
m.forceInitCopy = true
|
||||||
|
_, err = m.Backend(&BackendOpts{
|
||||||
|
ConfigExtra: extras,
|
||||||
|
Init: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("bad: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s = testStateRead(t, filepath.Join(DefaultDataDir, backendlocal.DefaultStateFilename))
|
||||||
|
|
||||||
|
if s.Backend.Hash == backendHash {
|
||||||
|
t.Fatal("state.Backend.Hash was not updated")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func testMetaBackend(t *testing.T, args []string) *Meta {
|
func testMetaBackend(t *testing.T, args []string) *Meta {
|
||||||
var m Meta
|
var m Meta
|
||||||
m.Ui = new(cli.MockUi)
|
m.Ui = new(cli.MockUi)
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
terraform {
|
||||||
|
backend "local" {
|
||||||
|
}
|
||||||
|
}
|
|
@ -72,7 +72,7 @@ type Backend struct {
|
||||||
Hash uint64
|
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.
|
// as a uint64 value.
|
||||||
func (b *Backend) Rehash() uint64 {
|
func (b *Backend) Rehash() uint64 {
|
||||||
// If we have no backend, the value is zero
|
// If we have no backend, the value is zero
|
||||||
|
|
|
@ -801,6 +801,27 @@ func (s *BackendState) Empty() bool {
|
||||||
return s == nil || s.Type == ""
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := config.Backend{
|
||||||
|
Type: s.Type,
|
||||||
|
RawConfig: &config.RawConfig{
|
||||||
|
Raw: s.Config,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg.Rehash()
|
||||||
|
}
|
||||||
|
|
||||||
// RemoteState is used to track the information about a remote
|
// RemoteState is used to track the information about a remote
|
||||||
// state store that we push/pull state to.
|
// state store that we push/pull state to.
|
||||||
type RemoteState struct {
|
type RemoteState struct {
|
||||||
|
|
Loading…
Reference in New Issue