github.com/hugorut/terraform@v1.1.3/src/backend/remote-state/swift/backend_state.go (about) 1 package swift 2 3 import ( 4 "fmt" 5 "strings" 6 7 "github.com/hugorut/terraform/src/backend" 8 "github.com/hugorut/terraform/src/states" 9 "github.com/hugorut/terraform/src/states/remote" 10 "github.com/hugorut/terraform/src/states/statemgr" 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) (statemgr.Full, error) { 95 if name == "" { 96 return nil, fmt.Errorf("missing state name") 97 } 98 99 client := &RemoteClient{ 100 client: b.client, 101 container: b.container, 102 archive: b.archive, 103 archiveContainer: b.archiveContainer, 104 expireSecs: b.expireSecs, 105 objectName: b.objectName(name), 106 lockState: b.lock, 107 } 108 109 var stateMgr statemgr.Full = &remote.State{Client: client} 110 111 // If we're not locking, disable it 112 if !b.lock { 113 stateMgr = &statemgr.LockDisabled{Inner: stateMgr} 114 } 115 116 // Check to see if this state already exists. 117 // If we're trying to force-unlock a state, we can't take the lock before 118 // fetching the state. If the state doesn't exist, we have to assume this 119 // is a normal create operation, and take the lock at that point. 120 // 121 // If we need to force-unlock, but for some reason the state no longer 122 // exists, the user will have to use openstack tools to manually fix the 123 // situation. 124 existing, err := b.Workspaces() 125 if err != nil { 126 return nil, err 127 } 128 129 exists := false 130 for _, s := range existing { 131 if s == name { 132 exists = true 133 break 134 } 135 } 136 137 // We need to create the object so it's listed by States. 138 if !exists { 139 // the default state always exists 140 if name == backend.DefaultStateName { 141 return stateMgr, nil 142 } 143 144 // Grab a lock, we use this to write an empty state if one doesn't 145 // exist already. We have to write an empty state as a sentinel value 146 // so States() knows it exists. 147 lockInfo := statemgr.NewLockInfo() 148 lockInfo.Operation = "init" 149 lockId, err := stateMgr.Lock(lockInfo) 150 if err != nil { 151 return nil, fmt.Errorf("failed to lock state in Swift: %s", err) 152 } 153 154 // Local helper function so we can call it multiple places 155 lockUnlock := func(parent error) error { 156 if err := stateMgr.Unlock(lockId); err != nil { 157 return fmt.Errorf(strings.TrimSpace(errStateUnlock), lockId, err) 158 } 159 160 return parent 161 } 162 163 // Grab the value 164 if err := stateMgr.RefreshState(); err != nil { 165 err = lockUnlock(err) 166 return nil, err 167 } 168 169 // If we have no state, we have to create an empty state 170 if v := stateMgr.State(); v == nil { 171 if err := stateMgr.WriteState(states.NewState()); err != nil { 172 err = lockUnlock(err) 173 return nil, err 174 } 175 if err := stateMgr.PersistState(); err != nil { 176 err = lockUnlock(err) 177 return nil, err 178 } 179 } 180 181 // Unlock, the state should now be initialized 182 if err := lockUnlock(nil); err != nil { 183 return nil, err 184 } 185 } 186 187 return stateMgr, nil 188 } 189 190 func (b *Backend) objectName(name string) string { 191 if name != backend.DefaultStateName { 192 name = fmt.Sprintf("%s%s/%s", objectEnvPrefix, name, b.stateName) 193 } else { 194 name = b.stateName 195 } 196 197 return name 198 } 199 200 const errStateUnlock = ` 201 Error unlocking Swift state. Lock ID: %s 202 203 Error: %s 204 205 You may have to force-unlock this state in order to use it again. 206 The Swift backend acquires a lock during initialization to ensure 207 the minimum required keys are prepared. 208 `