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