github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/backend/remote-state/swift/backend_state.go (about) 1 package swift 2 3 import ( 4 "fmt" 5 "strings" 6 7 "github.com/hashicorp/terraform/backend" 8 "github.com/hashicorp/terraform/state" 9 "github.com/hashicorp/terraform/state/remote" 10 "github.com/hashicorp/terraform/states" 11 ) 12 13 const ( 14 objectEnvPrefix = "env-" 15 delimiter = "/" 16 ) 17 18 func (b *Backend) Workspaces() ([]string, error) { 19 client := &RemoteClient{ 20 client: b.client, 21 container: b.container, 22 archive: b.archive, 23 archiveContainer: b.archiveContainer, 24 expireSecs: b.expireSecs, 25 lockState: b.lock, 26 } 27 28 // List our container objects 29 objectNames, err := client.ListObjectsNames(objectEnvPrefix, delimiter) 30 31 if err != nil { 32 return nil, err 33 } 34 35 // Find the envs, we use a map since we can get duplicates with 36 // path suffixes. 37 envs := map[string]struct{}{} 38 for _, object := range objectNames { 39 object = strings.TrimPrefix(object, objectEnvPrefix) 40 object = strings.TrimSuffix(object, delimiter) 41 42 // Ignore objects that still contain a "/" 43 // as we dont store states in subdirectories 44 if idx := strings.Index(object, delimiter); idx >= 0 { 45 continue 46 } 47 48 // swift is eventually consistent, thus a deleted object may 49 // be listed in objectList. To ensure consistency, we query 50 // each object with a "newest" arg set to true 51 payload, err := client.get(b.objectName(object)) 52 if err != nil { 53 return nil, err 54 } 55 if payload == nil { 56 // object doesn't exist anymore. skipping. 57 continue 58 } 59 60 envs[object] = struct{}{} 61 } 62 63 result := make([]string, 1, len(envs)+1) 64 result[0] = backend.DefaultStateName 65 66 for k, _ := range envs { 67 result = append(result, k) 68 } 69 70 return result, nil 71 } 72 73 func (b *Backend) DeleteWorkspace(name string) error { 74 if name == backend.DefaultStateName || name == "" { 75 return fmt.Errorf("can't delete default state") 76 } 77 78 client := &RemoteClient{ 79 client: b.client, 80 container: b.container, 81 archive: b.archive, 82 archiveContainer: b.archiveContainer, 83 expireSecs: b.expireSecs, 84 objectName: b.objectName(name), 85 lockState: b.lock, 86 } 87 88 // Delete our object 89 err := client.Delete() 90 91 return err 92 } 93 94 func (b *Backend) StateMgr(name string) (state.State, error) { 95 return b.stateMgr(name, true) 96 } 97 98 func (b *Backend) StateMgrWithoutCheckVersion(name string) (state.State, error) { 99 return b.stateMgr(name, false) 100 } 101 102 func (b *Backend) stateMgr(name string, checkVersion bool) (state.State, error) { 103 if name == "" { 104 return nil, fmt.Errorf("missing state name") 105 } 106 107 client := &RemoteClient{ 108 client: b.client, 109 container: b.container, 110 archive: b.archive, 111 archiveContainer: b.archiveContainer, 112 expireSecs: b.expireSecs, 113 objectName: b.objectName(name), 114 lockState: b.lock, 115 } 116 117 var stateMgr state.State = &remote.State{Client: client} 118 119 // If we're not locking, disable it 120 if !b.lock { 121 stateMgr = &state.LockDisabled{Inner: stateMgr} 122 } 123 124 // Check to see if this state already exists. 125 // If we're trying to force-unlock a state, we can't take the lock before 126 // fetching the state. If the state doesn't exist, we have to assume this 127 // is a normal create operation, and take the lock at that point. 128 // 129 // If we need to force-unlock, but for some reason the state no longer 130 // exists, the user will have to use openstack tools to manually fix the 131 // situation. 132 existing, err := b.Workspaces() 133 if err != nil { 134 return nil, err 135 } 136 137 exists := false 138 for _, s := range existing { 139 if s == name { 140 exists = true 141 break 142 } 143 } 144 145 // We need to create the object so it's listed by States. 146 if !exists { 147 // the default state always exists 148 if name == backend.DefaultStateName { 149 return stateMgr, nil 150 } 151 152 // Grab a lock, we use this to write an empty state if one doesn't 153 // exist already. We have to write an empty state as a sentinel value 154 // so States() knows it exists. 155 lockInfo := state.NewLockInfo() 156 lockInfo.Operation = "init" 157 lockId, err := stateMgr.Lock(lockInfo) 158 if err != nil { 159 return nil, fmt.Errorf("failed to lock state in Swift: %s", err) 160 } 161 162 // Local helper function so we can call it multiple places 163 lockUnlock := func(parent error) error { 164 if err := stateMgr.Unlock(lockId); err != nil { 165 return fmt.Errorf(strings.TrimSpace(errStateUnlock), lockId, err) 166 } 167 168 return parent 169 } 170 171 // Grab the value 172 if checkVersion { 173 if err := stateMgr.RefreshState(); err != nil { 174 err = lockUnlock(err) 175 return nil, err 176 } 177 } else { 178 if err := stateMgr.RefreshStateWithoutCheckVersion(); err != nil { 179 err = lockUnlock(err) 180 return nil, err 181 } 182 } 183 184 // If we have no state, we have to create an empty state 185 if v := stateMgr.State(); v == nil { 186 if err := stateMgr.WriteState(states.NewState()); err != nil { 187 err = lockUnlock(err) 188 return nil, err 189 } 190 if err := stateMgr.PersistState(); err != nil { 191 err = lockUnlock(err) 192 return nil, err 193 } 194 } 195 196 // Unlock, the state should now be initialized 197 if err := lockUnlock(nil); err != nil { 198 return nil, err 199 } 200 } 201 202 return stateMgr, nil 203 } 204 205 func (b *Backend) objectName(name string) string { 206 if name != backend.DefaultStateName { 207 name = fmt.Sprintf("%s%s/%s", objectEnvPrefix, name, b.stateName) 208 } else { 209 name = b.stateName 210 } 211 212 return name 213 } 214 215 const errStateUnlock = ` 216 Error unlocking Swift state. Lock ID: %s 217 218 Error: %s 219 220 You may have to force-unlock this state in order to use it again. 221 The Swift backend acquires a lock during initialization to ensure 222 the minimum required keys are prepared. 223 `