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