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