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  `