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