github.com/hugorut/terraform@v1.1.3/src/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/hugorut/terraform/src/backend"
    15  	"github.com/hugorut/terraform/src/states"
    16  	"github.com/hugorut/terraform/src/states/remote"
    17  	"github.com/hugorut/terraform/src/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  		} else {
    89  			break
    90  		}
    91  	}
    92  	sort.Strings(result[1:])
    93  	return result, nil
    94  }
    95  
    96  func (b *Backend) DeleteWorkspace(name string) error {
    97  	if name == backend.DefaultStateName || name == "" {
    98  		return fmt.Errorf("can't delete default state")
    99  	}
   100  
   101  	client, err := b.remoteClient(name)
   102  	if err != nil {
   103  		return err
   104  	}
   105  	return client.Delete()
   106  }
   107  
   108  func (b *Backend) StateMgr(name string) (statemgr.Full, error) {
   109  	client, err := b.remoteClient(name)
   110  	if err != nil {
   111  		return nil, err
   112  	}
   113  	stateMgr := &remote.State{Client: client}
   114  
   115  	// Check to see if this state already exists.
   116  	existing, err := b.Workspaces()
   117  	if err != nil {
   118  		return nil, err
   119  	}
   120  
   121  	log.Printf("[DEBUG] Current workspace name: %s. All workspaces:%#v", name, existing)
   122  
   123  	exists := false
   124  	for _, s := range existing {
   125  		if s == name {
   126  			exists = true
   127  			break
   128  		}
   129  	}
   130  	// We need to create the object so it's listed by States.
   131  	if !exists {
   132  		// take a lock on this state while we write it
   133  		lockInfo := statemgr.NewLockInfo()
   134  		lockInfo.Operation = "init"
   135  		lockId, err := client.Lock(lockInfo)
   136  		if err != nil {
   137  			return nil, fmt.Errorf("failed to lock OSS state: %s", err)
   138  		}
   139  
   140  		// Local helper function so we can call it multiple places
   141  		lockUnlock := func(e error) error {
   142  			if err := stateMgr.Unlock(lockId); err != nil {
   143  				return fmt.Errorf(strings.TrimSpace(stateUnlockError), lockId, err)
   144  			}
   145  			return e
   146  		}
   147  
   148  		// Grab the value
   149  		if err := stateMgr.RefreshState(); err != nil {
   150  			err = lockUnlock(err)
   151  			return nil, err
   152  		}
   153  
   154  		// If we have no state, we have to create an empty state
   155  		if v := stateMgr.State(); v == nil {
   156  			if err := stateMgr.WriteState(states.NewState()); err != nil {
   157  				err = lockUnlock(err)
   158  				return nil, err
   159  			}
   160  			if err := stateMgr.PersistState(); err != nil {
   161  				err = lockUnlock(err)
   162  				return nil, err
   163  			}
   164  		}
   165  
   166  		// Unlock, the state should now be initialized
   167  		if err := lockUnlock(nil); err != nil {
   168  			return nil, err
   169  		}
   170  
   171  	}
   172  	return stateMgr, nil
   173  }
   174  
   175  func (b *Backend) stateFile(name string) string {
   176  	if name == backend.DefaultStateName {
   177  		return path.Join(b.statePrefix, b.stateKey)
   178  	}
   179  	return path.Join(b.statePrefix, name, b.stateKey)
   180  }
   181  
   182  func (b *Backend) lockFile(name string) string {
   183  	return b.stateFile(name) + lockFileSuffix
   184  }
   185  
   186  const stateUnlockError = `
   187  Error unlocking Alibaba Cloud OSS state file:
   188  
   189  Lock ID: %s
   190  Error message: %#v
   191  
   192  You may have to force-unlock this state in order to use it again.
   193  The Alibaba Cloud backend acquires a lock during initialization to ensure the initial state file is created.
   194  `