From 4fab46749ab8fa2234ad8a48471b34bebdd4eece Mon Sep 17 00:00:00 2001 From: Megan Bang Date: Thu, 25 Aug 2022 14:57:40 -0500 Subject: [PATCH] update persist state --- internal/backend/local/backend.go | 2 +- internal/backend/local/backend_apply.go | 18 +++++----- internal/backend/local/backend_local_test.go | 3 +- internal/backend/local/backend_refresh.go | 12 +++++-- .../remote-state/azure/backend_state.go | 2 +- .../remote-state/consul/backend_state.go | 2 +- .../backend/remote-state/cos/backend_state.go | 2 +- .../backend/remote-state/gcs/backend_state.go | 2 +- .../backend/remote-state/inmem/backend.go | 2 +- .../remote-state/inmem/backend_test.go | 4 +-- .../remote-state/kubernetes/backend_state.go | 2 +- .../remote-state/manta/backend_state.go | 2 +- .../backend/remote-state/oss/backend_state.go | 2 +- .../backend/remote-state/pg/backend_state.go | 2 +- .../backend/remote-state/pg/backend_test.go | 4 +-- .../backend/remote-state/s3/backend_state.go | 2 +- .../backend/remote-state/s3/backend_test.go | 10 +++--- .../remote-state/swift/backend_state.go | 2 +- internal/backend/testing.go | 4 +-- internal/cloud/state.go | 7 ++-- internal/command/helper.go | 34 +++++++++++++++++++ internal/command/import.go | 11 +++++- internal/command/meta_backend.go | 2 +- internal/command/meta_backend_migrate.go | 20 +++++++++-- internal/command/meta_backend_test.go | 26 +++++++------- internal/command/show.go | 16 ++------- internal/command/state_mv.go | 24 +++++++++++-- internal/command/state_push.go | 19 ++++++++++- internal/command/state_push_test.go | 2 +- internal/command/state_replace_provider.go | 21 +++++++++++- internal/command/state_rm.go | 22 +++++++++++- internal/command/taint.go | 22 +++++++++++- internal/command/untaint.go | 22 +++++++++++- internal/command/workspace_new.go | 2 +- internal/states/remote/state.go | 3 +- internal/states/remote/state_test.go | 8 ++--- internal/states/statemgr/filesystem.go | 3 +- internal/states/statemgr/helper.go | 5 +-- internal/states/statemgr/lock.go | 9 +++-- internal/states/statemgr/persistent.go | 7 +++- internal/states/statemgr/statemgr_fake.go | 5 +-- internal/states/statemgr/testing.go | 6 ++-- internal/states/statemgr/transient.go | 2 +- 43 files changed, 280 insertions(+), 97 deletions(-) create mode 100644 internal/command/helper.go diff --git a/internal/backend/local/backend.go b/internal/backend/local/backend.go index dd6c9cc56f..de1e7e5a42 100644 --- a/internal/backend/local/backend.go +++ b/internal/backend/local/backend.go @@ -344,7 +344,7 @@ func (b *Local) opWait( // try to force a PersistState just in case the process is terminated // before we can complete. - if err := opStateMgr.PersistState(); err != nil { + if err := opStateMgr.PersistState(nil); err != nil { // We can't error out from here, but warn the user if there was an error. // If this isn't transient, we will catch it again below, and // attempt to save the state another way. diff --git a/internal/backend/local/backend_apply.go b/internal/backend/local/backend_apply.go index a6c6ea9f0b..5bec4b442a 100644 --- a/internal/backend/local/backend_apply.go +++ b/internal/backend/local/backend_apply.go @@ -53,7 +53,7 @@ func (b *Local) opApply( op.ReportResult(runningOp, diags) return } - // the state was locked during succesfull context creation; unlock the state + // the state was locked during successful context creation; unlock the state // when the operation completes defer func() { diags := op.StateLocker.Unlock() @@ -68,6 +68,13 @@ func (b *Local) opApply( // operation. runningOp.State = lr.InputState + schemas, moreDiags := lr.Core.Schemas(lr.Config, lr.InputState) + diags = diags.Append(moreDiags) + if moreDiags.HasErrors() { + op.ReportResult(runningOp, diags) + return + } + var plan *plans.Plan // If we weren't given a plan, then we refresh/plan if op.PlanFile == nil { @@ -80,13 +87,6 @@ func (b *Local) opApply( return } - schemas, moreDiags := lr.Core.Schemas(lr.Config, lr.InputState) - diags = diags.Append(moreDiags) - if moreDiags.HasErrors() { - op.ReportResult(runningOp, diags) - return - } - trivialPlan := !plan.CanApply() hasUI := op.UIOut != nil && op.UIIn != nil mustConfirm := hasUI && !op.AutoApprove && !trivialPlan @@ -198,7 +198,7 @@ func (b *Local) opApply( // Store the final state runningOp.State = applyState - err := statemgr.WriteAndPersist(opState, applyState) + err := statemgr.WriteAndPersist(opState, applyState, schemas) if err != nil { // Export the state file from the state manager and assign the new // state. This is needed to preserve the existing serial and lineage. diff --git a/internal/backend/local/backend_local_test.go b/internal/backend/local/backend_local_test.go index 05573f0f9d..136dbaccf2 100644 --- a/internal/backend/local/backend_local_test.go +++ b/internal/backend/local/backend_local_test.go @@ -21,6 +21,7 @@ import ( "github.com/hashicorp/terraform/internal/states/statefile" "github.com/hashicorp/terraform/internal/states/statemgr" "github.com/hashicorp/terraform/internal/terminal" + "github.com/hashicorp/terraform/internal/terraform" "github.com/hashicorp/terraform/internal/tfdiags" ) @@ -233,6 +234,6 @@ func (s *stateStorageThatFailsRefresh) RefreshState() error { return fmt.Errorf("intentionally failing for testing purposes") } -func (s *stateStorageThatFailsRefresh) PersistState() error { +func (s *stateStorageThatFailsRefresh) PersistState(schemas *terraform.Schemas) error { return fmt.Errorf("unimplemented") } diff --git a/internal/backend/local/backend_refresh.go b/internal/backend/local/backend_refresh.go index 8ce3b6aff1..244e8e89bb 100644 --- a/internal/backend/local/backend_refresh.go +++ b/internal/backend/local/backend_refresh.go @@ -52,7 +52,7 @@ func (b *Local) opRefresh( return } - // the state was locked during succesfull context creation; unlock the state + // the state was locked during successful context creation; unlock the state // when the operation completes defer func() { diags := op.StateLocker.Unlock() @@ -73,6 +73,14 @@ func (b *Local) opRefresh( )) } + // get schemas before writing state + schemas, moreDiags := lr.Core.Schemas(lr.Config, lr.InputState) + diags = diags.Append(moreDiags) + if moreDiags.HasErrors() { + op.ReportResult(runningOp, diags) + return + } + // Perform the refresh in a goroutine so we can be interrupted var newState *states.State var refreshDiags tfdiags.Diagnostics @@ -96,7 +104,7 @@ func (b *Local) opRefresh( return } - err := statemgr.WriteAndPersist(opState, newState) + err := statemgr.WriteAndPersist(opState, newState, schemas) if err != nil { diags = diags.Append(fmt.Errorf("failed to write state: %w", err)) op.ReportResult(runningOp, diags) diff --git a/internal/backend/remote-state/azure/backend_state.go b/internal/backend/remote-state/azure/backend_state.go index 6a1a9c02f0..82d2505c65 100644 --- a/internal/backend/remote-state/azure/backend_state.go +++ b/internal/backend/remote-state/azure/backend_state.go @@ -131,7 +131,7 @@ func (b *Backend) StateMgr(name string) (statemgr.Full, error) { err = lockUnlock(err) return nil, err } - if err := stateMgr.PersistState(); err != nil { + if err := stateMgr.PersistState(nil); err != nil { err = lockUnlock(err) return nil, err } diff --git a/internal/backend/remote-state/consul/backend_state.go b/internal/backend/remote-state/consul/backend_state.go index be1841eb8d..5bd74b2508 100644 --- a/internal/backend/remote-state/consul/backend_state.go +++ b/internal/backend/remote-state/consul/backend_state.go @@ -120,7 +120,7 @@ func (b *Backend) StateMgr(name string) (statemgr.Full, error) { err = lockUnlock(err) return nil, err } - if err := stateMgr.PersistState(); err != nil { + if err := stateMgr.PersistState(nil); err != nil { err = lockUnlock(err) return nil, err } diff --git a/internal/backend/remote-state/cos/backend_state.go b/internal/backend/remote-state/cos/backend_state.go index ab92cfb7c0..4e47ae0e68 100644 --- a/internal/backend/remote-state/cos/backend_state.go +++ b/internal/backend/remote-state/cos/backend_state.go @@ -126,7 +126,7 @@ func (b *Backend) StateMgr(name string) (statemgr.Full, error) { err = lockUnlock(err) return nil, err } - if err := stateMgr.PersistState(); err != nil { + if err := stateMgr.PersistState(nil); err != nil { err = lockUnlock(err) return nil, err } diff --git a/internal/backend/remote-state/gcs/backend_state.go b/internal/backend/remote-state/gcs/backend_state.go index ee764efb4c..21b7183473 100644 --- a/internal/backend/remote-state/gcs/backend_state.go +++ b/internal/backend/remote-state/gcs/backend_state.go @@ -131,7 +131,7 @@ func (b *Backend) StateMgr(name string) (statemgr.Full, error) { if err := st.WriteState(states.NewState()); err != nil { return nil, unlock(err) } - if err := st.PersistState(); err != nil { + if err := st.PersistState(nil); err != nil { return nil, unlock(err) } diff --git a/internal/backend/remote-state/inmem/backend.go b/internal/backend/remote-state/inmem/backend.go index 7f8f56ef20..4e0113cbc7 100644 --- a/internal/backend/remote-state/inmem/backend.go +++ b/internal/backend/remote-state/inmem/backend.go @@ -141,7 +141,7 @@ func (b *Backend) StateMgr(name string) (statemgr.Full, error) { if err := s.WriteState(statespkg.NewState()); err != nil { return nil, err } - if err := s.PersistState(); err != nil { + if err := s.PersistState(nil); err != nil { return nil, err } } diff --git a/internal/backend/remote-state/inmem/backend_test.go b/internal/backend/remote-state/inmem/backend_test.go index 395199890a..204858824d 100644 --- a/internal/backend/remote-state/inmem/backend_test.go +++ b/internal/backend/remote-state/inmem/backend_test.go @@ -5,8 +5,6 @@ import ( "os" "testing" - "github.com/hashicorp/hcl/v2" - "github.com/hashicorp/terraform/internal/backend" statespkg "github.com/hashicorp/terraform/internal/states" "github.com/hashicorp/terraform/internal/states/remote" @@ -82,7 +80,7 @@ func TestRemoteState(t *testing.T) { t.Fatal(err) } - if err := s.PersistState(); err != nil { + if err := s.PersistState(nil); err != nil { t.Fatal(err) } diff --git a/internal/backend/remote-state/kubernetes/backend_state.go b/internal/backend/remote-state/kubernetes/backend_state.go index 56aa089ff8..6e8ce449d1 100644 --- a/internal/backend/remote-state/kubernetes/backend_state.go +++ b/internal/backend/remote-state/kubernetes/backend_state.go @@ -123,7 +123,7 @@ func (b *Backend) StateMgr(name string) (statemgr.Full, error) { if err := stateMgr.WriteState(states.NewState()); err != nil { return nil, unlock(err) } - if err := stateMgr.PersistState(); err != nil { + if err := stateMgr.PersistState(nil); err != nil { return nil, unlock(err) } diff --git a/internal/backend/remote-state/manta/backend_state.go b/internal/backend/remote-state/manta/backend_state.go index 925d82083d..b30b250dc7 100644 --- a/internal/backend/remote-state/manta/backend_state.go +++ b/internal/backend/remote-state/manta/backend_state.go @@ -108,7 +108,7 @@ func (b *Backend) StateMgr(name string) (statemgr.Full, error) { err = lockUnlock(err) return nil, err } - if err := stateMgr.PersistState(); err != nil { + if err := stateMgr.PersistState(nil); err != nil { err = lockUnlock(err) return nil, err } diff --git a/internal/backend/remote-state/oss/backend_state.go b/internal/backend/remote-state/oss/backend_state.go index 77a2775f8a..672a2e1aa2 100644 --- a/internal/backend/remote-state/oss/backend_state.go +++ b/internal/backend/remote-state/oss/backend_state.go @@ -160,7 +160,7 @@ func (b *Backend) StateMgr(name string) (statemgr.Full, error) { err = lockUnlock(err) return nil, err } - if err := stateMgr.PersistState(); err != nil { + if err := stateMgr.PersistState(nil); err != nil { err = lockUnlock(err) return nil, err } diff --git a/internal/backend/remote-state/pg/backend_state.go b/internal/backend/remote-state/pg/backend_state.go index 2700c51969..f3eb650092 100644 --- a/internal/backend/remote-state/pg/backend_state.go +++ b/internal/backend/remote-state/pg/backend_state.go @@ -99,7 +99,7 @@ func (b *Backend) StateMgr(name string) (statemgr.Full, error) { err = lockUnlock(err) return nil, err } - if err := stateMgr.PersistState(); err != nil { + if err := stateMgr.PersistState(nil); err != nil { err = lockUnlock(err) return nil, err } diff --git a/internal/backend/remote-state/pg/backend_test.go b/internal/backend/remote-state/pg/backend_test.go index da058483d8..064c001f5b 100644 --- a/internal/backend/remote-state/pg/backend_test.go +++ b/internal/backend/remote-state/pg/backend_test.go @@ -330,7 +330,7 @@ func TestBackendConcurrentLock(t *testing.T) { t.Fatalf("failed to lock first state: %v", err) } - if err = s1.PersistState(); err != nil { + if err = s1.PersistState(nil); err != nil { t.Fatalf("failed to persist state: %v", err) } @@ -343,7 +343,7 @@ func TestBackendConcurrentLock(t *testing.T) { t.Fatalf("failed to lock second state: %v", err) } - if err = s2.PersistState(); err != nil { + if err = s2.PersistState(nil); err != nil { t.Fatalf("failed to persist state: %v", err) } diff --git a/internal/backend/remote-state/s3/backend_state.go b/internal/backend/remote-state/s3/backend_state.go index 0134c861d0..d13cc32d4f 100644 --- a/internal/backend/remote-state/s3/backend_state.go +++ b/internal/backend/remote-state/s3/backend_state.go @@ -184,7 +184,7 @@ func (b *Backend) StateMgr(name string) (statemgr.Full, error) { err = lockUnlock(err) return nil, err } - if err := stateMgr.PersistState(); err != nil { + if err := stateMgr.PersistState(nil); err != nil { err = lockUnlock(err) return nil, err } diff --git a/internal/backend/remote-state/s3/backend_test.go b/internal/backend/remote-state/s3/backend_test.go index a44f154c08..1fd49c461a 100644 --- a/internal/backend/remote-state/s3/backend_test.go +++ b/internal/backend/remote-state/s3/backend_test.go @@ -478,7 +478,7 @@ func TestBackendExtraPaths(t *testing.T) { // Write the first state stateMgr := &remote.State{Client: client} stateMgr.WriteState(s1) - if err := stateMgr.PersistState(); err != nil { + if err := stateMgr.PersistState(nil); err != nil { t.Fatal(err) } @@ -488,7 +488,7 @@ func TestBackendExtraPaths(t *testing.T) { client.path = b.path("s2") stateMgr2 := &remote.State{Client: client} stateMgr2.WriteState(s2) - if err := stateMgr2.PersistState(); err != nil { + if err := stateMgr2.PersistState(nil); err != nil { t.Fatal(err) } @@ -501,7 +501,7 @@ func TestBackendExtraPaths(t *testing.T) { // put a state in an env directory name client.path = b.workspaceKeyPrefix + "/error" stateMgr.WriteState(states.NewState()) - if err := stateMgr.PersistState(); err != nil { + if err := stateMgr.PersistState(nil); err != nil { t.Fatal(err) } if err := checkStateList(b, []string{"default", "s1", "s2"}); err != nil { @@ -511,7 +511,7 @@ func TestBackendExtraPaths(t *testing.T) { // add state with the wrong key for an existing env client.path = b.workspaceKeyPrefix + "/s2/notTestState" stateMgr.WriteState(states.NewState()) - if err := stateMgr.PersistState(); err != nil { + if err := stateMgr.PersistState(nil); err != nil { t.Fatal(err) } if err := checkStateList(b, []string{"default", "s1", "s2"}); err != nil { @@ -550,7 +550,7 @@ func TestBackendExtraPaths(t *testing.T) { // add a state with a key that matches an existing environment dir name client.path = b.workspaceKeyPrefix + "/s2/" stateMgr.WriteState(states.NewState()) - if err := stateMgr.PersistState(); err != nil { + if err := stateMgr.PersistState(nil); err != nil { t.Fatal(err) } diff --git a/internal/backend/remote-state/swift/backend_state.go b/internal/backend/remote-state/swift/backend_state.go index b853b64c96..719585d855 100644 --- a/internal/backend/remote-state/swift/backend_state.go +++ b/internal/backend/remote-state/swift/backend_state.go @@ -172,7 +172,7 @@ func (b *Backend) StateMgr(name string) (statemgr.Full, error) { err = lockUnlock(err) return nil, err } - if err := stateMgr.PersistState(); err != nil { + if err := stateMgr.PersistState(nil); err != nil { err = lockUnlock(err) return nil, err } diff --git a/internal/backend/testing.go b/internal/backend/testing.go index 844f95668d..a8c04e0ac7 100644 --- a/internal/backend/testing.go +++ b/internal/backend/testing.go @@ -132,7 +132,7 @@ func TestBackendStates(t *testing.T, b Backend) { if err := foo.WriteState(fooState); err != nil { t.Fatal("error writing foo state:", err) } - if err := foo.PersistState(); err != nil { + if err := foo.PersistState(nil); err != nil { t.Fatal("error persisting foo state:", err) } @@ -160,7 +160,7 @@ func TestBackendStates(t *testing.T, b Backend) { if err := bar.WriteState(barState); err != nil { t.Fatalf("bad: %s", err) } - if err := bar.PersistState(); err != nil { + if err := bar.PersistState(nil); err != nil { t.Fatalf("bad: %s", err) } diff --git a/internal/cloud/state.go b/internal/cloud/state.go index 73bea8ba5d..bc1cc1414e 100644 --- a/internal/cloud/state.go +++ b/internal/cloud/state.go @@ -15,6 +15,7 @@ import ( "github.com/hashicorp/terraform/internal/states" "github.com/hashicorp/terraform/internal/states/remote" "github.com/hashicorp/terraform/internal/states/statemgr" + "github.com/hashicorp/terraform/internal/terraform" ) // State is similar to remote State and delegates to it, except in the case of output values, @@ -63,9 +64,9 @@ func (s *State) RefreshState() error { return s.delegate.RefreshState() } -// RefreshState delegates calls to refresh State to the remote State -func (s *State) PersistState() error { - return s.delegate.PersistState() +// PersistState delegates calls to persist State to the remote State +func (s *State) PersistState(schemas *terraform.Schemas) error { + return s.delegate.PersistState(schemas) } // WriteState delegates calls to write State to the remote State diff --git a/internal/command/helper.go b/internal/command/helper.go new file mode 100644 index 0000000000..d8e7515df5 --- /dev/null +++ b/internal/command/helper.go @@ -0,0 +1,34 @@ +package command + +import ( + "github.com/hashicorp/terraform/internal/configs" + "github.com/hashicorp/terraform/internal/states" + "github.com/hashicorp/terraform/internal/terraform" + "github.com/hashicorp/terraform/internal/tfdiags" +) + +func getSchemas(c *Meta, state *states.State, config *configs.Config) (*terraform.Schemas, tfdiags.Diagnostics) { + var diags tfdiags.Diagnostics + + if config != nil || state != nil { + opts, err := c.contextOpts() + if err != nil { + diags = diags.Append(err) + return nil, diags + } + tfCtx, ctxDiags := terraform.NewContext(opts) + diags = diags.Append(ctxDiags) + if ctxDiags.HasErrors() { + return nil, diags + } + var schemaDiags tfdiags.Diagnostics + schemas, schemaDiags := tfCtx.Schemas(config, state) + diags = diags.Append(schemaDiags) + if schemaDiags.HasErrors() { + return nil, diags + } + return schemas, diags + + } + return nil, diags +} diff --git a/internal/command/import.go b/internal/command/import.go index 51d3895e73..e25f8e3af1 100644 --- a/internal/command/import.go +++ b/internal/command/import.go @@ -248,13 +248,22 @@ func (c *ImportCommand) Run(args []string) int { return 1 } + // Get schemas, if possible, before writing state + schemas, diags := getSchemas(&c.Meta, newState, config) + if diags.HasErrors() { + // MBANG TODO - add warning that the schema could not be initialized + // and therefore the JSON state can not be created and may affect + // external applications relying on that data format + return 1 + } + // Persist the final state log.Printf("[INFO] Writing state output to: %s", c.Meta.StateOutPath()) if err := state.WriteState(newState); err != nil { c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err)) return 1 } - if err := state.PersistState(); err != nil { + if err := state.PersistState(schemas); err != nil { c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err)) return 1 } diff --git a/internal/command/meta_backend.go b/internal/command/meta_backend.go index 43e49152cf..d7fa62e001 100644 --- a/internal/command/meta_backend.go +++ b/internal/command/meta_backend.go @@ -998,7 +998,7 @@ func (m *Meta) backend_C_r_s(c *configs.Backend, cHash int, sMgr *clistate.Local diags = diags.Append(fmt.Errorf(errBackendMigrateLocalDelete, err)) return nil, diags } - if err := localState.PersistState(); err != nil { + if err := localState.PersistState(nil); err != nil { diags = diags.Append(fmt.Errorf(errBackendMigrateLocalDelete, err)) return nil, diags } diff --git a/internal/command/meta_backend_migrate.go b/internal/command/meta_backend_migrate.go index 8cd011eff0..e0a81ba390 100644 --- a/internal/command/meta_backend_migrate.go +++ b/internal/command/meta_backend_migrate.go @@ -434,11 +434,27 @@ func (m *Meta) backendMigrateState_s_s(opts *backendMigrateOpts) error { // includes preserving any lineage/serial information where possible, if // both managers support such metadata. log.Print("[TRACE] backendMigrateState: migration confirmed, so migrating") + + path, err := os.Getwd() + if err != nil { + return fmt.Errorf("could not get working directory") + } + + config, diags := m.loadConfig(path) + if diags.HasErrors() { + return diags.Err() + } + + schemas, diags := getSchemas(m, destination, config) + if diags.HasErrors() { + return diags.Err() + } + if err := statemgr.Migrate(destinationState, sourceState); err != nil { return fmt.Errorf(strings.TrimSpace(errBackendStateCopy), opts.SourceType, opts.DestinationType, err) } - if err := destinationState.PersistState(); err != nil { + if err := destinationState.PersistState(schemas); err != nil { return fmt.Errorf(strings.TrimSpace(errBackendStateCopy), opts.SourceType, opts.DestinationType, err) } @@ -960,7 +976,7 @@ This will attempt to copy (with permission) all workspaces again. ` const errBackendStateCopy = ` -Error copying state from the previous %q backend to the newly configured +Error copying state from the previous %q backend to the newly configured %q backend: %s diff --git a/internal/command/meta_backend_test.go b/internal/command/meta_backend_test.go index 111e136cee..2fa61b618f 100644 --- a/internal/command/meta_backend_test.go +++ b/internal/command/meta_backend_test.go @@ -45,7 +45,7 @@ func TestMetaBackend_emptyDir(t *testing.T) { t.Fatalf("unexpected error: %s", err) } s.WriteState(testState()) - if err := s.PersistState(); err != nil { + if err := s.PersistState(nil); err != nil { t.Fatalf("unexpected error: %s", err) } @@ -134,7 +134,7 @@ func TestMetaBackend_emptyWithDefaultState(t *testing.T) { next := testState() next.RootModule().SetOutputValue("foo", cty.StringVal("bar"), false) s.WriteState(next) - if err := s.PersistState(); err != nil { + if err := s.PersistState(nil); err != nil { t.Fatalf("unexpected error: %s", err) } @@ -205,7 +205,7 @@ func TestMetaBackend_emptyWithExplicitState(t *testing.T) { next := testState() markStateForMatching(next, "bar") // just any change so it shows as different than before s.WriteState(next) - if err := s.PersistState(); err != nil { + if err := s.PersistState(nil); err != nil { t.Fatalf("unexpected error: %s", err) } @@ -265,7 +265,7 @@ func TestMetaBackend_configureNew(t *testing.T) { mark := markStateForMatching(state, "changing") s.WriteState(state) - if err := s.PersistState(); err != nil { + if err := s.PersistState(nil); err != nil { t.Fatalf("unexpected error: %s", err) } @@ -339,7 +339,7 @@ func TestMetaBackend_configureNewWithState(t *testing.T) { state = states.NewState() mark := markStateForMatching(state, "changing") - if err := statemgr.WriteAndPersist(s, state); err != nil { + if err := statemgr.WriteAndPersist(s, state, nil); err != nil { t.Fatalf("unexpected error: %s", err) } @@ -505,7 +505,7 @@ func TestMetaBackend_configureNewWithStateExisting(t *testing.T) { mark := markStateForMatching(state, "changing") s.WriteState(state) - if err := s.PersistState(); err != nil { + if err := s.PersistState(nil); err != nil { t.Fatalf("unexpected error: %s", err) } @@ -576,7 +576,7 @@ func TestMetaBackend_configureNewWithStateExistingNoMigrate(t *testing.T) { state = states.NewState() mark := markStateForMatching(state, "changing") s.WriteState(state) - if err := s.PersistState(); err != nil { + if err := s.PersistState(nil); err != nil { t.Fatalf("unexpected error: %s", err) } @@ -695,7 +695,7 @@ func TestMetaBackend_configuredChange(t *testing.T) { mark := markStateForMatching(state, "changing") s.WriteState(state) - if err := s.PersistState(); err != nil { + if err := s.PersistState(nil); err != nil { t.Fatalf("unexpected error: %s", err) } @@ -1448,7 +1448,7 @@ func TestMetaBackend_configuredUnset(t *testing.T) { // Write some state s.WriteState(testState()) - if err := s.PersistState(); err != nil { + if err := s.PersistState(nil); err != nil { t.Fatalf("unexpected error: %s", err) } @@ -1506,7 +1506,7 @@ func TestMetaBackend_configuredUnsetCopy(t *testing.T) { // Write some state s.WriteState(testState()) - if err := s.PersistState(); err != nil { + if err := s.PersistState(nil); err != nil { t.Fatalf("unexpected error: %s", err) } @@ -1585,7 +1585,7 @@ func TestMetaBackend_planLocal(t *testing.T) { mark := markStateForMatching(state, "changing") s.WriteState(state) - if err := s.PersistState(); err != nil { + if err := s.PersistState(nil); err != nil { t.Fatalf("unexpected error: %s", err) } @@ -1686,7 +1686,7 @@ func TestMetaBackend_planLocalStatePath(t *testing.T) { mark := markStateForMatching(state, "changing") s.WriteState(state) - if err := s.PersistState(); err != nil { + if err := s.PersistState(nil); err != nil { t.Fatalf("unexpected error: %s", err) } @@ -1773,7 +1773,7 @@ func TestMetaBackend_planLocalMatch(t *testing.T) { mark := markStateForMatching(state, "changing") s.WriteState(state) - if err := s.PersistState(); err != nil { + if err := s.PersistState(nil); err != nil { t.Fatalf("unexpected error: %s", err) } diff --git a/internal/command/show.go b/internal/command/show.go index 123b86536e..c3486cbac7 100644 --- a/internal/command/show.go +++ b/internal/command/show.go @@ -110,20 +110,8 @@ func (c *ShowCommand) show(path string) (*plans.Plan, *statefile.File, *configs. // Get schemas, if possible if config != nil || stateFile != nil { - opts, err := c.contextOpts() - if err != nil { - diags = diags.Append(err) - return plan, stateFile, config, schemas, diags - } - tfCtx, ctxDiags := terraform.NewContext(opts) - diags = diags.Append(ctxDiags) - if ctxDiags.HasErrors() { - return plan, stateFile, config, schemas, diags - } - var schemaDiags tfdiags.Diagnostics - schemas, schemaDiags = tfCtx.Schemas(config, stateFile.State) - diags = diags.Append(schemaDiags) - if schemaDiags.HasErrors() { + schemas, diags = getSchemas(&c.Meta, stateFile.State, config) + if diags.HasErrors() { return plan, stateFile, config, schemas, diags } } diff --git a/internal/command/state_mv.go b/internal/command/state_mv.go index 949f6c4b45..a0ceb4368a 100644 --- a/internal/command/state_mv.go +++ b/internal/command/state_mv.go @@ -2,6 +2,7 @@ package command import ( "fmt" + "os" "strings" "github.com/hashicorp/terraform/internal/addrs" @@ -385,12 +386,31 @@ func (c *StateMvCommand) Run(args []string) int { return 0 // This is as far as we go in dry-run mode } + path, err := os.Getwd() + if err != nil { + // MBANG TODO - add warning here too? + return 1 + } + + config, diags := c.loadConfig(path) + if diags.HasErrors() { + c.Ui.Error(fmt.Sprintf(errStateRmPersist, err)) + return 1 + } + + schemas, diags := getSchemas(&c.Meta, stateTo, config) + if diags.HasErrors() { + // MBANG TODO - is this the warning? + c.Ui.Error(fmt.Sprintf(errStateRmPersist, err)) + return 1 + } + // Write the new state if err := stateToMgr.WriteState(stateTo); err != nil { c.Ui.Error(fmt.Sprintf(errStateRmPersist, err)) return 1 } - if err := stateToMgr.PersistState(); err != nil { + if err := stateToMgr.PersistState(schemas); err != nil { c.Ui.Error(fmt.Sprintf(errStateRmPersist, err)) return 1 } @@ -401,7 +421,7 @@ func (c *StateMvCommand) Run(args []string) int { c.Ui.Error(fmt.Sprintf(errStateRmPersist, err)) return 1 } - if err := stateFromMgr.PersistState(); err != nil { + if err := stateFromMgr.PersistState(schemas); err != nil { c.Ui.Error(fmt.Sprintf(errStateRmPersist, err)) return 1 } diff --git a/internal/command/state_push.go b/internal/command/state_push.go index 0b863740c5..3654e24c76 100644 --- a/internal/command/state_push.go +++ b/internal/command/state_push.go @@ -126,11 +126,28 @@ func (c *StatePushCommand) Run(args []string) int { c.Ui.Error(fmt.Sprintf("Failed to write state: %s", err)) return 1 } + + // Get schemas, if possible, before writing state + path, err := os.Getwd() + if err != nil { + return 1 + } + config, diags := c.loadConfig(path) + if diags.HasErrors() { + // MBANG TODO - add warnings here? + return 1 + } + schemas, diags := getSchemas(&c.Meta, srcStateFile.State, config) + if diags.HasErrors() { + c.Ui.Error(fmt.Sprintf("Failed to load schemas: %s", err)) + return 1 + } + if err := stateMgr.WriteState(srcStateFile.State); err != nil { c.Ui.Error(fmt.Sprintf("Failed to write state: %s", err)) return 1 } - if err := stateMgr.PersistState(); err != nil { + if err := stateMgr.PersistState(schemas); err != nil { c.Ui.Error(fmt.Sprintf("Failed to persist state: %s", err)) return 1 } diff --git a/internal/command/state_push_test.go b/internal/command/state_push_test.go index e30010bb9e..f79efa9b3b 100644 --- a/internal/command/state_push_test.go +++ b/internal/command/state_push_test.go @@ -267,7 +267,7 @@ func TestStatePush_forceRemoteState(t *testing.T) { if err := sMgr.WriteState(states.NewState()); err != nil { t.Fatal(err) } - if err := sMgr.PersistState(); err != nil { + if err := sMgr.PersistState(nil); err != nil { t.Fatal(err) } diff --git a/internal/command/state_replace_provider.go b/internal/command/state_replace_provider.go index ec5347a769..015a214335 100644 --- a/internal/command/state_replace_provider.go +++ b/internal/command/state_replace_provider.go @@ -2,6 +2,7 @@ package command import ( "fmt" + "os" "strings" "github.com/hashicorp/terraform/internal/addrs" @@ -160,12 +161,30 @@ func (c *StateReplaceProviderCommand) Run(args []string) int { resource.ProviderConfig.Provider = to } + // Get schemas, if possible, before writing state + path, err := os.Getwd() + if err != nil { + return 1 + } + + config, diags := c.loadConfig(path) + if diags.HasErrors() { + // MBANG TODO - add warnings here? + return 1 + } + + schemas, diags := getSchemas(&c.Meta, state, config) + if diags.HasErrors() { + c.Ui.Error(fmt.Sprintf("Failed to load schemas: %s", err)) + return 1 + } + // Write the updated state if err := stateMgr.WriteState(state); err != nil { c.Ui.Error(fmt.Sprintf(errStateRmPersist, err)) return 1 } - if err := stateMgr.PersistState(); err != nil { + if err := stateMgr.PersistState(schemas); err != nil { c.Ui.Error(fmt.Sprintf(errStateRmPersist, err)) return 1 } diff --git a/internal/command/state_rm.go b/internal/command/state_rm.go index f126c5f5a5..a8a93d98c8 100644 --- a/internal/command/state_rm.go +++ b/internal/command/state_rm.go @@ -2,6 +2,7 @@ package command import ( "fmt" + "os" "strings" "github.com/hashicorp/terraform/internal/addrs" @@ -110,11 +111,30 @@ func (c *StateRmCommand) Run(args []string) int { return 0 // This is as far as we go in dry-run mode } + // Get schemas, if possible, before writing state + path, err := os.Getwd() + if err != nil { + // MBANG TODO - add warnings here? + return 1 + } + + config, diags := c.loadConfig(path) + if diags.HasErrors() { + c.Ui.Error(fmt.Sprintf(errStateRmPersist, err)) + return 1 + } + + schemas, diags := getSchemas(&c.Meta, state, config) + if diags.HasErrors() { + c.Ui.Error(fmt.Sprintf(errStateRmPersist, err)) + return 1 + } + if err := stateMgr.WriteState(state); err != nil { c.Ui.Error(fmt.Sprintf(errStateRmPersist, err)) return 1 } - if err := stateMgr.PersistState(); err != nil { + if err := stateMgr.PersistState(schemas); err != nil { c.Ui.Error(fmt.Sprintf(errStateRmPersist, err)) return 1 } diff --git a/internal/command/taint.go b/internal/command/taint.go index 0c5a499f2e..b3ced4219c 100644 --- a/internal/command/taint.go +++ b/internal/command/taint.go @@ -2,6 +2,7 @@ package command import ( "fmt" + "os" "strings" "github.com/hashicorp/terraform/internal/addrs" @@ -125,6 +126,25 @@ func (c *TaintCommand) Run(args []string) int { return 1 } + // Get schemas, if possible, before writing state + path, err := os.Getwd() + if err != nil { + // MBANG TODO - add warnings here? + return 1 + } + + config, diags := c.loadConfig(path) + if diags.HasErrors() { + c.Ui.Error(fmt.Sprintf("Failed to load config: %s", err)) + return 1 + } + + schemas, diags := getSchemas(&c.Meta, state, config) + if diags.HasErrors() { + c.Ui.Error(fmt.Sprintf("Failed to load config: %s", err)) + return 1 + } + ss := state.SyncWrapper() // Get the resource and instance we're going to taint @@ -171,7 +191,7 @@ func (c *TaintCommand) Run(args []string) int { c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err)) return 1 } - if err := stateMgr.PersistState(); err != nil { + if err := stateMgr.PersistState(schemas); err != nil { c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err)) return 1 } diff --git a/internal/command/untaint.go b/internal/command/untaint.go index ba290a8a47..c7c5174b02 100644 --- a/internal/command/untaint.go +++ b/internal/command/untaint.go @@ -2,6 +2,7 @@ package command import ( "fmt" + "os" "strings" "github.com/hashicorp/terraform/internal/addrs" @@ -163,6 +164,25 @@ func (c *UntaintCommand) Run(args []string) int { c.showDiagnostics(diags) return 1 } + + // Get schemas, if possible, before writing state + path, err := os.Getwd() + if err != nil { + // MBANG TODO - add warnings here? + return 1 + } + + config, diags := c.loadConfig(path) + if diags.HasErrors() { + c.Ui.Error(fmt.Sprintf("Failed to load config: %s", err)) + return 1 + } + + schemas, diags := getSchemas(&c.Meta, state, config) + if diags.HasErrors() { + c.Ui.Error(fmt.Sprintf("Failed to load config: %s", err)) + return 1 + } obj.Status = states.ObjectReady ss.SetResourceInstanceCurrent(addr, obj, rs.ProviderConfig) @@ -170,7 +190,7 @@ func (c *UntaintCommand) Run(args []string) int { c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err)) return 1 } - if err := stateMgr.PersistState(); err != nil { + if err := stateMgr.PersistState(schemas); err != nil { c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err)) return 1 } diff --git a/internal/command/workspace_new.go b/internal/command/workspace_new.go index cd28e69867..3898562207 100644 --- a/internal/command/workspace_new.go +++ b/internal/command/workspace_new.go @@ -156,7 +156,7 @@ func (c *WorkspaceNewCommand) Run(args []string) int { c.Ui.Error(err.Error()) return 1 } - err = stateMgr.PersistState() + err = stateMgr.PersistState(nil) if err != nil { c.Ui.Error(err.Error()) return 1 diff --git a/internal/states/remote/state.go b/internal/states/remote/state.go index ae123f8e36..412adc3eb4 100644 --- a/internal/states/remote/state.go +++ b/internal/states/remote/state.go @@ -10,6 +10,7 @@ import ( "github.com/hashicorp/terraform/internal/states" "github.com/hashicorp/terraform/internal/states/statefile" "github.com/hashicorp/terraform/internal/states/statemgr" + "github.com/hashicorp/terraform/internal/terraform" ) // State implements the State interfaces in the state package to handle @@ -153,7 +154,7 @@ func (s *State) refreshState() error { } // statemgr.Persister impl. -func (s *State) PersistState() error { +func (s *State) PersistState(schemas *terraform.Schemas) error { s.mu.Lock() defer s.mu.Unlock() diff --git a/internal/states/remote/state_test.go b/internal/states/remote/state_test.go index 1089ba1aae..721a7a0af3 100644 --- a/internal/states/remote/state_test.go +++ b/internal/states/remote/state_test.go @@ -37,7 +37,7 @@ func TestStateRace(t *testing.T) { go func() { defer wg.Done() s.WriteState(current) - s.PersistState() + s.PersistState(nil) s.RefreshState() }() } @@ -252,7 +252,7 @@ func TestStatePersist(t *testing.T) { if err := mgr.WriteState(s); err != nil { t.Fatalf("failed to WriteState for %q: %s", tc.name, err) } - if err := mgr.PersistState(); err != nil { + if err := mgr.PersistState(nil); err != nil { t.Fatalf("failed to PersistState for %q: %s", tc.name, err) } @@ -447,7 +447,7 @@ func TestWriteStateForMigration(t *testing.T) { // At this point we should just do a normal write and persist // as would happen from the CLI mgr.WriteState(mgr.State()) - mgr.PersistState() + mgr.PersistState(nil) if logIdx >= len(mockClient.log) { t.Fatalf("request lock and index are out of sync on %q: idx=%d len=%d", tc.name, logIdx, len(mockClient.log)) @@ -611,7 +611,7 @@ func TestWriteStateForMigrationWithForcePushClient(t *testing.T) { // At this point we should just do a normal write and persist // as would happen from the CLI mgr.WriteState(mgr.State()) - mgr.PersistState() + mgr.PersistState(nil) if logIdx >= len(mockClient.log) { t.Fatalf("request lock and index are out of sync on %q: idx=%d len=%d", tc.name, logIdx, len(mockClient.log)) diff --git a/internal/states/statemgr/filesystem.go b/internal/states/statemgr/filesystem.go index 1406f04656..bdfc6832b5 100644 --- a/internal/states/statemgr/filesystem.go +++ b/internal/states/statemgr/filesystem.go @@ -16,6 +16,7 @@ import ( "github.com/hashicorp/terraform/internal/states" "github.com/hashicorp/terraform/internal/states/statefile" + "github.com/hashicorp/terraform/internal/terraform" ) // Filesystem is a full state manager that uses a file in the local filesystem @@ -223,7 +224,7 @@ func (s *Filesystem) writeState(state *states.State, meta *SnapshotMeta) error { // PersistState is an implementation of Persister that does nothing because // this type's Writer implementation does its own persistence. -func (s *Filesystem) PersistState() error { +func (s *Filesystem) PersistState(schemas *terraform.Schemas) error { return nil } diff --git a/internal/states/statemgr/helper.go b/internal/states/statemgr/helper.go index a019b2c431..6cae85702e 100644 --- a/internal/states/statemgr/helper.go +++ b/internal/states/statemgr/helper.go @@ -6,6 +6,7 @@ package statemgr import ( "github.com/hashicorp/terraform/internal/states" "github.com/hashicorp/terraform/internal/states/statefile" + "github.com/hashicorp/terraform/internal/terraform" "github.com/hashicorp/terraform/version" ) @@ -44,10 +45,10 @@ func RefreshAndRead(mgr Storage) (*states.State, error) { // out quickly with a user-facing error. In situations where more control // is required, call WriteState and PersistState on the state manager directly // and handle their errors. -func WriteAndPersist(mgr Storage, state *states.State) error { +func WriteAndPersist(mgr Storage, state *states.State, schemas *terraform.Schemas) error { err := mgr.WriteState(state) if err != nil { return err } - return mgr.PersistState() + return mgr.PersistState(schemas) } diff --git a/internal/states/statemgr/lock.go b/internal/states/statemgr/lock.go index 79c149fe73..863dc2f0dd 100644 --- a/internal/states/statemgr/lock.go +++ b/internal/states/statemgr/lock.go @@ -1,6 +1,9 @@ package statemgr -import "github.com/hashicorp/terraform/internal/states" +import ( + "github.com/hashicorp/terraform/internal/states" + "github.com/hashicorp/terraform/internal/terraform" +) // LockDisabled implements State and Locker but disables state locking. // If State doesn't support locking, this is a no-op. This is useful for @@ -27,8 +30,8 @@ func (s *LockDisabled) RefreshState() error { return s.Inner.RefreshState() } -func (s *LockDisabled) PersistState() error { - return s.Inner.PersistState() +func (s *LockDisabled) PersistState(schemas *terraform.Schemas) error { + return s.Inner.PersistState(schemas) } func (s *LockDisabled) Lock(info *LockInfo) (string, error) { diff --git a/internal/states/statemgr/persistent.go b/internal/states/statemgr/persistent.go index fde4a7f0f8..70d709f85f 100644 --- a/internal/states/statemgr/persistent.go +++ b/internal/states/statemgr/persistent.go @@ -4,6 +4,7 @@ import ( version "github.com/hashicorp/go-version" "github.com/hashicorp/terraform/internal/states" + "github.com/hashicorp/terraform/internal/terraform" ) // Persistent is a union of the Refresher and Persistent interfaces, for types @@ -72,8 +73,12 @@ type Refresher interface { // is most commonly achieved by making use of atomic write capabilities on // the remote storage backend in conjunction with book-keeping with the // Serial and Lineage fields in the standard state file formats. +// +// Some implementations may optionally utilize config schema to persist +// state. For example, when representing state in an external JSON +// representation. type Persister interface { - PersistState() error + PersistState(*terraform.Schemas) error } // PersistentMeta is an optional extension to Persistent that allows inspecting diff --git a/internal/states/statemgr/statemgr_fake.go b/internal/states/statemgr/statemgr_fake.go index 8d88e4d24e..985e6c6775 100644 --- a/internal/states/statemgr/statemgr_fake.go +++ b/internal/states/statemgr/statemgr_fake.go @@ -5,6 +5,7 @@ import ( "sync" "github.com/hashicorp/terraform/internal/states" + "github.com/hashicorp/terraform/internal/terraform" ) // NewFullFake returns a full state manager that really only supports transient @@ -61,7 +62,7 @@ func (m *fakeFull) RefreshState() error { return m.t.WriteState(m.fakeP.State()) } -func (m *fakeFull) PersistState() error { +func (m *fakeFull) PersistState(schemas *terraform.Schemas) error { return m.fakeP.WriteState(m.t.State()) } @@ -127,7 +128,7 @@ func (m *fakeErrorFull) RefreshState() error { return errors.New("fake state manager error") } -func (m *fakeErrorFull) PersistState() error { +func (m *fakeErrorFull) PersistState(schemas *terraform.Schemas) error { return errors.New("fake state manager error") } diff --git a/internal/states/statemgr/testing.go b/internal/states/statemgr/testing.go index 171b21ad2e..eabf46dc0e 100644 --- a/internal/states/statemgr/testing.go +++ b/internal/states/statemgr/testing.go @@ -56,7 +56,7 @@ func TestFull(t *testing.T, s Full) { } // Test persistence - if err := s.PersistState(); err != nil { + if err := s.PersistState(nil); err != nil { t.Fatalf("err: %s", err) } @@ -81,7 +81,7 @@ func TestFull(t *testing.T, s Full) { if err := s.WriteState(current); err != nil { t.Fatalf("err: %s", err) } - if err := s.PersistState(); err != nil { + if err := s.PersistState(nil); err != nil { t.Fatalf("err: %s", err) } @@ -104,7 +104,7 @@ func TestFull(t *testing.T, s Full) { if err := s.WriteState(current); err != nil { t.Fatalf("err: %s", err) } - if err := s.PersistState(); err != nil { + if err := s.PersistState(nil); err != nil { t.Fatalf("err: %s", err) } diff --git a/internal/states/statemgr/transient.go b/internal/states/statemgr/transient.go index 0ac9b1deda..e47683e98b 100644 --- a/internal/states/statemgr/transient.go +++ b/internal/states/statemgr/transient.go @@ -57,7 +57,7 @@ type Reader interface { // since the caller may continue to modify the given state object after // WriteState returns. type Writer interface { - // Write state saves a transient snapshot of the given state. + // WriteState saves a transient snapshot of the given state. // // The caller must ensure that the given state object is not concurrently // modified while a WriteState call is in progress. WriteState itself