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