github.com/pgray/terraform@v0.5.4-0.20170822184730-b6a464c5214d/backend/remote-state/consul/backend_state.go (about)

     1  package consul
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  
     7  	"github.com/hashicorp/terraform/backend"
     8  	"github.com/hashicorp/terraform/state"
     9  	"github.com/hashicorp/terraform/state/remote"
    10  	"github.com/hashicorp/terraform/terraform"
    11  )
    12  
    13  const (
    14  	keyEnvPrefix = "-env:"
    15  )
    16  
    17  func (b *Backend) States() ([]string, error) {
    18  	// Get the Consul client
    19  	client, err := b.clientRaw()
    20  	if err != nil {
    21  		return nil, err
    22  	}
    23  
    24  	// List our raw path
    25  	prefix := b.configData.Get("path").(string) + keyEnvPrefix
    26  	keys, _, err := client.KV().Keys(prefix, "/", nil)
    27  	if err != nil {
    28  		return nil, err
    29  	}
    30  
    31  	// Find the envs, we use a map since we can get duplicates with
    32  	// path suffixes.
    33  	envs := map[string]struct{}{}
    34  	for _, key := range keys {
    35  		// Consul should ensure this but it doesn't hurt to check again
    36  		if strings.HasPrefix(key, prefix) {
    37  			key = strings.TrimPrefix(key, prefix)
    38  
    39  			// Ignore anything with a "/" in it since we store the state
    40  			// directly in a key not a directory.
    41  			if idx := strings.IndexRune(key, '/'); idx >= 0 {
    42  				continue
    43  			}
    44  
    45  			envs[key] = struct{}{}
    46  		}
    47  	}
    48  
    49  	result := make([]string, 1, len(envs)+1)
    50  	result[0] = backend.DefaultStateName
    51  	for k, _ := range envs {
    52  		result = append(result, k)
    53  	}
    54  
    55  	return result, nil
    56  }
    57  
    58  func (b *Backend) DeleteState(name string) error {
    59  	if name == backend.DefaultStateName || name == "" {
    60  		return fmt.Errorf("can't delete default state")
    61  	}
    62  
    63  	// Get the Consul API client
    64  	client, err := b.clientRaw()
    65  	if err != nil {
    66  		return err
    67  	}
    68  
    69  	// Determine the path of the data
    70  	path := b.path(name)
    71  
    72  	// Delete it. We just delete it without any locking since
    73  	// the DeleteState API is documented as such.
    74  	_, err = client.KV().Delete(path, nil)
    75  	return err
    76  }
    77  
    78  func (b *Backend) State(name string) (state.State, error) {
    79  	// Get the Consul API client
    80  	client, err := b.clientRaw()
    81  	if err != nil {
    82  		return nil, err
    83  	}
    84  
    85  	// Determine the path of the data
    86  	path := b.path(name)
    87  
    88  	// Determine whether to gzip or not
    89  	gzip := b.configData.Get("gzip").(bool)
    90  
    91  	// Build the state client
    92  	var stateMgr state.State = &remote.State{
    93  		Client: &RemoteClient{
    94  			Client:    client,
    95  			Path:      path,
    96  			GZip:      gzip,
    97  			lockState: b.lock,
    98  		},
    99  	}
   100  
   101  	// If we're not locking, disable it
   102  	if !b.lock {
   103  		stateMgr = &state.LockDisabled{Inner: stateMgr}
   104  	}
   105  
   106  	// Grab a lock, we use this to write an empty state if one doesn't
   107  	// exist already. We have to write an empty state as a sentinel value
   108  	// so States() knows it exists.
   109  	lockInfo := state.NewLockInfo()
   110  	lockInfo.Operation = "init"
   111  	lockId, err := stateMgr.Lock(lockInfo)
   112  	if err != nil {
   113  		return nil, fmt.Errorf("failed to lock state in Consul: %s", err)
   114  	}
   115  
   116  	// Local helper function so we can call it multiple places
   117  	lockUnlock := func(parent error) error {
   118  		if err := stateMgr.Unlock(lockId); err != nil {
   119  			return fmt.Errorf(strings.TrimSpace(errStateUnlock), lockId, err)
   120  		}
   121  
   122  		return parent
   123  	}
   124  
   125  	// Grab the value
   126  	if err := stateMgr.RefreshState(); err != nil {
   127  		err = lockUnlock(err)
   128  		return nil, err
   129  	}
   130  
   131  	// If we have no state, we have to create an empty state
   132  	if v := stateMgr.State(); v == nil {
   133  		if err := stateMgr.WriteState(terraform.NewState()); err != nil {
   134  			err = lockUnlock(err)
   135  			return nil, err
   136  		}
   137  		if err := stateMgr.PersistState(); err != nil {
   138  			err = lockUnlock(err)
   139  			return nil, err
   140  		}
   141  	}
   142  
   143  	// Unlock, the state should now be initialized
   144  	if err := lockUnlock(nil); err != nil {
   145  		return nil, err
   146  	}
   147  
   148  	return stateMgr, nil
   149  }
   150  
   151  func (b *Backend) path(name string) string {
   152  	path := b.configData.Get("path").(string)
   153  	if name != backend.DefaultStateName {
   154  		path += fmt.Sprintf("%s%s", keyEnvPrefix, name)
   155  	}
   156  
   157  	return path
   158  }
   159  
   160  const errStateUnlock = `
   161  Error unlocking Consul state. Lock ID: %s
   162  
   163  Error: %s
   164  
   165  You may have to force-unlock this state in order to use it again.
   166  The Consul backend acquires a lock during initialization to ensure
   167  the minimum required key/values are prepared.
   168  `