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