github.com/dougneal/terraform@v0.6.15-0.20170330092735-b6a3840768a4/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/terraform" 11 ) 12 13 const ( 14 keyEnvPrefix = "-env:" 15 ) 16 17 func (b *Backend) States() ([]string, error) { 18 // Get the Consul client 19 client, err := b.clientRaw() 20 if err != nil { 21 return nil, err 22 } 23 24 // List our raw path 25 prefix := b.configData.Get("path").(string) + keyEnvPrefix 26 keys, _, err := client.KV().Keys(prefix, "/", nil) 27 if err != nil { 28 return nil, err 29 } 30 31 // Find the envs, we use a map since we can get duplicates with 32 // path suffixes. 33 envs := map[string]struct{}{} 34 for _, key := range keys { 35 // Consul should ensure this but it doesn't hurt to check again 36 if strings.HasPrefix(key, prefix) { 37 key = strings.TrimPrefix(key, prefix) 38 39 // Ignore anything with a "/" in it since we store the state 40 // directly in a key not a directory. 41 if idx := strings.IndexRune(key, '/'); idx >= 0 { 42 continue 43 } 44 45 envs[key] = struct{}{} 46 } 47 } 48 49 result := make([]string, 1, len(envs)+1) 50 result[0] = backend.DefaultStateName 51 for k, _ := range envs { 52 result = append(result, k) 53 } 54 55 return result, nil 56 } 57 58 func (b *Backend) DeleteState(name string) error { 59 if name == backend.DefaultStateName || name == "" { 60 return fmt.Errorf("can't delete default state") 61 } 62 63 // Get the Consul API client 64 client, err := b.clientRaw() 65 if err != nil { 66 return err 67 } 68 69 // Determine the path of the data 70 path := b.path(name) 71 72 // Delete it. We just delete it without any locking since 73 // the DeleteState API is documented as such. 74 _, err = client.KV().Delete(path, nil) 75 return err 76 } 77 78 func (b *Backend) State(name string) (state.State, error) { 79 // Get the Consul API client 80 client, err := b.clientRaw() 81 if err != nil { 82 return nil, err 83 } 84 85 // Determine the path of the data 86 path := b.path(name) 87 88 // Determine whether to gzip or not 89 gzip := b.configData.Get("gzip").(bool) 90 91 // Build the state client 92 var stateMgr state.State = &remote.State{ 93 Client: &RemoteClient{ 94 Client: client, 95 Path: path, 96 GZip: gzip, 97 }, 98 } 99 100 // If we're not locking, disable it 101 if !b.lock { 102 stateMgr = &state.LockDisabled{Inner: stateMgr} 103 } 104 105 // Get the locker, which we know always exists 106 stateMgrLocker := stateMgr.(state.Locker) 107 108 // Grab a lock, we use this to write an empty state if one doesn't 109 // exist already. We have to write an empty state as a sentinel value 110 // so States() knows it exists. 111 lockInfo := state.NewLockInfo() 112 lockInfo.Operation = "init" 113 lockId, err := stateMgrLocker.Lock(lockInfo) 114 if err != nil { 115 return nil, fmt.Errorf("failed to lock state in Consul: %s", err) 116 } 117 118 // Local helper function so we can call it multiple places 119 lockUnlock := func(parent error) error { 120 if err := stateMgrLocker.Unlock(lockId); err != nil { 121 return fmt.Errorf(strings.TrimSpace(errStateUnlock), lockId, err) 122 } 123 124 return parent 125 } 126 127 // Grab the value 128 if err := stateMgr.RefreshState(); err != nil { 129 err = lockUnlock(err) 130 return nil, err 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(terraform.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 `