github.com/opentofu/opentofu@v1.7.1/internal/backend/remote-state/inmem/backend.go (about)

     1  // Copyright (c) The OpenTofu Authors
     2  // SPDX-License-Identifier: MPL-2.0
     3  // Copyright (c) 2023 HashiCorp, Inc.
     4  // SPDX-License-Identifier: MPL-2.0
     5  
     6  package inmem
     7  
     8  import (
     9  	"context"
    10  	"errors"
    11  	"fmt"
    12  	"sort"
    13  	"sync"
    14  	"time"
    15  
    16  	"github.com/opentofu/opentofu/internal/backend"
    17  	"github.com/opentofu/opentofu/internal/encryption"
    18  	"github.com/opentofu/opentofu/internal/legacy/helper/schema"
    19  	statespkg "github.com/opentofu/opentofu/internal/states"
    20  	"github.com/opentofu/opentofu/internal/states/remote"
    21  	"github.com/opentofu/opentofu/internal/states/statemgr"
    22  )
    23  
    24  // we keep the states and locks in package-level variables, so that they can be
    25  // accessed from multiple instances of the backend. This better emulates
    26  // backend instances accessing a single remote data store.
    27  var (
    28  	states stateMap
    29  	locks  lockMap
    30  )
    31  
    32  func init() {
    33  	Reset()
    34  }
    35  
    36  // Reset clears out all existing state and lock data.
    37  // This is used to initialize the package during init, as well as between
    38  // tests.
    39  func Reset() {
    40  	states = stateMap{
    41  		m: map[string]*remote.State{},
    42  	}
    43  
    44  	locks = lockMap{
    45  		m: map[string]*statemgr.LockInfo{},
    46  	}
    47  }
    48  
    49  // New creates a new backend for Inmem remote state.
    50  func New(enc encryption.StateEncryption) backend.Backend {
    51  	// Set the schema
    52  	s := &schema.Backend{
    53  		Schema: map[string]*schema.Schema{
    54  			"lock_id": &schema.Schema{
    55  				Type:        schema.TypeString,
    56  				Optional:    true,
    57  				Description: "initializes the state in a locked configuration",
    58  			},
    59  		},
    60  	}
    61  	backend := &Backend{Backend: s, encryption: enc}
    62  	backend.Backend.ConfigureFunc = backend.configure
    63  	return backend
    64  }
    65  
    66  type Backend struct {
    67  	*schema.Backend
    68  	encryption encryption.StateEncryption
    69  }
    70  
    71  func (b *Backend) configure(ctx context.Context) error {
    72  	states.Lock()
    73  	defer states.Unlock()
    74  
    75  	defaultClient := &RemoteClient{
    76  		Name: backend.DefaultStateName,
    77  	}
    78  
    79  	states.m[backend.DefaultStateName] = remote.NewState(defaultClient, b.encryption)
    80  
    81  	// set the default client lock info per the test config
    82  	data := schema.FromContextBackendConfig(ctx)
    83  	if v, ok := data.GetOk("lock_id"); ok && v.(string) != "" {
    84  		info := statemgr.NewLockInfo()
    85  		info.ID = v.(string)
    86  		info.Operation = "test"
    87  		info.Info = "test config"
    88  
    89  		locks.lock(backend.DefaultStateName, info)
    90  	}
    91  
    92  	return nil
    93  }
    94  
    95  func (b *Backend) Workspaces() ([]string, error) {
    96  	states.Lock()
    97  	defer states.Unlock()
    98  
    99  	var workspaces []string
   100  
   101  	for s := range states.m {
   102  		workspaces = append(workspaces, s)
   103  	}
   104  
   105  	sort.Strings(workspaces)
   106  	return workspaces, nil
   107  }
   108  
   109  func (b *Backend) DeleteWorkspace(name string, _ bool) error {
   110  	states.Lock()
   111  	defer states.Unlock()
   112  
   113  	if name == backend.DefaultStateName || name == "" {
   114  		return fmt.Errorf("can't delete default state")
   115  	}
   116  
   117  	delete(states.m, name)
   118  	return nil
   119  }
   120  
   121  func (b *Backend) StateMgr(name string) (statemgr.Full, error) {
   122  	states.Lock()
   123  	defer states.Unlock()
   124  
   125  	s := states.m[name]
   126  	if s == nil {
   127  		s = remote.NewState(
   128  			&RemoteClient{
   129  				Name: name,
   130  			},
   131  			b.encryption,
   132  		)
   133  		states.m[name] = s
   134  
   135  		// to most closely replicate other implementations, we are going to
   136  		// take a lock and create a new state if it doesn't exist.
   137  		lockInfo := statemgr.NewLockInfo()
   138  		lockInfo.Operation = "init"
   139  		lockID, err := s.Lock(lockInfo)
   140  		if err != nil {
   141  			return nil, fmt.Errorf("failed to lock inmem state: %w", err)
   142  		}
   143  		defer s.Unlock(lockID)
   144  
   145  		// If we have no state, we have to create an empty state
   146  		if v := s.State(); v == nil {
   147  			if err := s.WriteState(statespkg.NewState()); err != nil {
   148  				return nil, err
   149  			}
   150  			if err := s.PersistState(nil); err != nil {
   151  				return nil, err
   152  			}
   153  		}
   154  	}
   155  
   156  	return s, nil
   157  }
   158  
   159  type stateMap struct {
   160  	sync.Mutex
   161  	m map[string]*remote.State
   162  }
   163  
   164  // Global level locks for inmem backends.
   165  type lockMap struct {
   166  	sync.Mutex
   167  	m map[string]*statemgr.LockInfo
   168  }
   169  
   170  func (l *lockMap) lock(name string, info *statemgr.LockInfo) (string, error) {
   171  	l.Lock()
   172  	defer l.Unlock()
   173  
   174  	lockInfo := l.m[name]
   175  	if lockInfo != nil {
   176  		lockErr := &statemgr.LockError{
   177  			Info: lockInfo,
   178  		}
   179  
   180  		lockErr.Err = errors.New("state locked")
   181  		// make a copy of the lock info to avoid any testing shenanigans
   182  		*lockErr.Info = *lockInfo
   183  		return "", lockErr
   184  	}
   185  
   186  	info.Created = time.Now().UTC()
   187  	l.m[name] = info
   188  
   189  	return info.ID, nil
   190  }
   191  
   192  func (l *lockMap) unlock(name, id string) error {
   193  	l.Lock()
   194  	defer l.Unlock()
   195  
   196  	lockInfo := l.m[name]
   197  
   198  	if lockInfo == nil {
   199  		return errors.New("state not locked")
   200  	}
   201  
   202  	lockErr := &statemgr.LockError{
   203  		Info: &statemgr.LockInfo{},
   204  	}
   205  
   206  	if id != lockInfo.ID {
   207  		lockErr.Err = errors.New("invalid lock id")
   208  		*lockErr.Info = *lockInfo
   209  		return lockErr
   210  	}
   211  
   212  	delete(l.m, name)
   213  	return nil
   214  }