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