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