github.com/vic3lord/terraform@v0.8.0-rc1.0.20170626102919-16c6dd2cb372/backend/remote-state/s3/backend_state.go (about) 1 package s3 2 3 import ( 4 "errors" 5 "fmt" 6 "sort" 7 "strings" 8 9 "github.com/aws/aws-sdk-go/aws" 10 "github.com/aws/aws-sdk-go/service/s3" 11 "github.com/hashicorp/terraform/backend" 12 "github.com/hashicorp/terraform/state" 13 "github.com/hashicorp/terraform/state/remote" 14 "github.com/hashicorp/terraform/terraform" 15 ) 16 17 const ( 18 // This will be used as directory name, the odd looking colon is simply to 19 // reduce the chance of name conflicts with existing objects. 20 keyEnvPrefix = "env:" 21 ) 22 23 func (b *Backend) States() ([]string, error) { 24 params := &s3.ListObjectsInput{ 25 Bucket: &b.bucketName, 26 Prefix: aws.String(keyEnvPrefix + "/"), 27 } 28 29 resp, err := b.s3Client.ListObjects(params) 30 if err != nil { 31 return nil, err 32 } 33 34 envs := []string{backend.DefaultStateName} 35 for _, obj := range resp.Contents { 36 env := b.keyEnv(*obj.Key) 37 if env != "" { 38 envs = append(envs, env) 39 } 40 } 41 42 sort.Strings(envs[1:]) 43 return envs, nil 44 } 45 46 // extract the env name from the S3 key 47 func (b *Backend) keyEnv(key string) string { 48 // we have 3 parts, the prefix, the env name, and the key name 49 parts := strings.SplitN(key, "/", 3) 50 if len(parts) < 3 { 51 // no env here 52 return "" 53 } 54 55 // shouldn't happen since we listed by prefix 56 if parts[0] != keyEnvPrefix { 57 return "" 58 } 59 60 // not our key, so don't include it in our listing 61 if parts[2] != b.keyName { 62 return "" 63 } 64 65 return parts[1] 66 } 67 68 func (b *Backend) DeleteState(name string) error { 69 if name == backend.DefaultStateName || name == "" { 70 return fmt.Errorf("can't delete default state") 71 } 72 73 client, err := b.remoteClient(name) 74 if err != nil { 75 return err 76 } 77 78 return client.Delete() 79 } 80 81 // get a remote client configured for this state 82 func (b *Backend) remoteClient(name string) (*RemoteClient, error) { 83 if name == "" { 84 return nil, errors.New("missing state name") 85 } 86 87 client := &RemoteClient{ 88 s3Client: b.s3Client, 89 dynClient: b.dynClient, 90 bucketName: b.bucketName, 91 path: b.path(name), 92 serverSideEncryption: b.serverSideEncryption, 93 acl: b.acl, 94 kmsKeyID: b.kmsKeyID, 95 ddbTable: b.ddbTable, 96 } 97 98 return client, nil 99 } 100 101 func (b *Backend) State(name string) (state.State, error) { 102 client, err := b.remoteClient(name) 103 if err != nil { 104 return nil, err 105 } 106 107 stateMgr := &remote.State{Client: client} 108 // Check to see if this state already exists. 109 // If we're trying to force-unlock a state, we can't take the lock before 110 // fetching the state. If the state doesn't exist, we have to assume this 111 // is a normal create operation, and take the lock at that point. 112 // 113 // If we need to force-unlock, but for some reason the state no longer 114 // exists, the user will have to use aws tools to manually fix the 115 // situation. 116 existing, err := b.States() 117 if err != nil { 118 return nil, err 119 } 120 121 exists := false 122 for _, s := range existing { 123 if s == name { 124 exists = true 125 break 126 } 127 } 128 129 // We need to create the object so it's listed by States. 130 if !exists { 131 // take a lock on this state while we write it 132 lockInfo := state.NewLockInfo() 133 lockInfo.Operation = "init" 134 lockId, err := client.Lock(lockInfo) 135 if err != nil { 136 return nil, fmt.Errorf("failed to lock s3 state: %s", err) 137 } 138 139 // Local helper function so we can call it multiple places 140 lockUnlock := func(parent error) error { 141 if err := stateMgr.Unlock(lockId); err != nil { 142 return fmt.Errorf(strings.TrimSpace(errStateUnlock), lockId, err) 143 } 144 return parent 145 } 146 147 // Grab the value 148 // This is to ensure that no one beat us to writing a state between 149 // the `exists` check and taking the lock. 150 if err := stateMgr.RefreshState(); err != nil { 151 err = lockUnlock(err) 152 return nil, err 153 } 154 155 // If we have no state, we have to create an empty state 156 if v := stateMgr.State(); v == nil { 157 if err := stateMgr.WriteState(terraform.NewState()); err != nil { 158 err = lockUnlock(err) 159 return nil, err 160 } 161 if err := stateMgr.PersistState(); err != nil { 162 err = lockUnlock(err) 163 return nil, err 164 } 165 } 166 167 // Unlock, the state should now be initialized 168 if err := lockUnlock(nil); err != nil { 169 return nil, err 170 } 171 172 } 173 174 return stateMgr, nil 175 } 176 177 func (b *Backend) client() *RemoteClient { 178 return &RemoteClient{} 179 } 180 181 func (b *Backend) path(name string) string { 182 if name == backend.DefaultStateName { 183 return b.keyName 184 } 185 186 return strings.Join([]string{keyEnvPrefix, name, b.keyName}, "/") 187 } 188 189 const errStateUnlock = ` 190 Error unlocking S3 state. Lock ID: %s 191 192 Error: %s 193 194 You may have to force-unlock this state in order to use it again. 195 `