github.com/bradfeehan/terraform@v0.7.0-rc3.0.20170529055808-34b45c5ad841/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 params := &s3.DeleteObjectInput{ 74 Bucket: &b.bucketName, 75 Key: aws.String(b.path(name)), 76 } 77 78 _, err := b.s3Client.DeleteObject(params) 79 if err != nil { 80 return err 81 } 82 83 return nil 84 } 85 86 func (b *Backend) State(name string) (state.State, error) { 87 if name == "" { 88 return nil, errors.New("missing state name") 89 } 90 91 client := &RemoteClient{ 92 s3Client: b.s3Client, 93 dynClient: b.dynClient, 94 bucketName: b.bucketName, 95 path: b.path(name), 96 serverSideEncryption: b.serverSideEncryption, 97 acl: b.acl, 98 kmsKeyID: b.kmsKeyID, 99 lockTable: b.lockTable, 100 } 101 102 stateMgr := &remote.State{Client: client} 103 104 // Check to see if this state already exists. 105 // If we're trying to force-unlock a state, we can't take the lock before 106 // fetching the state. If the state doesn't exist, we have to assume this 107 // is a normal create operation, and take the lock at that point. 108 // 109 // If we need to force-unlock, but for some reason the state no longer 110 // exists, the user will have to use aws tools to manually fix the 111 // situation. 112 existing, err := b.States() 113 if err != nil { 114 return nil, err 115 } 116 117 exists := false 118 for _, s := range existing { 119 if s == name { 120 exists = true 121 break 122 } 123 } 124 125 // We need to create the object so it's listed by States. 126 if !exists { 127 // take a lock on this state while we write it 128 lockInfo := state.NewLockInfo() 129 lockInfo.Operation = "init" 130 lockId, err := client.Lock(lockInfo) 131 if err != nil { 132 return nil, fmt.Errorf("failed to lock s3 state: %s", err) 133 } 134 135 // Local helper function so we can call it multiple places 136 lockUnlock := func(parent error) error { 137 if err := stateMgr.Unlock(lockId); err != nil { 138 return fmt.Errorf(strings.TrimSpace(errStateUnlock), lockId, err) 139 } 140 return parent 141 } 142 143 // Grab the value 144 // This is to ensure that no one beat us to writing a state between 145 // the `exists` check and taking the lock. 146 if err := stateMgr.RefreshState(); err != nil { 147 err = lockUnlock(err) 148 return nil, err 149 } 150 151 // If we have no state, we have to create an empty state 152 if v := stateMgr.State(); v == nil { 153 if err := stateMgr.WriteState(terraform.NewState()); err != nil { 154 err = lockUnlock(err) 155 return nil, err 156 } 157 if err := stateMgr.PersistState(); err != nil { 158 err = lockUnlock(err) 159 return nil, err 160 } 161 } 162 163 // Unlock, the state should now be initialized 164 if err := lockUnlock(nil); err != nil { 165 return nil, err 166 } 167 168 } 169 170 return stateMgr, nil 171 } 172 173 func (b *Backend) client() *RemoteClient { 174 return &RemoteClient{} 175 } 176 177 func (b *Backend) path(name string) string { 178 if name == backend.DefaultStateName { 179 return b.keyName 180 } 181 182 return strings.Join([]string{keyEnvPrefix, name, b.keyName}, "/") 183 } 184 185 const errStateUnlock = ` 186 Error unlocking S3 state. Lock ID: %s 187 188 Error: %s 189 190 You may have to force-unlock this state in order to use it again. 191 `