github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/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/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  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, _ bool) 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) (statemgr.Full, error) {
   128  	client, err := b.remoteClient(name)
   129  	if err != nil {
   130  		return nil, err
   131  	}
   132  
   133  	stateMgr := &remote.State{Client: client}
   134  	// Check to see if this state already exists.
   135  	// If we're trying to force-unlock a state, we can't take the lock before
   136  	// fetching the state. If the state doesn't exist, we have to assume this
   137  	// is a normal create operation, and take the lock at that point.
   138  	//
   139  	// If we need to force-unlock, but for some reason the state no longer
   140  	// exists, the user will have to use aws tools to manually fix the
   141  	// situation.
   142  	existing, err := b.Workspaces()
   143  	if err != nil {
   144  		return nil, err
   145  	}
   146  
   147  	exists := false
   148  	for _, s := range existing {
   149  		if s == name {
   150  			exists = true
   151  			break
   152  		}
   153  	}
   154  
   155  	// We need to create the object so it's listed by States.
   156  	if !exists {
   157  		// take a lock on this state while we write it
   158  		lockInfo := statemgr.NewLockInfo()
   159  		lockInfo.Operation = "init"
   160  		lockId, err := client.Lock(lockInfo)
   161  		if err != nil {
   162  			return nil, fmt.Errorf("failed to lock s3 state: %s", err)
   163  		}
   164  
   165  		// Local helper function so we can call it multiple places
   166  		lockUnlock := func(parent error) error {
   167  			if err := stateMgr.Unlock(lockId); err != nil {
   168  				return fmt.Errorf(strings.TrimSpace(errStateUnlock), lockId, err)
   169  			}
   170  			return parent
   171  		}
   172  
   173  		// Grab the value
   174  		// This is to ensure that no one beat us to writing a state between
   175  		// the `exists` check and taking the lock.
   176  		if err := stateMgr.RefreshState(); err != nil {
   177  			err = lockUnlock(err)
   178  			return nil, err
   179  		}
   180  
   181  		// If we have no state, we have to create an empty state
   182  		if v := stateMgr.State(); v == nil {
   183  			if err := stateMgr.WriteState(states.NewState()); err != nil {
   184  				err = lockUnlock(err)
   185  				return nil, err
   186  			}
   187  			if err := stateMgr.PersistState(nil); err != nil {
   188  				err = lockUnlock(err)
   189  				return nil, err
   190  			}
   191  		}
   192  
   193  		// Unlock, the state should now be initialized
   194  		if err := lockUnlock(nil); err != nil {
   195  			return nil, err
   196  		}
   197  
   198  	}
   199  
   200  	return stateMgr, nil
   201  }
   202  
   203  func (b *Backend) client() *RemoteClient {
   204  	return &RemoteClient{}
   205  }
   206  
   207  func (b *Backend) path(name string) string {
   208  	if name == backend.DefaultStateName {
   209  		return b.keyName
   210  	}
   211  
   212  	return path.Join(b.workspaceKeyPrefix, name, b.keyName)
   213  }
   214  
   215  const errStateUnlock = `
   216  Error unlocking S3 state. Lock ID: %s
   217  
   218  Error: %s
   219  
   220  You may have to force-unlock this state in order to use it again.
   221  `