github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/backend/remote-state/azure/backend_state.go (about)

     1  package azure
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"sort"
     7  	"strings"
     8  
     9  	"github.com/Azure/azure-sdk-for-go/storage"
    10  	"github.com/hashicorp/terraform/backend"
    11  	"github.com/hashicorp/terraform/state"
    12  	"github.com/hashicorp/terraform/state/remote"
    13  	"github.com/hashicorp/terraform/states"
    14  )
    15  
    16  const (
    17  	// This will be used as directory name, the odd looking colon is simply to
    18  	// reduce the chance of name conflicts with existing objects.
    19  	keyEnvPrefix = "env:"
    20  )
    21  
    22  func (b *Backend) Workspaces() ([]string, error) {
    23  	prefix := b.keyName + keyEnvPrefix
    24  	params := storage.ListBlobsParameters{
    25  		Prefix: prefix,
    26  	}
    27  
    28  	ctx := context.TODO()
    29  	client, err := b.armClient.getBlobClient(ctx)
    30  	if err != nil {
    31  		return nil, err
    32  	}
    33  	container := client.GetContainerReference(b.containerName)
    34  	resp, err := container.ListBlobs(params)
    35  	if err != nil {
    36  		return nil, err
    37  	}
    38  
    39  	envs := map[string]struct{}{}
    40  	for _, obj := range resp.Blobs {
    41  		key := obj.Name
    42  		if strings.HasPrefix(key, prefix) {
    43  			name := strings.TrimPrefix(key, prefix)
    44  			// we store the state in a key, not a directory
    45  			if strings.Contains(name, "/") {
    46  				continue
    47  			}
    48  
    49  			envs[name] = struct{}{}
    50  		}
    51  	}
    52  
    53  	result := []string{backend.DefaultStateName}
    54  	for name := range envs {
    55  		result = append(result, name)
    56  	}
    57  	sort.Strings(result[1:])
    58  	return result, nil
    59  }
    60  
    61  func (b *Backend) DeleteWorkspace(name string) error {
    62  	if name == backend.DefaultStateName || name == "" {
    63  		return fmt.Errorf("can't delete default state")
    64  	}
    65  
    66  	ctx := context.TODO()
    67  	client, err := b.armClient.getBlobClient(ctx)
    68  	if err != nil {
    69  		return err
    70  	}
    71  
    72  	containerReference := client.GetContainerReference(b.containerName)
    73  	blobReference := containerReference.GetBlobReference(b.path(name))
    74  	options := &storage.DeleteBlobOptions{}
    75  
    76  	return blobReference.Delete(options)
    77  }
    78  
    79  func (b *Backend) StateMgr(name string) (state.State, error) {
    80  	return b.stateMgr(name, true)
    81  }
    82  
    83  func (b *Backend) StateMgrWithoutCheckVersion(name string) (state.State, error) {
    84  	return b.stateMgr(name, false)
    85  }
    86  
    87  func (b *Backend) stateMgr(name string, checkVersion bool) (state.State, error) {
    88  	ctx := context.TODO()
    89  	blobClient, err := b.armClient.getBlobClient(ctx)
    90  	if err != nil {
    91  		return nil, err
    92  	}
    93  
    94  	client := &RemoteClient{
    95  		blobClient:    *blobClient,
    96  		containerName: b.containerName,
    97  		keyName:       b.path(name),
    98  	}
    99  
   100  	stateMgr := &remote.State{Client: client}
   101  
   102  	//if this isn't the default state name, we need to create the object so
   103  	//it's listed by States.
   104  	if name != backend.DefaultStateName {
   105  		// take a lock on this state while we write it
   106  		lockInfo := state.NewLockInfo()
   107  		lockInfo.Operation = "init"
   108  		lockId, err := client.Lock(lockInfo)
   109  		if err != nil {
   110  			return nil, fmt.Errorf("failed to lock azure state: %s", err)
   111  		}
   112  
   113  		// Local helper function so we can call it multiple places
   114  		lockUnlock := func(parent error) error {
   115  			if err := stateMgr.Unlock(lockId); err != nil {
   116  				return fmt.Errorf(strings.TrimSpace(errStateUnlock), lockId, err)
   117  			}
   118  			return parent
   119  		}
   120  
   121  		// Grab the value
   122  		if checkVersion {
   123  			if err := stateMgr.RefreshState(); err != nil {
   124  				err = lockUnlock(err)
   125  				return nil, err
   126  			}
   127  		} else {
   128  			if err := stateMgr.RefreshStateWithoutCheckVersion(); err != nil {
   129  				err = lockUnlock(err)
   130  				return nil, err
   131  			}
   132  		}
   133  
   134  		// If we have no state, we have to create an empty state
   135  		if v := stateMgr.State(); v == nil {
   136  			if err := stateMgr.WriteState(states.NewState()); err != nil {
   137  				err = lockUnlock(err)
   138  				return nil, err
   139  			}
   140  			if err := stateMgr.PersistState(); err != nil {
   141  				err = lockUnlock(err)
   142  				return nil, err
   143  			}
   144  		}
   145  
   146  		// Unlock, the state should now be initialized
   147  		if err := lockUnlock(nil); err != nil {
   148  			return nil, err
   149  		}
   150  
   151  	}
   152  
   153  	return stateMgr, nil
   154  }
   155  
   156  func (b *Backend) client() *RemoteClient {
   157  	return &RemoteClient{}
   158  }
   159  
   160  func (b *Backend) path(name string) string {
   161  	if name == backend.DefaultStateName {
   162  		return b.keyName
   163  	}
   164  
   165  	return b.keyName + keyEnvPrefix + name
   166  }
   167  
   168  const errStateUnlock = `
   169  Error unlocking Azure state. Lock ID: %s
   170  
   171  Error: %s
   172  
   173  You may have to force-unlock this state in order to use it again.
   174  `