update persist state

This commit is contained in:
Megan Bang 2022-08-25 14:57:40 -05:00
parent 03f5085026
commit 4fab46749a
43 changed files with 280 additions and 97 deletions

View File

@ -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.

View File

@ -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.

View File

@ -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")
}

View File

@ -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)

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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

View File

@ -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()

View File

@ -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))

View File

@ -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
}

View File

@ -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)
}

View File

@ -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) {

View File

@ -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

View File

@ -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")
}

View File

@ -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)
}

View File

@ -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