github.com/danp/terraform@v0.9.5-0.20170426144147-39d740081351/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 //if this isn't the default state name, we need to create the object so 105 //it's listed by States. 106 if name != backend.DefaultStateName { 107 // take a lock on this state while we write it 108 lockInfo := state.NewLockInfo() 109 lockInfo.Operation = "init" 110 lockId, err := client.Lock(lockInfo) 111 if err != nil { 112 return nil, fmt.Errorf("failed to lock s3 state: %s", err) 113 } 114 115 // Local helper function so we can call it multiple places 116 lockUnlock := func(parent error) error { 117 if err := stateMgr.Unlock(lockId); err != nil { 118 return fmt.Errorf(strings.TrimSpace(errStateUnlock), lockId, err) 119 } 120 return parent 121 } 122 123 // Grab the value 124 if err := stateMgr.RefreshState(); err != nil { 125 err = lockUnlock(err) 126 return nil, err 127 } 128 129 // If we have no state, we have to create an empty state 130 if v := stateMgr.State(); v == nil { 131 if err := stateMgr.WriteState(terraform.NewState()); err != nil { 132 err = lockUnlock(err) 133 return nil, err 134 } 135 if err := stateMgr.PersistState(); err != nil { 136 err = lockUnlock(err) 137 return nil, err 138 } 139 } 140 141 // Unlock, the state should now be initialized 142 if err := lockUnlock(nil); err != nil { 143 return nil, err 144 } 145 146 } 147 148 return stateMgr, nil 149 } 150 151 func (b *Backend) client() *RemoteClient { 152 return &RemoteClient{} 153 } 154 155 func (b *Backend) path(name string) string { 156 if name == backend.DefaultStateName { 157 return b.keyName 158 } 159 160 return strings.Join([]string{keyEnvPrefix, name, b.keyName}, "/") 161 } 162 163 const errStateUnlock = ` 164 Error unlocking S3 state. Lock ID: %s 165 166 Error: %s 167 168 You may have to force-unlock this state in order to use it again. 169 `