github.com/hugorut/terraform@v1.1.3/src/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/hugorut/terraform/src/backend"
    13  	"github.com/hugorut/terraform/src/states"
    14  	"github.com/hugorut/terraform/src/states/remote"
    15  	"github.com/hugorut/terraform/src/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) 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  	}, nil
    85  }
    86  
    87  // StateMgr reads and returns the named state from GCS. If the named state does
    88  // not yet exist, a new state file is created.
    89  func (b *Backend) StateMgr(name string) (statemgr.Full, error) {
    90  	c, err := b.client(name)
    91  	if err != nil {
    92  		return nil, err
    93  	}
    94  
    95  	st := &remote.State{Client: c}
    96  
    97  	// Grab the value
    98  	if err := st.RefreshState(); err != nil {
    99  		return nil, err
   100  	}
   101  
   102  	// If we have no state, we have to create an empty state
   103  	if v := st.State(); v == nil {
   104  
   105  		lockInfo := statemgr.NewLockInfo()
   106  		lockInfo.Operation = "init"
   107  		lockID, err := st.Lock(lockInfo)
   108  		if err != nil {
   109  			return nil, err
   110  		}
   111  
   112  		// Local helper function so we can call it multiple places
   113  		unlock := func(baseErr error) error {
   114  			if err := st.Unlock(lockID); err != nil {
   115  				const unlockErrMsg = `%v
   116  				Additionally, unlocking the state file on Google Cloud Storage failed:
   117  
   118  				Error message: %q
   119  				Lock ID (gen): %v
   120  				Lock file URL: %v
   121  
   122  				You may have to force-unlock this state in order to use it again.
   123  				The GCloud backend acquires a lock during initialization to ensure
   124  				the initial state file is created.`
   125  				return fmt.Errorf(unlockErrMsg, baseErr, err.Error(), lockID, c.lockFileURL())
   126  			}
   127  
   128  			return baseErr
   129  		}
   130  
   131  		if err := st.WriteState(states.NewState()); err != nil {
   132  			return nil, unlock(err)
   133  		}
   134  		if err := st.PersistState(); err != nil {
   135  			return nil, unlock(err)
   136  		}
   137  
   138  		// Unlock, the state should now be initialized
   139  		if err := unlock(nil); err != nil {
   140  			return nil, err
   141  		}
   142  
   143  	}
   144  
   145  	return st, nil
   146  }
   147  
   148  func (b *Backend) stateFile(name string) string {
   149  	return path.Join(b.prefix, name+stateFileSuffix)
   150  }
   151  
   152  func (b *Backend) lockFile(name string) string {
   153  	return path.Join(b.prefix, name+lockFileSuffix)
   154  }