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

     1  package s3
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"path"
     7  	"sort"
     8  	"strings"
     9  
    10  	"github.com/aws/aws-sdk-go/aws"
    11  	"github.com/aws/aws-sdk-go/aws/awserr"
    12  	"github.com/aws/aws-sdk-go/service/s3"
    13  
    14  	"github.com/hashicorp/terraform/backend"
    15  	"github.com/hashicorp/terraform/state"
    16  	"github.com/hashicorp/terraform/state/remote"
    17  	"github.com/hashicorp/terraform/states"
    18  )
    19  
    20  func (b *Backend) Workspaces() ([]string, error) {
    21  	const maxKeys = 1000
    22  
    23  	prefix := ""
    24  
    25  	if b.workspaceKeyPrefix != "" {
    26  		prefix = b.workspaceKeyPrefix + "/"
    27  	}
    28  
    29  	params := &s3.ListObjectsInput{
    30  		Bucket:  &b.bucketName,
    31  		Prefix:  aws.String(prefix),
    32  		MaxKeys: aws.Int64(maxKeys),
    33  	}
    34  
    35  	wss := []string{backend.DefaultStateName}
    36  	err := b.s3Client.ListObjectsPages(params, func(page *s3.ListObjectsOutput, lastPage bool) bool {
    37  		for _, obj := range page.Contents {
    38  			ws := b.keyEnv(*obj.Key)
    39  			if ws != "" {
    40  				wss = append(wss, ws)
    41  			}
    42  		}
    43  		return !lastPage
    44  	})
    45  
    46  	if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == s3.ErrCodeNoSuchBucket {
    47  		return nil, fmt.Errorf(errS3NoSuchBucket, err)
    48  	}
    49  
    50  	sort.Strings(wss[1:])
    51  	return wss, nil
    52  }
    53  
    54  func (b *Backend) keyEnv(key string) string {
    55  	prefix := b.workspaceKeyPrefix
    56  
    57  	if prefix == "" {
    58  		parts := strings.SplitN(key, "/", 2)
    59  		if len(parts) > 1 && parts[1] == b.keyName {
    60  			return parts[0]
    61  		} else {
    62  			return ""
    63  		}
    64  	}
    65  
    66  	// add a slash to treat this as a directory
    67  	prefix += "/"
    68  
    69  	parts := strings.SplitAfterN(key, prefix, 2)
    70  	if len(parts) < 2 {
    71  		return ""
    72  	}
    73  
    74  	// shouldn't happen since we listed by prefix
    75  	if parts[0] != prefix {
    76  		return ""
    77  	}
    78  
    79  	parts = strings.SplitN(parts[1], "/", 2)
    80  
    81  	if len(parts) < 2 {
    82  		return ""
    83  	}
    84  
    85  	// not our key, so don't include it in our listing
    86  	if parts[1] != b.keyName {
    87  		return ""
    88  	}
    89  
    90  	return parts[0]
    91  }
    92  
    93  func (b *Backend) DeleteWorkspace(name string) error {
    94  	if name == backend.DefaultStateName || name == "" {
    95  		return fmt.Errorf("can't delete default state")
    96  	}
    97  
    98  	client, err := b.remoteClient(name)
    99  	if err != nil {
   100  		return err
   101  	}
   102  
   103  	return client.Delete()
   104  }
   105  
   106  // get a remote client configured for this state
   107  func (b *Backend) remoteClient(name string) (*RemoteClient, error) {
   108  	if name == "" {
   109  		return nil, errors.New("missing state name")
   110  	}
   111  
   112  	client := &RemoteClient{
   113  		s3Client:              b.s3Client,
   114  		dynClient:             b.dynClient,
   115  		bucketName:            b.bucketName,
   116  		path:                  b.path(name),
   117  		serverSideEncryption:  b.serverSideEncryption,
   118  		customerEncryptionKey: b.customerEncryptionKey,
   119  		acl:                   b.acl,
   120  		kmsKeyID:              b.kmsKeyID,
   121  		ddbTable:              b.ddbTable,
   122  	}
   123  
   124  	return client, nil
   125  }
   126  
   127  func (b *Backend) StateMgr(name string) (state.State, error) {
   128  	return b.stateMgr(name, true)
   129  }
   130  
   131  func (b *Backend) StateMgrWithoutCheckVersion(name string) (state.State, error) {
   132  	return b.stateMgr(name, false)
   133  }
   134  
   135  func (b *Backend) stateMgr(name string, checkVersion bool) (state.State, error) {
   136  	client, err := b.remoteClient(name)
   137  	if err != nil {
   138  		return nil, err
   139  	}
   140  
   141  	stateMgr := &remote.State{Client: client}
   142  	// Check to see if this state already exists.
   143  	// If we're trying to force-unlock a state, we can't take the lock before
   144  	// fetching the state. If the state doesn't exist, we have to assume this
   145  	// is a normal create operation, and take the lock at that point.
   146  	//
   147  	// If we need to force-unlock, but for some reason the state no longer
   148  	// exists, the user will have to use aws tools to manually fix the
   149  	// situation.
   150  	existing, err := b.Workspaces()
   151  	if err != nil {
   152  		return nil, err
   153  	}
   154  
   155  	exists := false
   156  	for _, s := range existing {
   157  		if s == name {
   158  			exists = true
   159  			break
   160  		}
   161  	}
   162  
   163  	// We need to create the object so it's listed by States.
   164  	if !exists {
   165  		// take a lock on this state while we write it
   166  		lockInfo := state.NewLockInfo()
   167  		lockInfo.Operation = "init"
   168  		lockId, err := client.Lock(lockInfo)
   169  		if err != nil {
   170  			return nil, fmt.Errorf("failed to lock s3 state: %s", err)
   171  		}
   172  
   173  		// Local helper function so we can call it multiple places
   174  		lockUnlock := func(parent error) error {
   175  			if err := stateMgr.Unlock(lockId); err != nil {
   176  				return fmt.Errorf(strings.TrimSpace(errStateUnlock), lockId, err)
   177  			}
   178  			return parent
   179  		}
   180  
   181  		// Grab the value
   182  		// This is to ensure that no one beat us to writing a state between
   183  		// the `exists` check and taking the lock.
   184  		if checkVersion {
   185  			if err := stateMgr.RefreshState(); err != nil {
   186  				err = lockUnlock(err)
   187  				return nil, err
   188  			}
   189  		} else {
   190  			if err := stateMgr.RefreshStateWithoutCheckVersion(); err != nil {
   191  				err = lockUnlock(err)
   192  				return nil, err
   193  			}
   194  		}
   195  
   196  		// If we have no state, we have to create an empty state
   197  		if v := stateMgr.State(); v == nil {
   198  			if err := stateMgr.WriteState(states.NewState()); err != nil {
   199  				err = lockUnlock(err)
   200  				return nil, err
   201  			}
   202  			if err := stateMgr.PersistState(); err != nil {
   203  				err = lockUnlock(err)
   204  				return nil, err
   205  			}
   206  		}
   207  
   208  		// Unlock, the state should now be initialized
   209  		if err := lockUnlock(nil); err != nil {
   210  			return nil, err
   211  		}
   212  
   213  	}
   214  
   215  	return stateMgr, nil
   216  }
   217  
   218  func (b *Backend) client() *RemoteClient {
   219  	return &RemoteClient{}
   220  }
   221  
   222  func (b *Backend) path(name string) string {
   223  	if name == backend.DefaultStateName {
   224  		return b.keyName
   225  	}
   226  
   227  	return path.Join(b.workspaceKeyPrefix, name, b.keyName)
   228  }
   229  
   230  const errStateUnlock = `
   231  Error unlocking S3 state. Lock ID: %s
   232  
   233  Error: %s
   234  
   235  You may have to force-unlock this state in order to use it again.
   236  `