mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
This is part of a general effort to move all of Terraform's non-library package surface under internal in order to reinforce that these are for internal use within Terraform only. If you were previously importing packages under this prefix into an external codebase, you could pin to an earlier release tag as an interim solution until you've make a plan to achieve the same functionality some other way.
209 lines
4.2 KiB
Go
209 lines
4.2 KiB
Go
package inmem
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"sort"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/hashicorp/terraform/internal/backend"
|
|
"github.com/hashicorp/terraform/internal/legacy/helper/schema"
|
|
statespkg "github.com/hashicorp/terraform/internal/states"
|
|
"github.com/hashicorp/terraform/internal/states/remote"
|
|
"github.com/hashicorp/terraform/internal/states/statemgr"
|
|
)
|
|
|
|
// 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.
|
|
var (
|
|
states stateMap
|
|
locks lockMap
|
|
)
|
|
|
|
func init() {
|
|
Reset()
|
|
}
|
|
|
|
// 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{
|
|
m: map[string]*statemgr.LockInfo{},
|
|
}
|
|
}
|
|
|
|
// New creates a new backend for Inmem remote state.
|
|
func New() backend.Backend {
|
|
// 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",
|
|
},
|
|
},
|
|
}
|
|
backend := &Backend{Backend: s}
|
|
backend.Backend.ConfigureFunc = backend.configure
|
|
return backend
|
|
}
|
|
|
|
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
|
|
data := schema.FromContextBackendConfig(ctx)
|
|
if v, ok := data.GetOk("lock_id"); ok && v.(string) != "" {
|
|
info := statemgr.NewLockInfo()
|
|
info.ID = v.(string)
|
|
info.Operation = "test"
|
|
info.Info = "test config"
|
|
|
|
locks.lock(backend.DefaultStateName, info)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (b *Backend) Workspaces() ([]string, error) {
|
|
states.Lock()
|
|
defer states.Unlock()
|
|
|
|
var workspaces []string
|
|
|
|
for s := range states.m {
|
|
workspaces = append(workspaces, s)
|
|
}
|
|
|
|
sort.Strings(workspaces)
|
|
return workspaces, nil
|
|
}
|
|
|
|
func (b *Backend) DeleteWorkspace(name string) error {
|
|
states.Lock()
|
|
defer states.Unlock()
|
|
|
|
if name == backend.DefaultStateName || name == "" {
|
|
return fmt.Errorf("can't delete default state")
|
|
}
|
|
|
|
delete(states.m, name)
|
|
return nil
|
|
}
|
|
|
|
func (b *Backend) StateMgr(name string) (statemgr.Full, error) {
|
|
states.Lock()
|
|
defer states.Unlock()
|
|
|
|
s := states.m[name]
|
|
if s == nil {
|
|
s = &remote.State{
|
|
Client: &RemoteClient{
|
|
Name: name,
|
|
},
|
|
}
|
|
states.m[name] = s
|
|
|
|
// to most closely replicate other implementations, we are going to
|
|
// take a lock and create a new state if it doesn't exist.
|
|
lockInfo := statemgr.NewLockInfo()
|
|
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 {
|
|
if err := s.WriteState(statespkg.NewState()); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := s.PersistState(); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
}
|
|
|
|
return s, nil
|
|
}
|
|
|
|
type stateMap struct {
|
|
sync.Mutex
|
|
m map[string]*remote.State
|
|
}
|
|
|
|
// Global level locks for inmem backends.
|
|
type lockMap struct {
|
|
sync.Mutex
|
|
m map[string]*statemgr.LockInfo
|
|
}
|
|
|
|
func (l *lockMap) lock(name string, info *statemgr.LockInfo) (string, error) {
|
|
l.Lock()
|
|
defer l.Unlock()
|
|
|
|
lockInfo := l.m[name]
|
|
if lockInfo != nil {
|
|
lockErr := &statemgr.LockError{
|
|
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")
|
|
}
|
|
|
|
lockErr := &statemgr.LockError{
|
|
Info: &statemgr.LockInfo{},
|
|
}
|
|
|
|
if id != lockInfo.ID {
|
|
lockErr.Err = errors.New("invalid lock id")
|
|
*lockErr.Info = *lockInfo
|
|
return lockErr
|
|
}
|
|
|
|
delete(l.m, name)
|
|
return nil
|
|
}
|