2017-02-18 02:26:38 +01:00
|
|
|
package inmem
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2017-08-01 18:11:04 +02:00
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"sort"
|
|
|
|
"sync"
|
|
|
|
"time"
|
2017-02-18 02:26:38 +01:00
|
|
|
|
2021-05-17 17:42:17 +02:00
|
|
|
"github.com/hashicorp/terraform/internal/backend"
|
2020-11-18 16:07:30 +01:00
|
|
|
"github.com/hashicorp/terraform/internal/legacy/helper/schema"
|
2021-05-17 21:43:35 +02:00
|
|
|
statespkg "github.com/hashicorp/terraform/internal/states"
|
|
|
|
"github.com/hashicorp/terraform/internal/states/remote"
|
|
|
|
"github.com/hashicorp/terraform/internal/states/statemgr"
|
2017-02-18 02:26:38 +01:00
|
|
|
)
|
|
|
|
|
2017-08-01 18:11:04 +02:00
|
|
|
// we keep the states and locks in package-level variables, so that they can be
|
|
|
|
// accessed from multiple instances of the backend. This better emulates
|
|
|
|
// backend instances accessing a single remote data store.
|
2017-08-01 22:15:52 +02:00
|
|
|
var (
|
|
|
|
states stateMap
|
|
|
|
locks lockMap
|
|
|
|
)
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
Reset()
|
2017-08-01 18:11:04 +02:00
|
|
|
}
|
|
|
|
|
2017-08-01 22:15:52 +02:00
|
|
|
// Reset clears out all existing state and lock data.
|
|
|
|
// This is used to initialize the package during init, as well as between
|
|
|
|
// tests.
|
|
|
|
func Reset() {
|
|
|
|
states = stateMap{
|
|
|
|
m: map[string]*remote.State{},
|
|
|
|
}
|
|
|
|
|
|
|
|
locks = lockMap{
|
2020-08-11 17:43:01 +02:00
|
|
|
m: map[string]*statemgr.LockInfo{},
|
2017-08-01 22:15:52 +02:00
|
|
|
}
|
2017-08-01 18:11:04 +02:00
|
|
|
}
|
|
|
|
|
2017-02-18 02:26:38 +01:00
|
|
|
// New creates a new backend for Inmem remote state.
|
|
|
|
func New() backend.Backend {
|
2017-08-01 18:11:04 +02:00
|
|
|
// Set the schema
|
|
|
|
s := &schema.Backend{
|
|
|
|
Schema: map[string]*schema.Schema{
|
|
|
|
"lock_id": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
|
|
|
Description: "initializes the state in a locked configuration",
|
2017-02-18 02:26:38 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
2017-08-01 18:11:04 +02:00
|
|
|
backend := &Backend{Backend: s}
|
|
|
|
backend.Backend.ConfigureFunc = backend.configure
|
|
|
|
return backend
|
2017-02-18 02:26:38 +01:00
|
|
|
}
|
|
|
|
|
2017-08-01 18:11:04 +02:00
|
|
|
type Backend struct {
|
|
|
|
*schema.Backend
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *Backend) configure(ctx context.Context) error {
|
|
|
|
states.Lock()
|
|
|
|
defer states.Unlock()
|
|
|
|
|
|
|
|
defaultClient := &RemoteClient{
|
|
|
|
Name: backend.DefaultStateName,
|
|
|
|
}
|
|
|
|
|
|
|
|
states.m[backend.DefaultStateName] = &remote.State{
|
|
|
|
Client: defaultClient,
|
|
|
|
}
|
|
|
|
|
|
|
|
// set the default client lock info per the test config
|
2017-02-18 02:26:38 +01:00
|
|
|
data := schema.FromContextBackendConfig(ctx)
|
|
|
|
if v, ok := data.GetOk("lock_id"); ok && v.(string) != "" {
|
2020-08-11 17:43:01 +02:00
|
|
|
info := statemgr.NewLockInfo()
|
2017-02-18 02:26:38 +01:00
|
|
|
info.ID = v.(string)
|
|
|
|
info.Operation = "test"
|
|
|
|
info.Info = "test config"
|
2017-08-01 18:11:04 +02:00
|
|
|
|
|
|
|
locks.lock(backend.DefaultStateName, info)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
terraform: Ugly huge change to weave in new State and Plan types
Due to how often the state and plan types are referenced throughout
Terraform, there isn't a great way to switch them out gradually. As a
consequence, this huge commit gets us from the old world to a _compilable_
new world, but still has a large number of known test failures due to
key functionality being stubbed out.
The stubs here are for anything that interacts with providers, since we
now need to do the follow-up work to similarly replace the old
terraform.ResourceProvider interface with its replacement in the new
"providers" package. That work, along with work to fix the remaining
failing tests, will follow in subsequent commits.
The aim here was to replace all references to terraform.State and its
downstream types with states.State, terraform.Plan with plans.Plan,
state.State with statemgr.State, and switch to the new implementations of
the state and plan file formats. However, due to the number of times those
types are used, this also ended up affecting numerous other parts of core
such as terraform.Hook, the backend.Backend interface, and most of the CLI
commands.
Just as with 5861dbf3fc49b19587a31816eb06f511ab861bb4 before, I apologize
in advance to the person who inevitably just found this huge commit while
spelunking through the commit history.
2018-08-14 23:24:45 +02:00
|
|
|
func (b *Backend) Workspaces() ([]string, error) {
|
2017-08-01 18:11:04 +02:00
|
|
|
states.Lock()
|
|
|
|
defer states.Unlock()
|
|
|
|
|
|
|
|
var workspaces []string
|
|
|
|
|
|
|
|
for s := range states.m {
|
|
|
|
workspaces = append(workspaces, s)
|
2017-02-18 02:26:38 +01:00
|
|
|
}
|
2017-08-01 18:11:04 +02:00
|
|
|
|
|
|
|
sort.Strings(workspaces)
|
|
|
|
return workspaces, nil
|
|
|
|
}
|
|
|
|
|
terraform: Ugly huge change to weave in new State and Plan types
Due to how often the state and plan types are referenced throughout
Terraform, there isn't a great way to switch them out gradually. As a
consequence, this huge commit gets us from the old world to a _compilable_
new world, but still has a large number of known test failures due to
key functionality being stubbed out.
The stubs here are for anything that interacts with providers, since we
now need to do the follow-up work to similarly replace the old
terraform.ResourceProvider interface with its replacement in the new
"providers" package. That work, along with work to fix the remaining
failing tests, will follow in subsequent commits.
The aim here was to replace all references to terraform.State and its
downstream types with states.State, terraform.Plan with plans.Plan,
state.State with statemgr.State, and switch to the new implementations of
the state and plan file formats. However, due to the number of times those
types are used, this also ended up affecting numerous other parts of core
such as terraform.Hook, the backend.Backend interface, and most of the CLI
commands.
Just as with 5861dbf3fc49b19587a31816eb06f511ab861bb4 before, I apologize
in advance to the person who inevitably just found this huge commit while
spelunking through the commit history.
2018-08-14 23:24:45 +02:00
|
|
|
func (b *Backend) DeleteWorkspace(name string) error {
|
2017-08-01 18:11:04 +02:00
|
|
|
states.Lock()
|
|
|
|
defer states.Unlock()
|
|
|
|
|
|
|
|
if name == backend.DefaultStateName || name == "" {
|
|
|
|
return fmt.Errorf("can't delete default state")
|
|
|
|
}
|
|
|
|
|
|
|
|
delete(states.m, name)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-08-11 17:43:01 +02:00
|
|
|
func (b *Backend) StateMgr(name string) (statemgr.Full, error) {
|
2017-08-01 18:11:04 +02:00
|
|
|
states.Lock()
|
|
|
|
defer states.Unlock()
|
|
|
|
|
|
|
|
s := states.m[name]
|
|
|
|
if s == nil {
|
|
|
|
s = &remote.State{
|
|
|
|
Client: &RemoteClient{
|
|
|
|
Name: name,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
states.m[name] = s
|
2017-08-01 23:25:24 +02:00
|
|
|
|
|
|
|
// to most closely replicate other implementations, we are going to
|
|
|
|
// take a lock and create a new state if it doesn't exist.
|
2020-08-11 17:43:01 +02:00
|
|
|
lockInfo := statemgr.NewLockInfo()
|
2017-08-01 23:25:24 +02:00
|
|
|
lockInfo.Operation = "init"
|
|
|
|
lockID, err := s.Lock(lockInfo)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to lock inmem state: %s", err)
|
|
|
|
}
|
|
|
|
defer s.Unlock(lockID)
|
|
|
|
|
|
|
|
// If we have no state, we have to create an empty state
|
|
|
|
if v := s.State(); v == nil {
|
terraform: Ugly huge change to weave in new State and Plan types
Due to how often the state and plan types are referenced throughout
Terraform, there isn't a great way to switch them out gradually. As a
consequence, this huge commit gets us from the old world to a _compilable_
new world, but still has a large number of known test failures due to
key functionality being stubbed out.
The stubs here are for anything that interacts with providers, since we
now need to do the follow-up work to similarly replace the old
terraform.ResourceProvider interface with its replacement in the new
"providers" package. That work, along with work to fix the remaining
failing tests, will follow in subsequent commits.
The aim here was to replace all references to terraform.State and its
downstream types with states.State, terraform.Plan with plans.Plan,
state.State with statemgr.State, and switch to the new implementations of
the state and plan file formats. However, due to the number of times those
types are used, this also ended up affecting numerous other parts of core
such as terraform.Hook, the backend.Backend interface, and most of the CLI
commands.
Just as with 5861dbf3fc49b19587a31816eb06f511ab861bb4 before, I apologize
in advance to the person who inevitably just found this huge commit while
spelunking through the commit history.
2018-08-14 23:24:45 +02:00
|
|
|
if err := s.WriteState(statespkg.NewState()); err != nil {
|
2017-08-01 23:25:24 +02:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if err := s.PersistState(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
2017-08-01 18:11:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return s, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type stateMap struct {
|
|
|
|
sync.Mutex
|
|
|
|
m map[string]*remote.State
|
|
|
|
}
|
|
|
|
|
|
|
|
// Global level locks for inmem backends.
|
|
|
|
type lockMap struct {
|
|
|
|
sync.Mutex
|
2020-08-11 17:43:01 +02:00
|
|
|
m map[string]*statemgr.LockInfo
|
2017-08-01 18:11:04 +02:00
|
|
|
}
|
|
|
|
|
2020-08-11 17:43:01 +02:00
|
|
|
func (l *lockMap) lock(name string, info *statemgr.LockInfo) (string, error) {
|
2017-08-01 18:11:04 +02:00
|
|
|
l.Lock()
|
|
|
|
defer l.Unlock()
|
|
|
|
|
|
|
|
lockInfo := l.m[name]
|
|
|
|
if lockInfo != nil {
|
2020-08-11 17:43:01 +02:00
|
|
|
lockErr := &statemgr.LockError{
|
2017-08-01 18:11:04 +02:00
|
|
|
Info: lockInfo,
|
|
|
|
}
|
|
|
|
|
|
|
|
lockErr.Err = errors.New("state locked")
|
|
|
|
// make a copy of the lock info to avoid any testing shenanigans
|
|
|
|
*lockErr.Info = *lockInfo
|
|
|
|
return "", lockErr
|
|
|
|
}
|
|
|
|
|
|
|
|
info.Created = time.Now().UTC()
|
|
|
|
l.m[name] = info
|
|
|
|
|
|
|
|
return info.ID, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (l *lockMap) unlock(name, id string) error {
|
|
|
|
l.Lock()
|
|
|
|
defer l.Unlock()
|
|
|
|
|
|
|
|
lockInfo := l.m[name]
|
|
|
|
|
|
|
|
if lockInfo == nil {
|
|
|
|
return errors.New("state not locked")
|
|
|
|
}
|
|
|
|
|
2020-08-11 17:43:01 +02:00
|
|
|
lockErr := &statemgr.LockError{
|
|
|
|
Info: &statemgr.LockInfo{},
|
2017-08-01 18:11:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if id != lockInfo.ID {
|
|
|
|
lockErr.Err = errors.New("invalid lock id")
|
|
|
|
*lockErr.Info = *lockInfo
|
|
|
|
return lockErr
|
|
|
|
}
|
|
|
|
|
|
|
|
delete(l.m, name)
|
|
|
|
return nil
|
2017-02-18 02:26:38 +01:00
|
|
|
}
|