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  `