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