github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/backend/remote-state/oss/backend_state.go (about)

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