github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/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/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 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 if err != nil { 89 return nil, err 90 } 91 } else { 92 break 93 } 94 } 95 sort.Strings(result[1:]) 96 return result, nil 97 } 98 99 func (b *Backend) DeleteWorkspace(name string, _ bool) error { 100 if name == backend.DefaultStateName || name == "" { 101 return fmt.Errorf("can't delete default state") 102 } 103 104 client, err := b.remoteClient(name) 105 if err != nil { 106 return err 107 } 108 return client.Delete() 109 } 110 111 func (b *Backend) StateMgr(name string) (statemgr.Full, error) { 112 client, err := b.remoteClient(name) 113 if err != nil { 114 return nil, err 115 } 116 stateMgr := &remote.State{Client: client} 117 118 // Check to see if this state already exists. 119 existing, err := b.Workspaces() 120 if err != nil { 121 return nil, err 122 } 123 124 log.Printf("[DEBUG] Current workspace name: %s. All workspaces:%#v", name, existing) 125 126 exists := false 127 for _, s := range existing { 128 if s == name { 129 exists = true 130 break 131 } 132 } 133 // We need to create the object so it's listed by States. 134 if !exists { 135 // take a lock on this state while we write it 136 lockInfo := statemgr.NewLockInfo() 137 lockInfo.Operation = "init" 138 lockId, err := client.Lock(lockInfo) 139 if err != nil { 140 return nil, fmt.Errorf("failed to lock OSS state: %s", err) 141 } 142 143 // Local helper function so we can call it multiple places 144 lockUnlock := func(e error) error { 145 if err := stateMgr.Unlock(lockId); err != nil { 146 return fmt.Errorf(strings.TrimSpace(stateUnlockError), lockId, err) 147 } 148 return e 149 } 150 151 // Grab the value 152 if err := stateMgr.RefreshState(); err != nil { 153 err = lockUnlock(err) 154 return nil, err 155 } 156 157 // If we have no state, we have to create an empty state 158 if v := stateMgr.State(); v == nil { 159 if err := stateMgr.WriteState(states.NewState()); err != nil { 160 err = lockUnlock(err) 161 return nil, err 162 } 163 if err := stateMgr.PersistState(nil); err != nil { 164 err = lockUnlock(err) 165 return nil, err 166 } 167 } 168 169 // Unlock, the state should now be initialized 170 if err := lockUnlock(nil); err != nil { 171 return nil, err 172 } 173 174 } 175 return stateMgr, nil 176 } 177 178 func (b *Backend) stateFile(name string) string { 179 if name == backend.DefaultStateName { 180 return path.Join(b.statePrefix, b.stateKey) 181 } 182 return path.Join(b.statePrefix, name, b.stateKey) 183 } 184 185 func (b *Backend) lockFile(name string) string { 186 return b.stateFile(name) + lockFileSuffix 187 } 188 189 const stateUnlockError = ` 190 Error unlocking Alibaba Cloud OSS state file: 191 192 Lock ID: %s 193 Error message: %#v 194 195 You may have to force-unlock this state in order to use it again. 196 The Alibaba Cloud backend acquires a lock during initialization to ensure the initial state file is created. 197 `