github.com/paybyphone/terraform@v0.9.5-0.20170613192930-9706042ddd51/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 lockState: b.lock, 98 }, 99 } 100 101 // If we're not locking, disable it 102 if !b.lock { 103 stateMgr = &state.LockDisabled{Inner: stateMgr} 104 } 105 106 // Grab a lock, we use this to write an empty state if one doesn't 107 // exist already. We have to write an empty state as a sentinel value 108 // so States() knows it exists. 109 lockInfo := state.NewLockInfo() 110 lockInfo.Operation = "init" 111 lockId, err := stateMgr.Lock(lockInfo) 112 if err != nil { 113 return nil, fmt.Errorf("failed to lock state in Consul: %s", err) 114 } 115 116 // Local helper function so we can call it multiple places 117 lockUnlock := func(parent error) error { 118 if err := stateMgr.Unlock(lockId); err != nil { 119 return fmt.Errorf(strings.TrimSpace(errStateUnlock), lockId, err) 120 } 121 122 return parent 123 } 124 125 // Grab the value 126 if err := stateMgr.RefreshState(); err != nil { 127 err = lockUnlock(err) 128 return nil, err 129 } 130 131 // If we have no state, we have to create an empty state 132 if v := stateMgr.State(); v == nil { 133 if err := stateMgr.WriteState(terraform.NewState()); err != nil { 134 err = lockUnlock(err) 135 return nil, err 136 } 137 if err := stateMgr.PersistState(); err != nil { 138 err = lockUnlock(err) 139 return nil, err 140 } 141 } 142 143 // Unlock, the state should now be initialized 144 if err := lockUnlock(nil); err != nil { 145 return nil, err 146 } 147 148 return stateMgr, nil 149 } 150 151 func (b *Backend) path(name string) string { 152 path := b.configData.Get("path").(string) 153 if name != backend.DefaultStateName { 154 path += fmt.Sprintf("%s%s", keyEnvPrefix, name) 155 } 156 157 return path 158 } 159 160 const errStateUnlock = ` 161 Error unlocking Consul state. Lock ID: %s 162 163 Error: %s 164 165 You may have to force-unlock this state in order to use it again. 166 The Consul backend acquires a lock during initialization to ensure 167 the minimum required key/values are prepared. 168 `