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