kubeform.dev/terraform-backend-sdk@v0.0.0-20220310143633-45f07fe731c5/backend/remote-state/cos/backend_state.go (about) 1 package cos 2 3 import ( 4 "fmt" 5 "log" 6 "path" 7 "sort" 8 "strings" 9 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 "github.com/likexian/gokit/assert" 15 ) 16 17 // Define file suffix 18 const ( 19 stateFileSuffix = ".tfstate" 20 lockFileSuffix = ".tflock" 21 ) 22 23 // Workspaces returns a list of names for the workspaces 24 func (b *Backend) Workspaces() ([]string, error) { 25 c, err := b.client("tencentcloud") 26 if err != nil { 27 return nil, err 28 } 29 30 obs, err := c.getBucket(b.prefix) 31 log.Printf("[DEBUG] list all workspaces, objects: %v, error: %v", obs, err) 32 if err != nil { 33 return nil, err 34 } 35 36 ws := []string{backend.DefaultStateName} 37 for _, vv := range obs { 38 // <name>.tfstate 39 if !strings.HasSuffix(vv.Key, stateFileSuffix) { 40 continue 41 } 42 // default worksapce 43 if path.Join(b.prefix, b.key) == vv.Key { 44 continue 45 } 46 // <prefix>/<worksapce>/<key> 47 prefix := strings.TrimRight(b.prefix, "/") + "/" 48 parts := strings.Split(strings.TrimPrefix(vv.Key, prefix), "/") 49 if len(parts) > 0 && parts[0] != "" { 50 ws = append(ws, parts[0]) 51 } 52 } 53 54 sort.Strings(ws[1:]) 55 log.Printf("[DEBUG] list all workspaces, workspaces: %v", ws) 56 57 return ws, nil 58 } 59 60 // DeleteWorkspace deletes the named workspaces. The "default" state cannot be deleted. 61 func (b *Backend) DeleteWorkspace(name string) error { 62 log.Printf("[DEBUG] delete workspace, workspace: %v", name) 63 64 if name == backend.DefaultStateName || name == "" { 65 return fmt.Errorf("default state is not allow to delete") 66 } 67 68 c, err := b.client(name) 69 if err != nil { 70 return err 71 } 72 73 return c.Delete() 74 } 75 76 // StateMgr manage the state, if the named state not exists, a new file will created 77 func (b *Backend) StateMgr(name string) (statemgr.Full, error) { 78 log.Printf("[DEBUG] state manager, current workspace: %v", name) 79 80 c, err := b.client(name) 81 if err != nil { 82 return nil, err 83 } 84 stateMgr := &remote.State{Client: c} 85 86 ws, err := b.Workspaces() 87 if err != nil { 88 return nil, err 89 } 90 91 if !assert.IsContains(ws, name) { 92 log.Printf("[DEBUG] workspace %v not exists", name) 93 94 // take a lock on this state while we write it 95 lockInfo := statemgr.NewLockInfo() 96 lockInfo.Operation = "init" 97 lockId, err := c.Lock(lockInfo) 98 if err != nil { 99 return nil, fmt.Errorf("Failed to lock cos state: %s", err) 100 } 101 102 // Local helper function so we can call it multiple places 103 lockUnlock := func(e error) error { 104 if err := stateMgr.Unlock(lockId); err != nil { 105 return fmt.Errorf(unlockErrMsg, err, lockId) 106 } 107 return e 108 } 109 110 // Grab the value 111 if err := stateMgr.RefreshState(); err != nil { 112 err = lockUnlock(err) 113 return nil, err 114 } 115 116 // If we have no state, we have to create an empty state 117 if v := stateMgr.State(); v == nil { 118 if err := stateMgr.WriteState(states.NewState()); err != nil { 119 err = lockUnlock(err) 120 return nil, err 121 } 122 if err := stateMgr.PersistState(); err != nil { 123 err = lockUnlock(err) 124 return nil, err 125 } 126 } 127 128 // Unlock, the state should now be initialized 129 if err := lockUnlock(nil); err != nil { 130 return nil, err 131 } 132 } 133 134 return stateMgr, nil 135 } 136 137 // client returns a remoteClient for the named state. 138 func (b *Backend) client(name string) (*remoteClient, error) { 139 if strings.TrimSpace(name) == "" { 140 return nil, fmt.Errorf("state name not allow to be empty") 141 } 142 143 return &remoteClient{ 144 cosContext: b.cosContext, 145 cosClient: b.cosClient, 146 tagClient: b.tagClient, 147 bucket: b.bucket, 148 stateFile: b.stateFile(name), 149 lockFile: b.lockFile(name), 150 encrypt: b.encrypt, 151 acl: b.acl, 152 }, nil 153 } 154 155 // stateFile returns state file path by name 156 func (b *Backend) stateFile(name string) string { 157 if name == backend.DefaultStateName { 158 return path.Join(b.prefix, b.key) 159 } 160 return path.Join(b.prefix, name, b.key) 161 } 162 163 // lockFile returns lock file path by name 164 func (b *Backend) lockFile(name string) string { 165 return b.stateFile(name) + lockFileSuffix 166 } 167 168 // unlockErrMsg is error msg for unlock failed 169 const unlockErrMsg = ` 170 Unlocking the state file on TencentCloud cos backend failed: 171 172 Error message: %v 173 Lock ID (gen): %s 174 175 You may have to force-unlock this state in order to use it again. 176 The TencentCloud backend acquires a lock during initialization 177 to ensure the initial state file is created. 178 `