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