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  `