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