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  }