github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/backend/remote-state/oss/backend_state.go (about)

     1  package oss
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"log"
     7  	"path"
     8  	"sort"
     9  	"strings"
    10  
    11  	"github.com/aliyun/aliyun-oss-go-sdk/oss"
    12  	"github.com/aliyun/aliyun-tablestore-go-sdk/tablestore"
    13  
    14  	"github.com/hashicorp/terraform/internal/backend"
    15  	"github.com/hashicorp/terraform/internal/states"
    16  	"github.com/hashicorp/terraform/internal/states/remote"
    17  	"github.com/hashicorp/terraform/internal/states/statemgr"
    18  )
    19  
    20  const (
    21  	lockFileSuffix = ".tflock"
    22  )
    23  
    24  // get a remote client configured for this state
    25  func (b *Backend) remoteClient(name string) (*RemoteClient, error) {
    26  	if name == "" {
    27  		return nil, errors.New("missing state name")
    28  	}
    29  
    30  	client := &RemoteClient{
    31  		ossClient:            b.ossClient,
    32  		bucketName:           b.bucketName,
    33  		stateFile:            b.stateFile(name),
    34  		lockFile:             b.lockFile(name),
    35  		serverSideEncryption: b.serverSideEncryption,
    36  		acl:                  b.acl,
    37  		otsTable:             b.otsTable,
    38  		otsClient:            b.otsClient,
    39  	}
    40  	if b.otsEndpoint != "" && b.otsTable != "" {
    41  		_, err := b.otsClient.DescribeTable(&tablestore.DescribeTableRequest{
    42  			TableName: b.otsTable,
    43  		})
    44  		if err != nil {
    45  			return client, fmt.Errorf("error describing table store %s: %#v", b.otsTable, err)
    46  		}
    47  	}
    48  
    49  	return client, nil
    50  }
    51  
    52  func (b *Backend) Workspaces() ([]string, error) {
    53  	bucket, err := b.ossClient.Bucket(b.bucketName)
    54  	if err != nil {
    55  		return []string{""}, fmt.Errorf("error getting bucket: %#v", err)
    56  	}
    57  
    58  	var options []oss.Option
    59  	options = append(options, oss.Prefix(b.statePrefix+"/"), oss.MaxKeys(1000))
    60  	resp, err := bucket.ListObjects(options...)
    61  	if err != nil {
    62  		return nil, err
    63  	}
    64  
    65  	result := []string{backend.DefaultStateName}
    66  	prefix := b.statePrefix
    67  	lastObj := ""
    68  	for {
    69  		for _, obj := range resp.Objects {
    70  			// we have 3 parts, the state prefix, the workspace name, and the state file: <prefix>/<worksapce-name>/<key>
    71  			if path.Join(b.statePrefix, b.stateKey) == obj.Key {
    72  				// filter the default workspace
    73  				continue
    74  			}
    75  			lastObj = obj.Key
    76  			parts := strings.Split(strings.TrimPrefix(obj.Key, prefix+"/"), "/")
    77  			if len(parts) > 0 && parts[0] != "" {
    78  				result = append(result, parts[0])
    79  			}
    80  		}
    81  		if resp.IsTruncated {
    82  			if len(options) == 3 {
    83  				options[2] = oss.Marker(lastObj)
    84  			} else {
    85  				options = append(options, oss.Marker(lastObj))
    86  			}
    87  			resp, err = bucket.ListObjects(options...)
    88  			if err != nil {
    89  				return nil, err
    90  			}
    91  		} else {
    92  			break
    93  		}
    94  	}
    95  	sort.Strings(result[1:])
    96  	return result, nil
    97  }
    98  
    99  func (b *Backend) DeleteWorkspace(name string, _ bool) error {
   100  	if name == backend.DefaultStateName || name == "" {
   101  		return fmt.Errorf("can't delete default state")
   102  	}
   103  
   104  	client, err := b.remoteClient(name)
   105  	if err != nil {
   106  		return err
   107  	}
   108  	return client.Delete()
   109  }
   110  
   111  func (b *Backend) StateMgr(name string) (statemgr.Full, error) {
   112  	client, err := b.remoteClient(name)
   113  	if err != nil {
   114  		return nil, err
   115  	}
   116  	stateMgr := &remote.State{Client: client}
   117  
   118  	// Check to see if this state already exists.
   119  	existing, err := b.Workspaces()
   120  	if err != nil {
   121  		return nil, err
   122  	}
   123  
   124  	log.Printf("[DEBUG] Current workspace name: %s. All workspaces:%#v", name, existing)
   125  
   126  	exists := false
   127  	for _, s := range existing {
   128  		if s == name {
   129  			exists = true
   130  			break
   131  		}
   132  	}
   133  	// We need to create the object so it's listed by States.
   134  	if !exists {
   135  		// take a lock on this state while we write it
   136  		lockInfo := statemgr.NewLockInfo()
   137  		lockInfo.Operation = "init"
   138  		lockId, err := client.Lock(lockInfo)
   139  		if err != nil {
   140  			return nil, fmt.Errorf("failed to lock OSS state: %s", err)
   141  		}
   142  
   143  		// Local helper function so we can call it multiple places
   144  		lockUnlock := func(e error) error {
   145  			if err := stateMgr.Unlock(lockId); err != nil {
   146  				return fmt.Errorf(strings.TrimSpace(stateUnlockError), lockId, err)
   147  			}
   148  			return e
   149  		}
   150  
   151  		// Grab the value
   152  		if err := stateMgr.RefreshState(); err != nil {
   153  			err = lockUnlock(err)
   154  			return nil, err
   155  		}
   156  
   157  		// If we have no state, we have to create an empty state
   158  		if v := stateMgr.State(); v == nil {
   159  			if err := stateMgr.WriteState(states.NewState()); err != nil {
   160  				err = lockUnlock(err)
   161  				return nil, err
   162  			}
   163  			if err := stateMgr.PersistState(nil); err != nil {
   164  				err = lockUnlock(err)
   165  				return nil, err
   166  			}
   167  		}
   168  
   169  		// Unlock, the state should now be initialized
   170  		if err := lockUnlock(nil); err != nil {
   171  			return nil, err
   172  		}
   173  
   174  	}
   175  	return stateMgr, nil
   176  }
   177  
   178  func (b *Backend) stateFile(name string) string {
   179  	if name == backend.DefaultStateName {
   180  		return path.Join(b.statePrefix, b.stateKey)
   181  	}
   182  	return path.Join(b.statePrefix, name, b.stateKey)
   183  }
   184  
   185  func (b *Backend) lockFile(name string) string {
   186  	return b.stateFile(name) + lockFileSuffix
   187  }
   188  
   189  const stateUnlockError = `
   190  Error unlocking Alibaba Cloud OSS state file:
   191  
   192  Lock ID: %s
   193  Error message: %#v
   194  
   195  You may have to force-unlock this state in order to use it again.
   196  The Alibaba Cloud backend acquires a lock during initialization to ensure the initial state file is created.
   197  `