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