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 }