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