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