github.com/codeherentuk/terraform@v0.11.12-beta1/backend/remote-state/s3/backend_state.go (about)

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