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