github.com/codeherentuk/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 }