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

     1  package azure
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/base64"
     6  	"encoding/json"
     7  	"fmt"
     8  	"io"
     9  	"log"
    10  
    11  	"github.com/Azure/azure-sdk-for-go/storage"
    12  	"github.com/hashicorp/go-multierror"
    13  	"github.com/hashicorp/go-uuid"
    14  	"github.com/hashicorp/terraform/state"
    15  	"github.com/hashicorp/terraform/state/remote"
    16  	"github.com/hashicorp/terraform/states"
    17  )
    18  
    19  const (
    20  	leaseHeader = "x-ms-lease-id"
    21  	// Must be lower case
    22  	lockInfoMetaKey = "terraformlockid"
    23  )
    24  
    25  type RemoteClient struct {
    26  	blobClient    storage.BlobStorageClient
    27  	containerName string
    28  	keyName       string
    29  	leaseID       string
    30  }
    31  
    32  func (c *RemoteClient) Get() (*remote.Payload, error) {
    33  	containerReference := c.blobClient.GetContainerReference(c.containerName)
    34  	blobReference := containerReference.GetBlobReference(c.keyName)
    35  	options := &storage.GetBlobOptions{}
    36  
    37  	if c.leaseID != "" {
    38  		options.LeaseID = c.leaseID
    39  	}
    40  
    41  	blob, err := blobReference.Get(options)
    42  	if err != nil {
    43  		if storErr, ok := err.(storage.AzureStorageServiceError); ok {
    44  			if storErr.Code == "BlobNotFound" {
    45  				return nil, nil
    46  			}
    47  		}
    48  		return nil, err
    49  	}
    50  
    51  	defer blob.Close()
    52  
    53  	buf := bytes.NewBuffer(nil)
    54  	if _, err := io.Copy(buf, blob); err != nil {
    55  		return nil, fmt.Errorf("Failed to read remote state: %s", err)
    56  	}
    57  
    58  	payload := &remote.Payload{
    59  		Data: buf.Bytes(),
    60  	}
    61  
    62  	// If there was no data, then return nil
    63  	if len(payload.Data) == 0 {
    64  		return nil, nil
    65  	}
    66  
    67  	return payload, nil
    68  }
    69  
    70  func (c *RemoteClient) Put(data []byte) error {
    71  	getOptions := &storage.GetBlobMetadataOptions{}
    72  	setOptions := &storage.SetBlobPropertiesOptions{}
    73  	putOptions := &storage.PutBlobOptions{}
    74  
    75  	containerReference := c.blobClient.GetContainerReference(c.containerName)
    76  	blobReference := containerReference.GetBlobReference(c.keyName)
    77  
    78  	blobReference.Properties.ContentType = "application/json"
    79  	blobReference.Properties.ContentLength = int64(len(data))
    80  
    81  	if c.leaseID != "" {
    82  		getOptions.LeaseID = c.leaseID
    83  		setOptions.LeaseID = c.leaseID
    84  		putOptions.LeaseID = c.leaseID
    85  	}
    86  
    87  	exists, err := blobReference.Exists()
    88  	if err != nil {
    89  		return err
    90  	}
    91  
    92  	if exists {
    93  		err = blobReference.GetMetadata(getOptions)
    94  		if err != nil {
    95  			return err
    96  		}
    97  	}
    98  
    99  	reader := bytes.NewReader(data)
   100  
   101  	err = blobReference.CreateBlockBlobFromReader(reader, putOptions)
   102  	if err != nil {
   103  		return err
   104  	}
   105  
   106  	return blobReference.SetProperties(setOptions)
   107  }
   108  
   109  func (c *RemoteClient) Delete() error {
   110  	containerReference := c.blobClient.GetContainerReference(c.containerName)
   111  	blobReference := containerReference.GetBlobReference(c.keyName)
   112  	options := &storage.DeleteBlobOptions{}
   113  
   114  	if c.leaseID != "" {
   115  		options.LeaseID = c.leaseID
   116  	}
   117  
   118  	return blobReference.Delete(options)
   119  }
   120  
   121  func (c *RemoteClient) Lock(info *state.LockInfo) (string, error) {
   122  	stateName := fmt.Sprintf("%s/%s", c.containerName, c.keyName)
   123  	info.Path = stateName
   124  
   125  	if info.ID == "" {
   126  		lockID, err := uuid.GenerateUUID()
   127  		if err != nil {
   128  			return "", err
   129  		}
   130  
   131  		info.ID = lockID
   132  	}
   133  
   134  	getLockInfoErr := func(err error) error {
   135  		lockInfo, infoErr := c.getLockInfo()
   136  		if infoErr != nil {
   137  			err = multierror.Append(err, infoErr)
   138  		}
   139  
   140  		return &state.LockError{
   141  			Err:  err,
   142  			Info: lockInfo,
   143  		}
   144  	}
   145  
   146  	containerReference := c.blobClient.GetContainerReference(c.containerName)
   147  	blobReference := containerReference.GetBlobReference(c.keyName)
   148  	leaseID, err := blobReference.AcquireLease(-1, info.ID, &storage.LeaseOptions{})
   149  	if err != nil {
   150  		if storErr, ok := err.(storage.AzureStorageServiceError); ok && storErr.Code != "BlobNotFound" {
   151  			return "", getLockInfoErr(err)
   152  		}
   153  
   154  		// failed to lock as there was no state blob, write empty state
   155  		stateMgr := &remote.State{Client: c}
   156  
   157  		// ensure state is actually empty
   158  		if err := stateMgr.RefreshState(); err != nil {
   159  			return "", fmt.Errorf("Failed to refresh state before writing empty state for locking: %s", err)
   160  		}
   161  
   162  		log.Print("[DEBUG] Could not lock as state blob did not exist, creating with empty state")
   163  
   164  		if v := stateMgr.State(); v == nil {
   165  			if err := stateMgr.WriteState(states.NewState()); err != nil {
   166  				return "", fmt.Errorf("Failed to write empty state for locking: %s", err)
   167  			}
   168  			if err := stateMgr.PersistState(); err != nil {
   169  				return "", fmt.Errorf("Failed to persist empty state for locking: %s", err)
   170  			}
   171  		}
   172  
   173  		leaseID, err = blobReference.AcquireLease(-1, info.ID, &storage.LeaseOptions{})
   174  		if err != nil {
   175  			return "", getLockInfoErr(err)
   176  		}
   177  	}
   178  
   179  	info.ID = leaseID
   180  	c.leaseID = leaseID
   181  
   182  	if err := c.writeLockInfo(info); err != nil {
   183  		return "", err
   184  	}
   185  
   186  	return info.ID, nil
   187  }
   188  
   189  func (c *RemoteClient) getLockInfo() (*state.LockInfo, error) {
   190  	containerReference := c.blobClient.GetContainerReference(c.containerName)
   191  	blobReference := containerReference.GetBlobReference(c.keyName)
   192  	err := blobReference.GetMetadata(&storage.GetBlobMetadataOptions{})
   193  	if err != nil {
   194  		return nil, err
   195  	}
   196  
   197  	raw := blobReference.Metadata[lockInfoMetaKey]
   198  	if raw == "" {
   199  		return nil, fmt.Errorf("blob metadata %q was empty", lockInfoMetaKey)
   200  	}
   201  
   202  	data, err := base64.StdEncoding.DecodeString(raw)
   203  	if err != nil {
   204  		return nil, err
   205  	}
   206  
   207  	lockInfo := &state.LockInfo{}
   208  	err = json.Unmarshal(data, lockInfo)
   209  	if err != nil {
   210  		return nil, err
   211  	}
   212  
   213  	return lockInfo, nil
   214  }
   215  
   216  // writes info to blob meta data, deletes metadata entry if info is nil
   217  func (c *RemoteClient) writeLockInfo(info *state.LockInfo) error {
   218  	containerReference := c.blobClient.GetContainerReference(c.containerName)
   219  	blobReference := containerReference.GetBlobReference(c.keyName)
   220  	err := blobReference.GetMetadata(&storage.GetBlobMetadataOptions{
   221  		LeaseID: c.leaseID,
   222  	})
   223  	if err != nil {
   224  		return err
   225  	}
   226  
   227  	if info == nil {
   228  		delete(blobReference.Metadata, lockInfoMetaKey)
   229  	} else {
   230  		value := base64.StdEncoding.EncodeToString(info.Marshal())
   231  		blobReference.Metadata[lockInfoMetaKey] = value
   232  	}
   233  
   234  	opts := &storage.SetBlobMetadataOptions{
   235  		LeaseID: c.leaseID,
   236  	}
   237  	return blobReference.SetMetadata(opts)
   238  }
   239  
   240  func (c *RemoteClient) Unlock(id string) error {
   241  	lockErr := &state.LockError{}
   242  
   243  	lockInfo, err := c.getLockInfo()
   244  	if err != nil {
   245  		lockErr.Err = fmt.Errorf("failed to retrieve lock info: %s", err)
   246  		return lockErr
   247  	}
   248  	lockErr.Info = lockInfo
   249  
   250  	if lockInfo.ID != id {
   251  		lockErr.Err = fmt.Errorf("lock id %q does not match existing lock", id)
   252  		return lockErr
   253  	}
   254  
   255  	c.leaseID = lockInfo.ID
   256  	if err := c.writeLockInfo(nil); err != nil {
   257  		lockErr.Err = fmt.Errorf("failed to delete lock info from metadata: %s", err)
   258  		return lockErr
   259  	}
   260  
   261  	containerReference := c.blobClient.GetContainerReference(c.containerName)
   262  	blobReference := containerReference.GetBlobReference(c.keyName)
   263  	err = blobReference.ReleaseLease(id, &storage.LeaseOptions{})
   264  	if err != nil {
   265  		lockErr.Err = err
   266  		return lockErr
   267  	}
   268  
   269  	c.leaseID = ""
   270  
   271  	return nil
   272  }