github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/backend/remote-state/swift/backend_state.go (about)

     1  package swift
     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  )
    12  
    13  const (
    14  	objectEnvPrefix = "env-"
    15  	delimiter       = "/"
    16  )
    17  
    18  func (b *Backend) Workspaces() ([]string, error) {
    19  	client := &RemoteClient{
    20  		client:           b.client,
    21  		container:        b.container,
    22  		archive:          b.archive,
    23  		archiveContainer: b.archiveContainer,
    24  		expireSecs:       b.expireSecs,
    25  		lockState:        b.lock,
    26  	}
    27  
    28  	// List our container objects
    29  	objectNames, err := client.ListObjectsNames(objectEnvPrefix, delimiter)
    30  
    31  	if err != nil {
    32  		return nil, err
    33  	}
    34  
    35  	// Find the envs, we use a map since we can get duplicates with
    36  	// path suffixes.
    37  	envs := map[string]struct{}{}
    38  	for _, object := range objectNames {
    39  		object = strings.TrimPrefix(object, objectEnvPrefix)
    40  		object = strings.TrimSuffix(object, delimiter)
    41  
    42  		// Ignore objects that still contain a "/"
    43  		// as we dont store states in subdirectories
    44  		if idx := strings.Index(object, delimiter); idx >= 0 {
    45  			continue
    46  		}
    47  
    48  		// swift is eventually consistent, thus a deleted object may
    49  		// be listed in objectList. To ensure consistency, we query
    50  		// each object  with a "newest" arg set to true
    51  		payload, err := client.get(b.objectName(object))
    52  		if err != nil {
    53  			return nil, err
    54  		}
    55  		if payload == nil {
    56  			// object doesn't exist anymore. skipping.
    57  			continue
    58  		}
    59  
    60  		envs[object] = struct{}{}
    61  	}
    62  
    63  	result := make([]string, 1, len(envs)+1)
    64  	result[0] = backend.DefaultStateName
    65  
    66  	for k, _ := range envs {
    67  		result = append(result, k)
    68  	}
    69  
    70  	return result, nil
    71  }
    72  
    73  func (b *Backend) DeleteWorkspace(name string) error {
    74  	if name == backend.DefaultStateName || name == "" {
    75  		return fmt.Errorf("can't delete default state")
    76  	}
    77  
    78  	client := &RemoteClient{
    79  		client:           b.client,
    80  		container:        b.container,
    81  		archive:          b.archive,
    82  		archiveContainer: b.archiveContainer,
    83  		expireSecs:       b.expireSecs,
    84  		objectName:       b.objectName(name),
    85  		lockState:        b.lock,
    86  	}
    87  
    88  	// Delete our object
    89  	err := client.Delete()
    90  
    91  	return err
    92  }
    93  
    94  func (b *Backend) StateMgr(name string) (state.State, error) {
    95  	return b.stateMgr(name, true)
    96  }
    97  
    98  func (b *Backend) StateMgrWithoutCheckVersion(name string) (state.State, error) {
    99  	return b.stateMgr(name, false)
   100  }
   101  
   102  func (b *Backend) stateMgr(name string, checkVersion bool) (state.State, error) {
   103  	if name == "" {
   104  		return nil, fmt.Errorf("missing state name")
   105  	}
   106  
   107  	client := &RemoteClient{
   108  		client:           b.client,
   109  		container:        b.container,
   110  		archive:          b.archive,
   111  		archiveContainer: b.archiveContainer,
   112  		expireSecs:       b.expireSecs,
   113  		objectName:       b.objectName(name),
   114  		lockState:        b.lock,
   115  	}
   116  
   117  	var stateMgr state.State = &remote.State{Client: client}
   118  
   119  	// If we're not locking, disable it
   120  	if !b.lock {
   121  		stateMgr = &state.LockDisabled{Inner: stateMgr}
   122  	}
   123  
   124  	// Check to see if this state already exists.
   125  	// If we're trying to force-unlock a state, we can't take the lock before
   126  	// fetching the state. If the state doesn't exist, we have to assume this
   127  	// is a normal create operation, and take the lock at that point.
   128  	//
   129  	// If we need to force-unlock, but for some reason the state no longer
   130  	// exists, the user will have to use openstack tools to manually fix the
   131  	// situation.
   132  	existing, err := b.Workspaces()
   133  	if err != nil {
   134  		return nil, err
   135  	}
   136  
   137  	exists := false
   138  	for _, s := range existing {
   139  		if s == name {
   140  			exists = true
   141  			break
   142  		}
   143  	}
   144  
   145  	// We need to create the object so it's listed by States.
   146  	if !exists {
   147  		// the default state always exists
   148  		if name == backend.DefaultStateName {
   149  			return stateMgr, nil
   150  		}
   151  
   152  		// Grab a lock, we use this to write an empty state if one doesn't
   153  		// exist already. We have to write an empty state as a sentinel value
   154  		// so States() knows it exists.
   155  		lockInfo := state.NewLockInfo()
   156  		lockInfo.Operation = "init"
   157  		lockId, err := stateMgr.Lock(lockInfo)
   158  		if err != nil {
   159  			return nil, fmt.Errorf("failed to lock state in Swift: %s", err)
   160  		}
   161  
   162  		// Local helper function so we can call it multiple places
   163  		lockUnlock := func(parent error) error {
   164  			if err := stateMgr.Unlock(lockId); err != nil {
   165  				return fmt.Errorf(strings.TrimSpace(errStateUnlock), lockId, err)
   166  			}
   167  
   168  			return parent
   169  		}
   170  
   171  		// Grab the value
   172  		if checkVersion {
   173  			if err := stateMgr.RefreshState(); err != nil {
   174  				err = lockUnlock(err)
   175  				return nil, err
   176  			}
   177  		} else {
   178  			if err := stateMgr.RefreshStateWithoutCheckVersion(); err != nil {
   179  				err = lockUnlock(err)
   180  				return nil, err
   181  			}
   182  		}
   183  
   184  		// If we have no state, we have to create an empty state
   185  		if v := stateMgr.State(); v == nil {
   186  			if err := stateMgr.WriteState(states.NewState()); err != nil {
   187  				err = lockUnlock(err)
   188  				return nil, err
   189  			}
   190  			if err := stateMgr.PersistState(); err != nil {
   191  				err = lockUnlock(err)
   192  				return nil, err
   193  			}
   194  		}
   195  
   196  		// Unlock, the state should now be initialized
   197  		if err := lockUnlock(nil); err != nil {
   198  			return nil, err
   199  		}
   200  	}
   201  
   202  	return stateMgr, nil
   203  }
   204  
   205  func (b *Backend) objectName(name string) string {
   206  	if name != backend.DefaultStateName {
   207  		name = fmt.Sprintf("%s%s/%s", objectEnvPrefix, name, b.stateName)
   208  	} else {
   209  		name = b.stateName
   210  	}
   211  
   212  	return name
   213  }
   214  
   215  const errStateUnlock = `
   216  Error unlocking Swift state. Lock ID: %s
   217  
   218  Error: %s
   219  
   220  You may have to force-unlock this state in order to use it again.
   221  The Swift backend acquires a lock during initialization to ensure
   222  the minimum required keys are prepared.
   223  `