diff --git a/backend/atlas/backend.go b/backend/atlas/backend.go index a86983829c..6b7f274c3a 100644 --- a/backend/atlas/backend.go +++ b/backend/atlas/backend.go @@ -179,6 +179,10 @@ func (b *Backend) StateMgr(name string) (statemgr.Full, error) { return &remote.State{Client: b.stateClient}, nil } +func (b *Backend) StateMgrWithoutCheckVersion(name string) (statemgr.Full, error) { + return b.StateMgr(name) +} + // Colorize returns the Colorize structure that can be used for colorizing // output. This is gauranteed to always return a non-nil value and so is useful // as a helper to wrap any potentially colored strings. diff --git a/backend/backend.go b/backend/backend.go index 391a892468..0280345fee 100644 --- a/backend/backend.go +++ b/backend/backend.go @@ -103,6 +103,18 @@ type Backend interface { // PersistState is called, depending on the state manager implementation. StateMgr(workspace string) (statemgr.Full, error) + // StateMgrWithoutCheckVersion returns the state manager for the given + // workspace name, while ensuring that Terraform version checks are not + // performed if the backend needs to read a state file in order to + // initialize the state manager. + // + // For backends which do not need to read a state file at this point, this + // is identical to StateMgr. + // + // This is used to facilitate reading compatible state files from newer + // versions of Terraform. + StateMgrWithoutCheckVersion(workspace string) (statemgr.Full, error) + // DeleteWorkspace removes the workspace with the given name if it exists. // // DeleteWorkspace cannot prevent deleting a state that is in use. It is diff --git a/backend/local/backend.go b/backend/local/backend.go index 866c4899a3..f92a2cd90e 100644 --- a/backend/local/backend.go +++ b/backend/local/backend.go @@ -279,6 +279,10 @@ func (b *Local) StateMgr(name string) (statemgr.Full, error) { return s, nil } +func (b *Local) StateMgrWithoutCheckVersion(name string) (statemgr.Full, error) { + return b.StateMgr(name) +} + // Operation implements backend.Enhanced // // This will initialize an in-memory terraform.Context to perform the diff --git a/backend/nil.go b/backend/nil.go index 8c46f49d00..946d870b22 100644 --- a/backend/nil.go +++ b/backend/nil.go @@ -31,6 +31,10 @@ func (Nil) StateMgr(string) (statemgr.Full, error) { return statemgr.NewFullFake(statemgr.NewTransientInMemory(nil), nil), nil } +func (Nil) StateMgrWithoutCheckVersion(string) (statemgr.Full, error) { + return statemgr.NewFullFake(statemgr.NewTransientInMemory(nil), nil), nil +} + func (Nil) DeleteWorkspace(string) error { return nil } diff --git a/backend/remote-state/artifactory/backend.go b/backend/remote-state/artifactory/backend.go index 2062968afc..5bc1756dba 100644 --- a/backend/remote-state/artifactory/backend.go +++ b/backend/remote-state/artifactory/backend.go @@ -100,3 +100,7 @@ func (b *Backend) StateMgr(name string) (statemgr.Full, error) { Client: b.client, }, nil } + +func (b *Backend) StateMgrWithoutCheckVersion(name string) (statemgr.Full, error) { + return b.StateMgr(name) +} diff --git a/backend/remote-state/azure/backend_state.go b/backend/remote-state/azure/backend_state.go index e7d3162872..20fb58e569 100644 --- a/backend/remote-state/azure/backend_state.go +++ b/backend/remote-state/azure/backend_state.go @@ -79,6 +79,14 @@ func (b *Backend) DeleteWorkspace(name string) error { } func (b *Backend) StateMgr(name string) (statemgr.Full, error) { + return b.stateMgr(name, true) +} + +func (b *Backend) StateMgrWithoutCheckVersion(name string) (statemgr.Full, error) { + return b.stateMgr(name, false) +} + +func (b *Backend) stateMgr(name string, checkVersion bool) (statemgr.Full, error) { ctx := context.TODO() blobClient, err := b.armClient.getBlobClient(ctx) if err != nil { @@ -115,9 +123,16 @@ func (b *Backend) StateMgr(name string) (statemgr.Full, error) { } // Grab the value - if err := stateMgr.RefreshState(); err != nil { - err = lockUnlock(err) - return nil, err + if checkVersion { + if err := stateMgr.RefreshState(); err != nil { + err = lockUnlock(err) + return nil, err + } + } else { + if err := stateMgr.RefreshStateWithoutCheckVersion(); err != nil { + err = lockUnlock(err) + return nil, err + } } // If we have no state, we have to create an empty state diff --git a/backend/remote-state/consul/backend_state.go b/backend/remote-state/consul/backend_state.go index 30a12afeac..cf75face68 100644 --- a/backend/remote-state/consul/backend_state.go +++ b/backend/remote-state/consul/backend_state.go @@ -64,6 +64,14 @@ func (b *Backend) DeleteWorkspace(name string) error { } func (b *Backend) StateMgr(name string) (statemgr.Full, error) { + return b.stateMgr(name, true) +} + +func (b *Backend) StateMgrWithoutCheckVersion(name string) (statemgr.Full, error) { + return b.stateMgr(name, false) +} + +func (b *Backend) stateMgr(name string, checkVersion bool) (statemgr.Full, error) { // Determine the path of the data path := b.path(name) @@ -109,9 +117,16 @@ func (b *Backend) StateMgr(name string) (statemgr.Full, error) { } // Grab the value - if err := stateMgr.RefreshState(); err != nil { - err = lockUnlock(err) - return nil, err + if checkVersion { + if err := stateMgr.RefreshState(); err != nil { + err = lockUnlock(err) + return nil, err + } + } else { + if err := stateMgr.RefreshStateWithoutCheckVersion(); err != nil { + err = lockUnlock(err) + return nil, err + } } // If we have no state, we have to create an empty state diff --git a/backend/remote-state/cos/backend_state.go b/backend/remote-state/cos/backend_state.go index 7784ab4ff3..a47498c79c 100644 --- a/backend/remote-state/cos/backend_state.go +++ b/backend/remote-state/cos/backend_state.go @@ -75,6 +75,14 @@ func (b *Backend) DeleteWorkspace(name string) error { // StateMgr manage the state, if the named state not exists, a new file will created func (b *Backend) StateMgr(name string) (statemgr.Full, error) { + return b.stateMgr(name, true) +} + +func (b *Backend) StateMgrWithoutCheckVersion(name string) (statemgr.Full, error) { + return b.stateMgr(name, false) +} + +func (b *Backend) stateMgr(name string, checkVersion bool) (statemgr.Full, error) { log.Printf("[DEBUG] state manager, current workspace: %v", name) c, err := b.client(name) @@ -108,9 +116,16 @@ func (b *Backend) StateMgr(name string) (statemgr.Full, error) { } // Grab the value - if err := stateMgr.RefreshState(); err != nil { - err = lockUnlock(err) - return nil, err + if checkVersion { + if err := stateMgr.RefreshState(); err != nil { + err = lockUnlock(err) + return nil, err + } + } else { + if err := stateMgr.RefreshStateWithoutCheckVersion(); err != nil { + err = lockUnlock(err) + return nil, err + } } // If we have no state, we have to create an empty state diff --git a/backend/remote-state/etcdv2/backend.go b/backend/remote-state/etcdv2/backend.go index 9f9fa0904d..10626bb947 100644 --- a/backend/remote-state/etcdv2/backend.go +++ b/backend/remote-state/etcdv2/backend.go @@ -94,3 +94,7 @@ func (b *Backend) StateMgr(name string) (statemgr.Full, error) { }, }, nil } + +func (b *Backend) StateMgrWithoutCheckVersion(name string) (statemgr.Full, error) { + return b.StateMgr(name) +} diff --git a/backend/remote-state/etcdv3/backend_state.go b/backend/remote-state/etcdv3/backend_state.go index 1b8b1882e7..a8143691ef 100644 --- a/backend/remote-state/etcdv3/backend_state.go +++ b/backend/remote-state/etcdv3/backend_state.go @@ -42,6 +42,14 @@ func (b *Backend) DeleteWorkspace(name string) error { } func (b *Backend) StateMgr(name string) (statemgr.Full, error) { + return b.stateMgr(name, true) +} + +func (b *Backend) StateMgrWithoutCheckVersion(name string) (statemgr.Full, error) { + return b.stateMgr(name, false) +} + +func (b *Backend) stateMgr(name string, checkVersion bool) (statemgr.Full, error) { var stateMgr statemgr.Full = &remote.State{ Client: &RemoteClient{ Client: b.client, @@ -68,9 +76,16 @@ func (b *Backend) StateMgr(name string) (statemgr.Full, error) { return parent } - if err := stateMgr.RefreshState(); err != nil { - err = lockUnlock(err) - return nil, err + if checkVersion { + if err := stateMgr.RefreshState(); err != nil { + err = lockUnlock(err) + return nil, err + } + } else { + if err := stateMgr.RefreshStateWithoutCheckVersion(); err != nil { + err = lockUnlock(err) + return nil, err + } } if v := stateMgr.State(); v == nil { diff --git a/backend/remote-state/gcs/backend_state.go b/backend/remote-state/gcs/backend_state.go index d4916190f1..38bbdca933 100644 --- a/backend/remote-state/gcs/backend_state.go +++ b/backend/remote-state/gcs/backend_state.go @@ -87,6 +87,14 @@ func (b *Backend) client(name string) (*remoteClient, error) { // StateMgr reads and returns the named state from GCS. If the named state does // not yet exist, a new state file is created. func (b *Backend) StateMgr(name string) (statemgr.Full, error) { + return b.stateMgr(name, true) +} + +func (b *Backend) StateMgrWithoutCheckVersion(name string) (statemgr.Full, error) { + return b.stateMgr(name, false) +} + +func (b *Backend) stateMgr(name string, checkVersion bool) (statemgr.Full, error) { c, err := b.client(name) if err != nil { return nil, err @@ -95,8 +103,14 @@ func (b *Backend) StateMgr(name string) (statemgr.Full, error) { st := &remote.State{Client: c} // Grab the value - if err := st.RefreshState(); err != nil { - return nil, err + if checkVersion { + if err := st.RefreshState(); err != nil { + return nil, err + } + } else { + if err := st.RefreshStateWithoutCheckVersion(); err != nil { + return nil, err + } } // If we have no state, we have to create an empty state diff --git a/backend/remote-state/http/backend.go b/backend/remote-state/http/backend.go index 12076e01ab..ddc7d5d682 100644 --- a/backend/remote-state/http/backend.go +++ b/backend/remote-state/http/backend.go @@ -188,6 +188,10 @@ func (b *Backend) StateMgr(name string) (statemgr.Full, error) { return &remote.State{Client: b.client}, nil } +func (b *Backend) StateMgrWithoutCheckVersion(name string) (statemgr.Full, error) { + return b.StateMgr(name) +} + func (b *Backend) Workspaces() ([]string, error) { return nil, backend.ErrWorkspacesNotSupported } diff --git a/backend/remote-state/inmem/backend.go b/backend/remote-state/inmem/backend.go index 1a974a05bc..3388889e4b 100644 --- a/backend/remote-state/inmem/backend.go +++ b/backend/remote-state/inmem/backend.go @@ -150,6 +150,10 @@ func (b *Backend) StateMgr(name string) (statemgr.Full, error) { return s, nil } +func (b *Backend) StateMgrWithoutCheckVersion(name string) (statemgr.Full, error) { + return b.StateMgr(name) +} + type stateMap struct { sync.Mutex m map[string]*remote.State diff --git a/backend/remote-state/kubernetes/backend_state.go b/backend/remote-state/kubernetes/backend_state.go index f9c3c76d59..7c7b2827ab 100644 --- a/backend/remote-state/kubernetes/backend_state.go +++ b/backend/remote-state/kubernetes/backend_state.go @@ -72,6 +72,14 @@ func (b *Backend) DeleteWorkspace(name string) error { } func (b *Backend) StateMgr(name string) (statemgr.Full, error) { + return b.stateMgr(name, true) +} + +func (b *Backend) StateMgrWithoutCheckVersion(name string) (statemgr.Full, error) { + return b.stateMgr(name, false) +} + +func (b *Backend) stateMgr(name string, checkVersion bool) (statemgr.Full, error) { c, err := b.remoteClient(name) if err != nil { return nil, err @@ -80,8 +88,14 @@ func (b *Backend) StateMgr(name string) (statemgr.Full, error) { stateMgr := &remote.State{Client: c} // Grab the value - if err := stateMgr.RefreshState(); err != nil { - return nil, err + if checkVersion { + if err := stateMgr.RefreshState(); err != nil { + return nil, err + } + } else { + if err := stateMgr.RefreshStateWithoutCheckVersion(); err != nil { + return nil, err + } } // If we have no state, we have to create an empty state diff --git a/backend/remote-state/manta/backend_state.go b/backend/remote-state/manta/backend_state.go index 6ce9ba0f6f..71f8320821 100644 --- a/backend/remote-state/manta/backend_state.go +++ b/backend/remote-state/manta/backend_state.go @@ -65,6 +65,14 @@ func (b *Backend) DeleteWorkspace(name string) error { } func (b *Backend) StateMgr(name string) (statemgr.Full, error) { + return b.stateMgr(name, true) +} + +func (b *Backend) StateMgrWithoutCheckVersion(name string) (statemgr.Full, error) { + return b.stateMgr(name, false) +} + +func (b *Backend) stateMgr(name string, checkVersion bool) (statemgr.Full, error) { if name == "" { return nil, errors.New("missing state name") } @@ -97,9 +105,16 @@ func (b *Backend) StateMgr(name string) (statemgr.Full, error) { } // Grab the value - if err := stateMgr.RefreshState(); err != nil { - err = lockUnlock(err) - return nil, err + if checkVersion { + if err := stateMgr.RefreshState(); err != nil { + err = lockUnlock(err) + return nil, err + } + } else { + if err := stateMgr.RefreshStateWithoutCheckVersion(); err != nil { + err = lockUnlock(err) + return nil, err + } } // If we have no state, we have to create an empty state diff --git a/backend/remote-state/oss/backend_state.go b/backend/remote-state/oss/backend_state.go index 1af33d2680..84520b03b6 100644 --- a/backend/remote-state/oss/backend_state.go +++ b/backend/remote-state/oss/backend_state.go @@ -107,6 +107,14 @@ func (b *Backend) DeleteWorkspace(name string) error { } func (b *Backend) StateMgr(name string) (statemgr.Full, error) { + return b.stateMgr(name, true) +} + +func (b *Backend) StateMgrWithoutCheckVersion(name string) (statemgr.Full, error) { + return b.stateMgr(name, false) +} + +func (b *Backend) stateMgr(name string, checkVersion bool) (statemgr.Full, error) { client, err := b.remoteClient(name) if err != nil { return nil, err @@ -147,9 +155,16 @@ func (b *Backend) StateMgr(name string) (statemgr.Full, error) { } // Grab the value - if err := stateMgr.RefreshState(); err != nil { - err = lockUnlock(err) - return nil, err + if checkVersion { + if err := stateMgr.RefreshState(); err != nil { + err = lockUnlock(err) + return nil, err + } + } else { + if err := stateMgr.RefreshStateWithoutCheckVersion(); err != nil { + err = lockUnlock(err) + return nil, err + } } // If we have no state, we have to create an empty state diff --git a/backend/remote-state/pg/backend_state.go b/backend/remote-state/pg/backend_state.go index 1cbdc5b6cf..f327e31748 100644 --- a/backend/remote-state/pg/backend_state.go +++ b/backend/remote-state/pg/backend_state.go @@ -111,3 +111,7 @@ func (b *Backend) StateMgr(name string) (statemgr.Full, error) { return stateMgr, nil } + +func (b *Backend) StateMgrWithoutCheckVersion(name string) (statemgr.Full, error) { + return b.StateMgr(name) +} diff --git a/backend/remote-state/s3/backend_state.go b/backend/remote-state/s3/backend_state.go index c6809f5d6c..70e760f411 100644 --- a/backend/remote-state/s3/backend_state.go +++ b/backend/remote-state/s3/backend_state.go @@ -125,6 +125,14 @@ func (b *Backend) remoteClient(name string) (*RemoteClient, error) { } func (b *Backend) StateMgr(name string) (statemgr.Full, error) { + return b.stateMgr(name, true) +} + +func (b *Backend) StateMgrWithoutCheckVersion(name string) (statemgr.Full, error) { + return b.stateMgr(name, false) +} + +func (b *Backend) stateMgr(name string, checkVersion bool) (statemgr.Full, error) { client, err := b.remoteClient(name) if err != nil { return nil, err @@ -173,9 +181,16 @@ func (b *Backend) StateMgr(name string) (statemgr.Full, error) { // Grab the value // This is to ensure that no one beat us to writing a state between // the `exists` check and taking the lock. - if err := stateMgr.RefreshState(); err != nil { - err = lockUnlock(err) - return nil, err + if checkVersion { + if err := stateMgr.RefreshState(); err != nil { + err = lockUnlock(err) + return nil, err + } + } else { + if err := stateMgr.RefreshStateWithoutCheckVersion(); err != nil { + err = lockUnlock(err) + return nil, err + } } // If we have no state, we have to create an empty state diff --git a/backend/remote-state/swift/backend_state.go b/backend/remote-state/swift/backend_state.go index bdc21c79f8..5ad8bb2935 100644 --- a/backend/remote-state/swift/backend_state.go +++ b/backend/remote-state/swift/backend_state.go @@ -92,6 +92,14 @@ func (b *Backend) DeleteWorkspace(name string) error { } func (b *Backend) StateMgr(name string) (statemgr.Full, error) { + return b.stateMgr(name, true) +} + +func (b *Backend) StateMgrWithoutCheckVersion(name string) (statemgr.Full, error) { + return b.stateMgr(name, false) +} + +func (b *Backend) stateMgr(name string, checkVersion bool) (statemgr.Full, error) { if name == "" { return nil, fmt.Errorf("missing state name") } @@ -161,9 +169,16 @@ func (b *Backend) StateMgr(name string) (statemgr.Full, error) { } // Grab the value - if err := stateMgr.RefreshState(); err != nil { - err = lockUnlock(err) - return nil, err + if checkVersion { + if err := stateMgr.RefreshState(); err != nil { + err = lockUnlock(err) + return nil, err + } + } else { + if err := stateMgr.RefreshStateWithoutCheckVersion(); err != nil { + err = lockUnlock(err) + return nil, err + } } // If we have no state, we have to create an empty state diff --git a/backend/remote/backend.go b/backend/remote/backend.go index 77ad26225e..af5e576f63 100644 --- a/backend/remote/backend.go +++ b/backend/remote/backend.go @@ -641,6 +641,10 @@ func (b *Remote) StateMgr(name string) (statemgr.Full, error) { return &remote.State{Client: client}, nil } +func (b *Remote) StateMgrWithoutCheckVersion(name string) (statemgr.Full, error) { + return b.StateMgr(name) +} + // Operation implements backend.Enhanced. func (b *Remote) Operation(ctx context.Context, op *backend.Operation) (*backend.RunningOperation, error) { // Get the remote workspace name. diff --git a/builtin/providers/terraform/data_source_state_test.go b/builtin/providers/terraform/data_source_state_test.go index 6dbc153286..c557931ce0 100644 --- a/builtin/providers/terraform/data_source_state_test.go +++ b/builtin/providers/terraform/data_source_state_test.go @@ -362,6 +362,10 @@ func (b backendFailsConfigure) StateMgr(workspace string) (statemgr.Full, error) return nil, fmt.Errorf("StateMgr not implemented") } +func (b backendFailsConfigure) StateMgrWithoutCheckVersion(workspace string) (statemgr.Full, error) { + return nil, fmt.Errorf("StateMgrWithoutCheckVersion not implemented") +} + func (b backendFailsConfigure) DeleteWorkspace(name string) error { return fmt.Errorf("DeleteWorkspace not implemented") }