diff --git a/internal/backend/remote/backend.go b/internal/backend/remote/backend.go index edeb96c7cc..e440b2cbab 100644 --- a/internal/backend/remote/backend.go +++ b/internal/backend/remote/backend.go @@ -694,7 +694,18 @@ func (b *Remote) StateMgr(name string) (statemgr.Full, error) { runID: os.Getenv("TFE_RUN_ID"), } - return &remote.State{Client: client}, nil + return &remote.State{ + Client: client, + + // client.runID will be set if we're running a the Terraform Cloud + // or Terraform Enterprise remote execution environment, in which + // case we'll disable intermediate snapshots to avoid extra storage + // costs for Terraform Enterprise customers. + // Other implementations of the remote state protocol should not run + // in contexts where there's a "TFE Run ID" and so are not affected + // by this special case. + DisableIntermediateSnapshots: client.runID != "", + }, nil } func isLocalExecutionMode(execMode string) bool { diff --git a/internal/cloud/state.go b/internal/cloud/state.go index c573b9d45f..2672d0e8df 100644 --- a/internal/cloud/state.go +++ b/internal/cloud/state.go @@ -21,6 +21,8 @@ import ( tfe "github.com/hashicorp/go-tfe" uuid "github.com/hashicorp/go-uuid" + + "github.com/hashicorp/terraform/internal/backend/local" "github.com/hashicorp/terraform/internal/command/jsonstate" "github.com/hashicorp/terraform/internal/states" "github.com/hashicorp/terraform/internal/states/remote" @@ -66,6 +68,7 @@ remote state version. var _ statemgr.Full = (*State)(nil) var _ statemgr.Migrator = (*State)(nil) +var _ local.IntermediateStateConditionalPersister = (*State)(nil) // statemgr.Reader impl. func (s *State) State() *states.State { @@ -223,6 +226,14 @@ func (s *State) PersistState(schemas *terraform.Schemas) error { return nil } +// ShouldPersistIntermediateState implements local.IntermediateStateConditionalPersister +func (*State) ShouldPersistIntermediateState(info *local.IntermediateStatePersistInfo) bool { + // We currently don't create intermediate snapshots for Terraform Cloud or + // Terraform Enterprise at all, to avoid extra storage costs for Terraform + // Enterprise customers. + return false +} + func (s *State) uploadState(lineage string, serial uint64, isForcePush bool, state, jsonState, jsonStateOutputs []byte) error { ctx := context.Background() diff --git a/internal/states/remote/state.go b/internal/states/remote/state.go index cedd370def..f30a43df5a 100644 --- a/internal/states/remote/state.go +++ b/internal/states/remote/state.go @@ -11,6 +11,7 @@ import ( uuid "github.com/hashicorp/go-uuid" + "github.com/hashicorp/terraform/internal/backend/local" "github.com/hashicorp/terraform/internal/states" "github.com/hashicorp/terraform/internal/states/statefile" "github.com/hashicorp/terraform/internal/states/statemgr" @@ -39,10 +40,17 @@ type State struct { serial, readSerial uint64 state, readState *states.State disableLocks bool + + // If this is set then the state manager will decline to store intermediate + // state snapshots created while a Terraform Core apply operation is in + // progress. Otherwise (by default) it will accept persistent snapshots + // using the default rules defined in the local backend. + DisableIntermediateSnapshots bool } var _ statemgr.Full = (*State)(nil) var _ statemgr.Migrator = (*State)(nil) +var _ local.IntermediateStateConditionalPersister = (*State)(nil) // statemgr.Reader impl. func (s *State) State() *states.State { @@ -219,6 +227,14 @@ func (s *State) PersistState(schemas *terraform.Schemas) error { return nil } +// ShouldPersistIntermediateState implements local.IntermediateStateConditionalPersister +func (s *State) ShouldPersistIntermediateState(info *local.IntermediateStatePersistInfo) bool { + if s.DisableIntermediateSnapshots { + return false + } + return local.DefaultIntermediateStatePersistRule(info) +} + // Lock calls the Client's Lock method if it's implemented. func (s *State) Lock(info *statemgr.LockInfo) (string, error) { s.mu.Lock()