github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/backend/remote-state/consul/backend_state.go (about) 1 package consul 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 "github.com/hashicorp/terraform/states/statemgr" 12 ) 13 14 const ( 15 keyEnvPrefix = "-env:" 16 ) 17 18 func (b *Backend) Workspaces() ([]string, error) { 19 // List our raw path 20 prefix := b.configData.Get("path").(string) + keyEnvPrefix 21 keys, _, err := b.client.KV().Keys(prefix, "/", nil) 22 if err != nil { 23 return nil, err 24 } 25 26 // Find the envs, we use a map since we can get duplicates with 27 // path suffixes. 28 envs := map[string]struct{}{} 29 for _, key := range keys { 30 // Consul should ensure this but it doesn't hurt to check again 31 if strings.HasPrefix(key, prefix) { 32 key = strings.TrimPrefix(key, prefix) 33 34 // Ignore anything with a "/" in it since we store the state 35 // directly in a key not a directory. 36 if idx := strings.IndexRune(key, '/'); idx >= 0 { 37 continue 38 } 39 40 envs[key] = struct{}{} 41 } 42 } 43 44 result := make([]string, 1, len(envs)+1) 45 result[0] = backend.DefaultStateName 46 for k, _ := range envs { 47 result = append(result, k) 48 } 49 50 return result, nil 51 } 52 53 func (b *Backend) DeleteWorkspace(name string) error { 54 if name == backend.DefaultStateName || name == "" { 55 return fmt.Errorf("can't delete default state") 56 } 57 58 // Determine the path of the data 59 path := b.path(name) 60 61 // Delete it. We just delete it without any locking since 62 // the DeleteState API is documented as such. 63 _, err := b.client.KV().Delete(path, nil) 64 return err 65 } 66 67 func (b *Backend) StateMgr(name string) (statemgr.Full, error) { 68 return b.stateMgr(name, true) 69 } 70 71 func (b *Backend) StateMgrWithoutCheckVersion(name string) (statemgr.Full, error) { 72 return b.stateMgr(name, false) 73 } 74 75 func (b *Backend) stateMgr(name string, checkVersion bool) (statemgr.Full, error) { 76 // Determine the path of the data 77 path := b.path(name) 78 79 // Determine whether to gzip or not 80 gzip := b.configData.Get("gzip").(bool) 81 82 // Build the state client 83 var stateMgr = &remote.State{ 84 Client: &RemoteClient{ 85 Client: b.client, 86 Path: path, 87 GZip: gzip, 88 lockState: b.lock, 89 }, 90 } 91 92 if !b.lock { 93 stateMgr.DisableLocks() 94 } 95 96 // the default state always exists 97 if name == backend.DefaultStateName { 98 return stateMgr, nil 99 } 100 101 // Grab a lock, we use this to write an empty state if one doesn't 102 // exist already. We have to write an empty state as a sentinel value 103 // so States() knows it exists. 104 lockInfo := state.NewLockInfo() 105 lockInfo.Operation = "init" 106 lockId, err := stateMgr.Lock(lockInfo) 107 if err != nil { 108 return nil, fmt.Errorf("failed to lock state in Consul: %s", err) 109 } 110 111 // Local helper function so we can call it multiple places 112 lockUnlock := func(parent error) error { 113 if err := stateMgr.Unlock(lockId); err != nil { 114 return fmt.Errorf(strings.TrimSpace(errStateUnlock), lockId, err) 115 } 116 117 return parent 118 } 119 120 // Grab the value 121 if checkVersion { 122 if err := stateMgr.RefreshState(); err != nil { 123 err = lockUnlock(err) 124 return nil, err 125 } 126 } else { 127 if err := stateMgr.RefreshStateWithoutCheckVersion(); err != nil { 128 err = lockUnlock(err) 129 return nil, err 130 } 131 } 132 133 // If we have no state, we have to create an empty state 134 if v := stateMgr.State(); v == nil { 135 if err := stateMgr.WriteState(states.NewState()); err != nil { 136 err = lockUnlock(err) 137 return nil, err 138 } 139 if err := stateMgr.PersistState(); err != nil { 140 err = lockUnlock(err) 141 return nil, err 142 } 143 } 144 145 // Unlock, the state should now be initialized 146 if err := lockUnlock(nil); err != nil { 147 return nil, err 148 } 149 150 return stateMgr, nil 151 } 152 153 func (b *Backend) path(name string) string { 154 path := b.configData.Get("path").(string) 155 if name != backend.DefaultStateName { 156 path += fmt.Sprintf("%s%s", keyEnvPrefix, name) 157 } 158 159 return path 160 } 161 162 const errStateUnlock = ` 163 Error unlocking Consul state. Lock ID: %s 164 165 Error: %s 166 167 You may have to force-unlock this state in order to use it again. 168 The Consul backend acquires a lock during initialization to ensure 169 the minimum required key/values are prepared. 170 `