github.com/ctrox/terraform@v0.11.12-beta1/backend/remote/backend_state.go (about)

     1  package remote
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"crypto/md5"
     7  	"encoding/base64"
     8  	"fmt"
     9  
    10  	tfe "github.com/hashicorp/go-tfe"
    11  	"github.com/hashicorp/terraform/state"
    12  	"github.com/hashicorp/terraform/state/remote"
    13  	"github.com/hashicorp/terraform/terraform"
    14  )
    15  
    16  type remoteClient struct {
    17  	client       *tfe.Client
    18  	lockInfo     *state.LockInfo
    19  	organization string
    20  	runID        string
    21  	workspace    *tfe.Workspace
    22  }
    23  
    24  // Get the remote state.
    25  func (r *remoteClient) Get() (*remote.Payload, error) {
    26  	ctx := context.Background()
    27  
    28  	sv, err := r.client.StateVersions.Current(ctx, r.workspace.ID)
    29  	if err != nil {
    30  		if err == tfe.ErrResourceNotFound {
    31  			// If no state exists, then return nil.
    32  			return nil, nil
    33  		}
    34  		return nil, fmt.Errorf("Error retrieving remote state: %v", err)
    35  	}
    36  
    37  	state, err := r.client.StateVersions.Download(ctx, sv.DownloadURL)
    38  	if err != nil {
    39  		return nil, fmt.Errorf("Error downloading remote state: %v", err)
    40  	}
    41  
    42  	// If the state is empty, then return nil.
    43  	if len(state) == 0 {
    44  		return nil, nil
    45  	}
    46  
    47  	// Get the MD5 checksum of the state.
    48  	sum := md5.Sum(state)
    49  
    50  	return &remote.Payload{
    51  		Data: state,
    52  		MD5:  sum[:],
    53  	}, nil
    54  }
    55  
    56  // Put the remote state.
    57  func (r *remoteClient) Put(state []byte) error {
    58  	ctx := context.Background()
    59  
    60  	// Read the raw state into a Terraform state.
    61  	tfState, err := terraform.ReadState(bytes.NewReader(state))
    62  	if err != nil {
    63  		return fmt.Errorf("Error reading state: %s", err)
    64  	}
    65  
    66  	options := tfe.StateVersionCreateOptions{
    67  		Lineage: tfe.String(tfState.Lineage),
    68  		Serial:  tfe.Int64(tfState.Serial),
    69  		MD5:     tfe.String(fmt.Sprintf("%x", md5.Sum(state))),
    70  		State:   tfe.String(base64.StdEncoding.EncodeToString(state)),
    71  	}
    72  
    73  	// If we have a run ID, make sure to add it to the options
    74  	// so the state will be properly associated with the run.
    75  	if r.runID != "" {
    76  		options.Run = &tfe.Run{ID: r.runID}
    77  	}
    78  
    79  	// Create the new state.
    80  	_, err = r.client.StateVersions.Create(ctx, r.workspace.ID, options)
    81  	if err != nil {
    82  		return fmt.Errorf("Error creating remote state: %v", err)
    83  	}
    84  
    85  	return nil
    86  }
    87  
    88  // Delete the remote state.
    89  func (r *remoteClient) Delete() error {
    90  	err := r.client.Workspaces.Delete(context.Background(), r.organization, r.workspace.Name)
    91  	if err != nil && err != tfe.ErrResourceNotFound {
    92  		return fmt.Errorf("Error deleting workspace %s: %v", r.workspace.Name, err)
    93  	}
    94  
    95  	return nil
    96  }
    97  
    98  // Lock the remote state.
    99  func (r *remoteClient) Lock(info *state.LockInfo) (string, error) {
   100  	ctx := context.Background()
   101  
   102  	lockErr := &state.LockError{Info: r.lockInfo}
   103  
   104  	// Lock the workspace.
   105  	_, err := r.client.Workspaces.Lock(ctx, r.workspace.ID, tfe.WorkspaceLockOptions{
   106  		Reason: tfe.String("Locked by Terraform"),
   107  	})
   108  	if err != nil {
   109  		lockErr.Err = err
   110  		return "", lockErr
   111  	}
   112  
   113  	r.lockInfo = info
   114  
   115  	return r.lockInfo.ID, nil
   116  }
   117  
   118  // Unlock the remote state.
   119  func (r *remoteClient) Unlock(id string) error {
   120  	ctx := context.Background()
   121  
   122  	lockErr := &state.LockError{Info: r.lockInfo}
   123  
   124  	// With lock info this should be treated as a normal unlock.
   125  	if r.lockInfo != nil {
   126  		// Verify the expected lock ID.
   127  		if r.lockInfo.ID != id {
   128  			lockErr.Err = fmt.Errorf("lock ID does not match existing lock")
   129  			return lockErr
   130  		}
   131  
   132  		// Unlock the workspace.
   133  		_, err := r.client.Workspaces.Unlock(ctx, r.workspace.ID)
   134  		if err != nil {
   135  			lockErr.Err = err
   136  			return lockErr
   137  		}
   138  
   139  		return nil
   140  	}
   141  
   142  	// Verify the optional force-unlock lock ID.
   143  	if r.organization+"/"+r.workspace.Name != id {
   144  		lockErr.Err = fmt.Errorf("lock ID does not match existing lock")
   145  		return lockErr
   146  	}
   147  
   148  	// Force unlock the workspace.
   149  	_, err := r.client.Workspaces.ForceUnlock(ctx, r.workspace.ID)
   150  	if err != nil {
   151  		lockErr.Err = err
   152  		return lockErr
   153  	}
   154  
   155  	return nil
   156  }