github.com/svenhamers/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 }