github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/backend/remote-state/gcs/backend_state.go (about)

     1  package gcs
     2  
     3  import (
     4  	"fmt"
     5  	"path"
     6  	"sort"
     7  	"strings"
     8  
     9  	"cloud.google.com/go/storage"
    10  	"google.golang.org/api/iterator"
    11  
    12  	"github.com/hashicorp/terraform/internal/backend"
    13  	"github.com/hashicorp/terraform/internal/states"
    14  	"github.com/hashicorp/terraform/internal/states/remote"
    15  	"github.com/hashicorp/terraform/internal/states/statemgr"
    16  )
    17  
    18  const (
    19  	stateFileSuffix = ".tfstate"
    20  	lockFileSuffix  = ".tflock"
    21  )
    22  
    23  // Workspaces returns a list of names for the workspaces found on GCS. The default
    24  // state is always returned as the first element in the slice.
    25  func (b *Backend) Workspaces() ([]string, error) {
    26  	states := []string{backend.DefaultStateName}
    27  
    28  	bucket := b.storageClient.Bucket(b.bucketName)
    29  	objs := bucket.Objects(b.storageContext, &storage.Query{
    30  		Delimiter: "/",
    31  		Prefix:    b.prefix,
    32  	})
    33  	for {
    34  		attrs, err := objs.Next()
    35  		if err == iterator.Done {
    36  			break
    37  		}
    38  		if err != nil {
    39  			return nil, fmt.Errorf("querying Cloud Storage failed: %v", err)
    40  		}
    41  
    42  		name := path.Base(attrs.Name)
    43  		if !strings.HasSuffix(name, stateFileSuffix) {
    44  			continue
    45  		}
    46  		st := strings.TrimSuffix(name, stateFileSuffix)
    47  
    48  		if st != backend.DefaultStateName {
    49  			states = append(states, st)
    50  		}
    51  	}
    52  
    53  	sort.Strings(states[1:])
    54  	return states, nil
    55  }
    56  
    57  // DeleteWorkspace deletes the named workspaces. The "default" state cannot be deleted.
    58  func (b *Backend) DeleteWorkspace(name string, _ bool) error {
    59  	if name == backend.DefaultStateName {
    60  		return fmt.Errorf("cowardly refusing to delete the %q state", name)
    61  	}
    62  
    63  	c, err := b.client(name)
    64  	if err != nil {
    65  		return err
    66  	}
    67  
    68  	return c.Delete()
    69  }
    70  
    71  // client returns a remoteClient for the named state.
    72  func (b *Backend) client(name string) (*remoteClient, error) {
    73  	if name == "" {
    74  		return nil, fmt.Errorf("%q is not a valid state name", name)
    75  	}
    76  
    77  	return &remoteClient{
    78  		storageContext: b.storageContext,
    79  		storageClient:  b.storageClient,
    80  		bucketName:     b.bucketName,
    81  		stateFilePath:  b.stateFile(name),
    82  		lockFilePath:   b.lockFile(name),
    83  		encryptionKey:  b.encryptionKey,
    84  		kmsKeyName:     b.kmsKeyName,
    85  	}, nil
    86  }
    87  
    88  // StateMgr reads and returns the named state from GCS. If the named state does
    89  // not yet exist, a new state file is created.
    90  func (b *Backend) StateMgr(name string) (statemgr.Full, error) {
    91  	c, err := b.client(name)
    92  	if err != nil {
    93  		return nil, err
    94  	}
    95  
    96  	st := &remote.State{Client: c}
    97  
    98  	// Grab the value
    99  	if err := st.RefreshState(); err != nil {
   100  		return nil, err
   101  	}
   102  
   103  	// If we have no state, we have to create an empty state
   104  	if v := st.State(); v == nil {
   105  
   106  		lockInfo := statemgr.NewLockInfo()
   107  		lockInfo.Operation = "init"
   108  		lockID, err := st.Lock(lockInfo)
   109  		if err != nil {
   110  			return nil, err
   111  		}
   112  
   113  		// Local helper function so we can call it multiple places
   114  		unlock := func(baseErr error) error {
   115  			if err := st.Unlock(lockID); err != nil {
   116  				const unlockErrMsg = `%v
   117  				Additionally, unlocking the state file on Google Cloud Storage failed:
   118  
   119  				Error message: %q
   120  				Lock ID (gen): %v
   121  				Lock file URL: %v
   122  
   123  				You may have to force-unlock this state in order to use it again.
   124  				The GCloud backend acquires a lock during initialization to ensure
   125  				the initial state file is created.`
   126  				return fmt.Errorf(unlockErrMsg, baseErr, err.Error(), lockID, c.lockFileURL())
   127  			}
   128  
   129  			return baseErr
   130  		}
   131  
   132  		if err := st.WriteState(states.NewState()); err != nil {
   133  			return nil, unlock(err)
   134  		}
   135  		if err := st.PersistState(nil); err != nil {
   136  			return nil, unlock(err)
   137  		}
   138  
   139  		// Unlock, the state should now be initialized
   140  		if err := unlock(nil); err != nil {
   141  			return nil, err
   142  		}
   143  
   144  	}
   145  
   146  	return st, nil
   147  }
   148  
   149  func (b *Backend) stateFile(name string) string {
   150  	return path.Join(b.prefix, name+stateFileSuffix)
   151  }
   152  
   153  func (b *Backend) lockFile(name string) string {
   154  	return path.Join(b.prefix, name+lockFileSuffix)
   155  }