github.com/dougneal/terraform@v0.6.15-0.20170330092735-b6a3840768a4/backend/remote-state/s3/backend_state.go (about)

     1  package s3
     2  
     3  import (
     4  	"fmt"
     5  	"sort"
     6  	"strings"
     7  
     8  	"github.com/aws/aws-sdk-go/aws"
     9  	"github.com/aws/aws-sdk-go/service/s3"
    10  	"github.com/hashicorp/terraform/backend"
    11  	"github.com/hashicorp/terraform/state"
    12  	"github.com/hashicorp/terraform/state/remote"
    13  	"github.com/hashicorp/terraform/terraform"
    14  )
    15  
    16  const (
    17  	// This will be used as directory name, the odd looking colon is simply to
    18  	// reduce the chance of name conflicts with existing objects.
    19  	keyEnvPrefix = "env:"
    20  )
    21  
    22  func (b *Backend) States() ([]string, error) {
    23  	params := &s3.ListObjectsInput{
    24  		Bucket: &b.bucketName,
    25  		Prefix: aws.String(keyEnvPrefix + "/"),
    26  	}
    27  
    28  	resp, err := b.s3Client.ListObjects(params)
    29  	if err != nil {
    30  		return nil, err
    31  	}
    32  
    33  	var envs []string
    34  	for _, obj := range resp.Contents {
    35  		env := keyEnv(*obj.Key)
    36  		if env != "" {
    37  			envs = append(envs, env)
    38  		}
    39  	}
    40  
    41  	sort.Strings(envs)
    42  	envs = append([]string{backend.DefaultStateName}, envs...)
    43  	return envs, nil
    44  }
    45  
    46  // extract the env name from the S3 key
    47  func keyEnv(key string) string {
    48  	parts := strings.Split(key, "/")
    49  	if len(parts) < 3 {
    50  		// no env here
    51  		return ""
    52  	}
    53  
    54  	if parts[0] != keyEnvPrefix {
    55  		// not our key, so ignore
    56  		return ""
    57  	}
    58  
    59  	return parts[1]
    60  }
    61  
    62  func (b *Backend) DeleteState(name string) error {
    63  	if name == backend.DefaultStateName || name == "" {
    64  		return fmt.Errorf("can't delete default state")
    65  	}
    66  
    67  	params := &s3.DeleteObjectInput{
    68  		Bucket: &b.bucketName,
    69  		Key:    aws.String(b.path(name)),
    70  	}
    71  
    72  	_, err := b.s3Client.DeleteObject(params)
    73  	if err != nil {
    74  		return err
    75  	}
    76  
    77  	return nil
    78  }
    79  
    80  func (b *Backend) State(name string) (state.State, error) {
    81  	client := &RemoteClient{
    82  		s3Client:             b.s3Client,
    83  		dynClient:            b.dynClient,
    84  		bucketName:           b.bucketName,
    85  		path:                 b.path(name),
    86  		serverSideEncryption: b.serverSideEncryption,
    87  		acl:                  b.acl,
    88  		kmsKeyID:             b.kmsKeyID,
    89  		lockTable:            b.lockTable,
    90  	}
    91  
    92  	stateMgr := &remote.State{Client: client}
    93  
    94  	//if this isn't the default state name, we need to create the object so
    95  	//it's listed by States.
    96  	if name != backend.DefaultStateName {
    97  		// take a lock on this state while we write it
    98  		lockInfo := state.NewLockInfo()
    99  		lockInfo.Operation = "init"
   100  		lockId, err := client.Lock(lockInfo)
   101  		if err != nil {
   102  			return nil, fmt.Errorf("failed to lock s3 state: %s", err)
   103  		}
   104  
   105  		// Local helper function so we can call it multiple places
   106  		lockUnlock := func(parent error) error {
   107  			if err := stateMgr.Unlock(lockId); err != nil {
   108  				return fmt.Errorf(strings.TrimSpace(errStateUnlock), lockId, err)
   109  			}
   110  			return parent
   111  		}
   112  
   113  		// Grab the value
   114  		if err := stateMgr.RefreshState(); err != nil {
   115  			err = lockUnlock(err)
   116  			return nil, err
   117  		}
   118  
   119  		// If we have no state, we have to create an empty state
   120  		if v := stateMgr.State(); v == nil {
   121  			if err := stateMgr.WriteState(terraform.NewState()); err != nil {
   122  				err = lockUnlock(err)
   123  				return nil, err
   124  			}
   125  			if err := stateMgr.PersistState(); err != nil {
   126  				err = lockUnlock(err)
   127  				return nil, err
   128  			}
   129  		}
   130  
   131  		// Unlock, the state should now be initialized
   132  		if err := lockUnlock(nil); err != nil {
   133  			return nil, err
   134  		}
   135  
   136  	}
   137  
   138  	return stateMgr, nil
   139  }
   140  
   141  func (b *Backend) client() *RemoteClient {
   142  	return &RemoteClient{}
   143  }
   144  
   145  func (b *Backend) path(name string) string {
   146  	if name == backend.DefaultStateName {
   147  		return b.keyName
   148  	}
   149  
   150  	return strings.Join([]string{keyEnvPrefix, name, b.keyName}, "/")
   151  }
   152  
   153  const errStateUnlock = `
   154  Error unlocking S3 state. Lock ID: %s
   155  
   156  Error: %s
   157  
   158  You may have to force-unlock this state in order to use it again.
   159  `