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  }