github.com/hugorut/terraform@v1.1.3/src/backend/remote-state/oss/backend_state.go (about) 1 package oss 2 3 import ( 4 "errors" 5 "fmt" 6 "log" 7 "path" 8 "sort" 9 "strings" 10 11 "github.com/aliyun/aliyun-oss-go-sdk/oss" 12 "github.com/aliyun/aliyun-tablestore-go-sdk/tablestore" 13 14 "github.com/hugorut/terraform/src/backend" 15 "github.com/hugorut/terraform/src/states" 16 "github.com/hugorut/terraform/src/states/remote" 17 "github.com/hugorut/terraform/src/states/statemgr" 18 ) 19 20 const ( 21 lockFileSuffix = ".tflock" 22 ) 23 24 // get a remote client configured for this state 25 func (b *Backend) remoteClient(name string) (*RemoteClient, error) { 26 if name == "" { 27 return nil, errors.New("missing state name") 28 } 29 30 client := &RemoteClient{ 31 ossClient: b.ossClient, 32 bucketName: b.bucketName, 33 stateFile: b.stateFile(name), 34 lockFile: b.lockFile(name), 35 serverSideEncryption: b.serverSideEncryption, 36 acl: b.acl, 37 otsTable: b.otsTable, 38 otsClient: b.otsClient, 39 } 40 if b.otsEndpoint != "" && b.otsTable != "" { 41 _, err := b.otsClient.DescribeTable(&tablestore.DescribeTableRequest{ 42 TableName: b.otsTable, 43 }) 44 if err != nil { 45 return client, fmt.Errorf("error describing table store %s: %#v", b.otsTable, err) 46 } 47 } 48 49 return client, nil 50 } 51 52 func (b *Backend) Workspaces() ([]string, error) { 53 bucket, err := b.ossClient.Bucket(b.bucketName) 54 if err != nil { 55 return []string{""}, fmt.Errorf("error getting bucket: %#v", err) 56 } 57 58 var options []oss.Option 59 options = append(options, oss.Prefix(b.statePrefix+"/"), oss.MaxKeys(1000)) 60 resp, err := bucket.ListObjects(options...) 61 if err != nil { 62 return nil, err 63 } 64 65 result := []string{backend.DefaultStateName} 66 prefix := b.statePrefix 67 lastObj := "" 68 for { 69 for _, obj := range resp.Objects { 70 // we have 3 parts, the state prefix, the workspace name, and the state file: <prefix>/<worksapce-name>/<key> 71 if path.Join(b.statePrefix, b.stateKey) == obj.Key { 72 // filter the default workspace 73 continue 74 } 75 lastObj = obj.Key 76 parts := strings.Split(strings.TrimPrefix(obj.Key, prefix+"/"), "/") 77 if len(parts) > 0 && parts[0] != "" { 78 result = append(result, parts[0]) 79 } 80 } 81 if resp.IsTruncated { 82 if len(options) == 3 { 83 options[2] = oss.Marker(lastObj) 84 } else { 85 options = append(options, oss.Marker(lastObj)) 86 } 87 resp, err = bucket.ListObjects(options...) 88 } else { 89 break 90 } 91 } 92 sort.Strings(result[1:]) 93 return result, nil 94 } 95 96 func (b *Backend) DeleteWorkspace(name string) error { 97 if name == backend.DefaultStateName || name == "" { 98 return fmt.Errorf("can't delete default state") 99 } 100 101 client, err := b.remoteClient(name) 102 if err != nil { 103 return err 104 } 105 return client.Delete() 106 } 107 108 func (b *Backend) StateMgr(name string) (statemgr.Full, error) { 109 client, err := b.remoteClient(name) 110 if err != nil { 111 return nil, err 112 } 113 stateMgr := &remote.State{Client: client} 114 115 // Check to see if this state already exists. 116 existing, err := b.Workspaces() 117 if err != nil { 118 return nil, err 119 } 120 121 log.Printf("[DEBUG] Current workspace name: %s. All workspaces:%#v", name, existing) 122 123 exists := false 124 for _, s := range existing { 125 if s == name { 126 exists = true 127 break 128 } 129 } 130 // We need to create the object so it's listed by States. 131 if !exists { 132 // take a lock on this state while we write it 133 lockInfo := statemgr.NewLockInfo() 134 lockInfo.Operation = "init" 135 lockId, err := client.Lock(lockInfo) 136 if err != nil { 137 return nil, fmt.Errorf("failed to lock OSS state: %s", err) 138 } 139 140 // Local helper function so we can call it multiple places 141 lockUnlock := func(e error) error { 142 if err := stateMgr.Unlock(lockId); err != nil { 143 return fmt.Errorf(strings.TrimSpace(stateUnlockError), lockId, err) 144 } 145 return e 146 } 147 148 // Grab the value 149 if err := stateMgr.RefreshState(); err != nil { 150 err = lockUnlock(err) 151 return nil, err 152 } 153 154 // If we have no state, we have to create an empty state 155 if v := stateMgr.State(); v == nil { 156 if err := stateMgr.WriteState(states.NewState()); err != nil { 157 err = lockUnlock(err) 158 return nil, err 159 } 160 if err := stateMgr.PersistState(); err != nil { 161 err = lockUnlock(err) 162 return nil, err 163 } 164 } 165 166 // Unlock, the state should now be initialized 167 if err := lockUnlock(nil); err != nil { 168 return nil, err 169 } 170 171 } 172 return stateMgr, nil 173 } 174 175 func (b *Backend) stateFile(name string) string { 176 if name == backend.DefaultStateName { 177 return path.Join(b.statePrefix, b.stateKey) 178 } 179 return path.Join(b.statePrefix, name, b.stateKey) 180 } 181 182 func (b *Backend) lockFile(name string) string { 183 return b.stateFile(name) + lockFileSuffix 184 } 185 186 const stateUnlockError = ` 187 Error unlocking Alibaba Cloud OSS state file: 188 189 Lock ID: %s 190 Error message: %#v 191 192 You may have to force-unlock this state in order to use it again. 193 The Alibaba Cloud backend acquires a lock during initialization to ensure the initial state file is created. 194 `