github.com/dougneal/terraform@v0.6.15-0.20170330092735-b6a3840768a4/backend/remote-state/s3/backend_state.go (about) 1 package s3 2 3 import ( 4 "fmt" 5 "sort" 6 "strings" 7 8 "github.com/aws/aws-sdk-go/aws" 9 "github.com/aws/aws-sdk-go/service/s3" 10 "github.com/hashicorp/terraform/backend" 11 "github.com/hashicorp/terraform/state" 12 "github.com/hashicorp/terraform/state/remote" 13 "github.com/hashicorp/terraform/terraform" 14 ) 15 16 const ( 17 // This will be used as directory name, the odd looking colon is simply to 18 // reduce the chance of name conflicts with existing objects. 19 keyEnvPrefix = "env:" 20 ) 21 22 func (b *Backend) States() ([]string, error) { 23 params := &s3.ListObjectsInput{ 24 Bucket: &b.bucketName, 25 Prefix: aws.String(keyEnvPrefix + "/"), 26 } 27 28 resp, err := b.s3Client.ListObjects(params) 29 if err != nil { 30 return nil, err 31 } 32 33 var envs []string 34 for _, obj := range resp.Contents { 35 env := keyEnv(*obj.Key) 36 if env != "" { 37 envs = append(envs, env) 38 } 39 } 40 41 sort.Strings(envs) 42 envs = append([]string{backend.DefaultStateName}, envs...) 43 return envs, nil 44 } 45 46 // extract the env name from the S3 key 47 func keyEnv(key string) string { 48 parts := strings.Split(key, "/") 49 if len(parts) < 3 { 50 // no env here 51 return "" 52 } 53 54 if parts[0] != keyEnvPrefix { 55 // not our key, so ignore 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 params := &s3.DeleteObjectInput{ 68 Bucket: &b.bucketName, 69 Key: aws.String(b.path(name)), 70 } 71 72 _, err := b.s3Client.DeleteObject(params) 73 if err != nil { 74 return err 75 } 76 77 return nil 78 } 79 80 func (b *Backend) State(name string) (state.State, error) { 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 lockTable: b.lockTable, 90 } 91 92 stateMgr := &remote.State{Client: client} 93 94 //if this isn't the default state name, we need to create the object so 95 //it's listed by States. 96 if name != backend.DefaultStateName { 97 // take a lock on this state while we write it 98 lockInfo := state.NewLockInfo() 99 lockInfo.Operation = "init" 100 lockId, err := client.Lock(lockInfo) 101 if err != nil { 102 return nil, fmt.Errorf("failed to lock s3 state: %s", err) 103 } 104 105 // Local helper function so we can call it multiple places 106 lockUnlock := func(parent error) error { 107 if err := stateMgr.Unlock(lockId); err != nil { 108 return fmt.Errorf(strings.TrimSpace(errStateUnlock), lockId, err) 109 } 110 return parent 111 } 112 113 // Grab the value 114 if err := stateMgr.RefreshState(); err != nil { 115 err = lockUnlock(err) 116 return nil, err 117 } 118 119 // If we have no state, we have to create an empty state 120 if v := stateMgr.State(); v == nil { 121 if err := stateMgr.WriteState(terraform.NewState()); err != nil { 122 err = lockUnlock(err) 123 return nil, err 124 } 125 if err := stateMgr.PersistState(); err != nil { 126 err = lockUnlock(err) 127 return nil, err 128 } 129 } 130 131 // Unlock, the state should now be initialized 132 if err := lockUnlock(nil); err != nil { 133 return nil, err 134 } 135 136 } 137 138 return stateMgr, nil 139 } 140 141 func (b *Backend) client() *RemoteClient { 142 return &RemoteClient{} 143 } 144 145 func (b *Backend) path(name string) string { 146 if name == backend.DefaultStateName { 147 return b.keyName 148 } 149 150 return strings.Join([]string{keyEnvPrefix, name, b.keyName}, "/") 151 } 152 153 const errStateUnlock = ` 154 Error unlocking S3 state. Lock ID: %s 155 156 Error: %s 157 158 You may have to force-unlock this state in order to use it again. 159 `