github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/backend/remote-state/pg/backend_state.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package pg 5 6 import ( 7 "fmt" 8 9 "github.com/terramate-io/tf/backend" 10 "github.com/terramate-io/tf/states" 11 "github.com/terramate-io/tf/states/remote" 12 "github.com/terramate-io/tf/states/statemgr" 13 ) 14 15 func (b *Backend) Workspaces() ([]string, error) { 16 query := `SELECT name FROM %s.%s WHERE name != 'default' ORDER BY name` 17 rows, err := b.db.Query(fmt.Sprintf(query, b.schemaName, statesTableName)) 18 if err != nil { 19 return nil, err 20 } 21 defer rows.Close() 22 23 result := []string{ 24 backend.DefaultStateName, 25 } 26 27 for rows.Next() { 28 var name string 29 if err := rows.Scan(&name); err != nil { 30 return nil, err 31 } 32 result = append(result, name) 33 } 34 if err := rows.Err(); err != nil { 35 return nil, err 36 } 37 38 return result, nil 39 } 40 41 func (b *Backend) DeleteWorkspace(name string, _ bool) error { 42 if name == backend.DefaultStateName || name == "" { 43 return fmt.Errorf("can't delete default state") 44 } 45 46 query := `DELETE FROM %s.%s WHERE name = $1` 47 _, err := b.db.Exec(fmt.Sprintf(query, b.schemaName, statesTableName), name) 48 if err != nil { 49 return err 50 } 51 52 return nil 53 } 54 55 func (b *Backend) StateMgr(name string) (statemgr.Full, error) { 56 // Build the state client 57 var stateMgr statemgr.Full = &remote.State{ 58 Client: &RemoteClient{ 59 Client: b.db, 60 Name: name, 61 SchemaName: b.schemaName, 62 }, 63 } 64 65 // Check to see if this state already exists. 66 // If the state doesn't exist, we have to assume this 67 // is a normal create operation, and take the lock at that point. 68 existing, err := b.Workspaces() 69 if err != nil { 70 return nil, err 71 } 72 73 exists := false 74 for _, s := range existing { 75 if s == name { 76 exists = true 77 break 78 } 79 } 80 81 // Grab a lock, we use this to write an empty state if one doesn't 82 // exist already. We have to write an empty state as a sentinel value 83 // so Workspaces() knows it exists. 84 if !exists { 85 lockInfo := statemgr.NewLockInfo() 86 lockInfo.Operation = "init" 87 lockId, err := stateMgr.Lock(lockInfo) 88 if err != nil { 89 return nil, fmt.Errorf("failed to lock state in Postgres: %s", err) 90 } 91 92 // Local helper function so we can call it multiple places 93 lockUnlock := func(parent error) error { 94 if err := stateMgr.Unlock(lockId); err != nil { 95 return fmt.Errorf(`error unlocking Postgres state: %s`, err) 96 } 97 return parent 98 } 99 100 if v := stateMgr.State(); v == nil { 101 if err := stateMgr.WriteState(states.NewState()); err != nil { 102 err = lockUnlock(err) 103 return nil, err 104 } 105 if err := stateMgr.PersistState(nil); err != nil { 106 err = lockUnlock(err) 107 return nil, err 108 } 109 } 110 111 // Unlock, the state should now be initialized 112 if err := lockUnlock(nil); err != nil { 113 return nil, err 114 } 115 } 116 117 return stateMgr, nil 118 }