From c8d7515808fd45819ecc711c10ca8191a078b1bc Mon Sep 17 00:00:00 2001 From: Alisdair McDiarmid Date: Fri, 23 Oct 2020 15:46:42 -0400 Subject: [PATCH] backend: StateMgrWithoutCheckVersion Allow users of backends to initialize a state manager instance without checking the Terraform version of any state files which are retrieved during this process. Many backends call RefreshState as part of initialization, and this new method instead calls the new RefreshStateWithoutCheckVersion method to prevent version checking. --- backend/atlas/backend.go | 4 ++++ backend/backend.go | 12 +++++++++++ backend/local/backend.go | 4 ++++ backend/nil.go | 4 ++++ backend/remote-state/artifactory/backend.go | 4 ++++ backend/remote-state/azure/backend_state.go | 21 ++++++++++++++++--- backend/remote-state/consul/backend_state.go | 21 ++++++++++++++++--- backend/remote-state/cos/backend_state.go | 21 ++++++++++++++++--- backend/remote-state/etcdv2/backend.go | 4 ++++ backend/remote-state/etcdv3/backend_state.go | 21 ++++++++++++++++--- backend/remote-state/gcs/backend_state.go | 18 ++++++++++++++-- backend/remote-state/http/backend.go | 4 ++++ backend/remote-state/inmem/backend.go | 4 ++++ .../remote-state/kubernetes/backend_state.go | 18 ++++++++++++++-- backend/remote-state/manta/backend_state.go | 21 ++++++++++++++++--- backend/remote-state/oss/backend_state.go | 21 ++++++++++++++++--- backend/remote-state/pg/backend_state.go | 4 ++++ backend/remote-state/s3/backend_state.go | 21 ++++++++++++++++--- backend/remote-state/swift/backend_state.go | 21 ++++++++++++++++--- backend/remote/backend.go | 4 ++++ .../terraform/data_source_state_test.go | 4 ++++ 21 files changed, 228 insertions(+), 28 deletions(-) diff --git a/backend/atlas/backend.go b/backend/atlas/backend.go index a86983829cae..6b7f274c3a49 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 391a8924685e..0280345fee19 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 866c4899a39a..f92a2cd90ed0 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 8c46f49d0076..946d870b22c3 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 2062968afcdc..5bc1756dbaef 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 e7d31628723b..20fb58e569ec 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 30a12afeac1d..cf75face68f3 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 7784ab4ff301..a47498c79cae 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 9f9fa0904d2f..10626bb94717 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 1b8b1882e737..a8143691efb6 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 d4916190f1d9..38bbdca93318 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 12076e01ab77..ddc7d5d682e5 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 1a974a05bcc4..3388889e4be6 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 f9c3c76d59a5..7c7b2827ab7a 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 6ce9ba0f6f45..71f8320821d2 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 1af33d268030..84520b03b6a7 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 1cbdc5b6cff2..f327e317489e 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 c6809f5d6c15..70e760f411fb 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 bdc21c79f8d2..5ad8bb293548 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 77ad26225ee6..af5e576f639e 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 6dbc15328647..c557931ce01c 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") }