github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/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 "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 "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) (state.State, error) { 78 return b.stateMgr(name, true) 79 } 80 81 func (b *Backend) StateMgrWithoutCheckVersion(name string) (state.State, error) { 82 return b.stateMgr(name, false) 83 } 84 85 func (b *Backend) stateMgr(name string, checkVersion bool) (state.State, error) { 86 log.Printf("[DEBUG] state manager, current workspace: %v", name) 87 88 c, err := b.client(name) 89 if err != nil { 90 return nil, err 91 } 92 stateMgr := &remote.State{Client: c} 93 94 ws, err := b.Workspaces() 95 if err != nil { 96 return nil, err 97 } 98 99 if !assert.IsContains(ws, name) { 100 log.Printf("[DEBUG] workspace %v not exists", name) 101 102 // take a lock on this state while we write it 103 lockInfo := state.NewLockInfo() 104 lockInfo.Operation = "init" 105 lockId, err := c.Lock(lockInfo) 106 if err != nil { 107 return nil, fmt.Errorf("Failed to lock cos state: %s", err) 108 } 109 110 // Local helper function so we can call it multiple places 111 lockUnlock := func(e error) error { 112 if err := stateMgr.Unlock(lockId); err != nil { 113 return fmt.Errorf(unlockErrMsg, err, lockId) 114 } 115 return e 116 } 117 118 // Grab the value 119 if checkVersion { 120 if err := stateMgr.RefreshState(); err != nil { 121 err = lockUnlock(err) 122 return nil, err 123 } 124 } else { 125 if err := stateMgr.RefreshStateWithoutCheckVersion(); err != nil { 126 err = lockUnlock(err) 127 return nil, err 128 } 129 } 130 131 // If we have no state, we have to create an empty state 132 if v := stateMgr.State(); v == nil { 133 if err := stateMgr.WriteState(states.NewState()); err != nil { 134 err = lockUnlock(err) 135 return nil, err 136 } 137 if err := stateMgr.PersistState(); err != nil { 138 err = lockUnlock(err) 139 return nil, err 140 } 141 } 142 143 // Unlock, the state should now be initialized 144 if err := lockUnlock(nil); err != nil { 145 return nil, err 146 } 147 } 148 149 return stateMgr, nil 150 } 151 152 // client returns a remoteClient for the named state. 153 func (b *Backend) client(name string) (*remoteClient, error) { 154 if strings.TrimSpace(name) == "" { 155 return nil, fmt.Errorf("state name not allow to be empty") 156 } 157 158 return &remoteClient{ 159 cosContext: b.cosContext, 160 cosClient: b.cosClient, 161 tagClient: b.tagClient, 162 bucket: b.bucket, 163 stateFile: b.stateFile(name), 164 lockFile: b.lockFile(name), 165 encrypt: b.encrypt, 166 acl: b.acl, 167 }, nil 168 } 169 170 // stateFile returns state file path by name 171 func (b *Backend) stateFile(name string) string { 172 if name == backend.DefaultStateName { 173 return path.Join(b.prefix, b.key) 174 } 175 return path.Join(b.prefix, name, b.key) 176 } 177 178 // lockFile returns lock file path by name 179 func (b *Backend) lockFile(name string) string { 180 return b.stateFile(name) + lockFileSuffix 181 } 182 183 // unlockErrMsg is error msg for unlock failed 184 const unlockErrMsg = ` 185 Unlocking the state file on TencentCloud cos backend failed: 186 187 Error message: %v 188 Lock ID (gen): %s 189 190 You may have to force-unlock this state in order to use it again. 191 The TencentCloud backend acquires a lock during initialization 192 to ensure the initial state file is created. 193 `