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