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 }