github.com/rvichery/terraform@v0.11.10/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 string 22 } 23 24 // Get the remote state. 25 func (r *remoteClient) Get() (*remote.Payload, error) { 26 ctx := context.Background() 27 28 // Retrieve the workspace for which to create a new state. 29 w, err := r.client.Workspaces.Read(ctx, r.organization, r.workspace) 30 if err != nil { 31 if err == tfe.ErrResourceNotFound { 32 // If no state exists, then return nil. 33 return nil, nil 34 } 35 return nil, fmt.Errorf("Error retrieving workspace: %v", err) 36 } 37 38 sv, err := r.client.StateVersions.Current(ctx, w.ID) 39 if err != nil { 40 if err == tfe.ErrResourceNotFound { 41 // If no state exists, then return nil. 42 return nil, nil 43 } 44 return nil, fmt.Errorf("Error retrieving remote state: %v", err) 45 } 46 47 state, err := r.client.StateVersions.Download(ctx, sv.DownloadURL) 48 if err != nil { 49 return nil, fmt.Errorf("Error downloading remote state: %v", err) 50 } 51 52 // If the state is empty, then return nil. 53 if len(state) == 0 { 54 return nil, nil 55 } 56 57 // Get the MD5 checksum of the state. 58 sum := md5.Sum(state) 59 60 return &remote.Payload{ 61 Data: state, 62 MD5: sum[:], 63 }, nil 64 } 65 66 // Put the remote state. 67 func (r *remoteClient) Put(state []byte) error { 68 ctx := context.Background() 69 70 // Retrieve the workspace for which to create a new state. 71 w, err := r.client.Workspaces.Read(ctx, r.organization, r.workspace) 72 if err != nil { 73 return fmt.Errorf("Error retrieving workspace: %v", err) 74 } 75 76 // Read the raw state into a Terraform state. 77 tfState, err := terraform.ReadState(bytes.NewReader(state)) 78 if err != nil { 79 return fmt.Errorf("Error reading state: %s", err) 80 } 81 82 options := tfe.StateVersionCreateOptions{ 83 Lineage: tfe.String(tfState.Lineage), 84 Serial: tfe.Int64(tfState.Serial), 85 MD5: tfe.String(fmt.Sprintf("%x", md5.Sum(state))), 86 State: tfe.String(base64.StdEncoding.EncodeToString(state)), 87 } 88 89 // If we have a run ID, make sure to add it to the options 90 // so the state will be properly associated with the run. 91 if r.runID != "" { 92 options.Run = &tfe.Run{ID: r.runID} 93 } 94 95 // Create the new state. 96 _, err = r.client.StateVersions.Create(ctx, w.ID, options) 97 if err != nil { 98 return fmt.Errorf("Error creating remote state: %v", err) 99 } 100 101 return nil 102 } 103 104 // Delete the remote state. 105 func (r *remoteClient) Delete() error { 106 err := r.client.Workspaces.Delete(context.Background(), r.organization, r.workspace) 107 if err != nil && err != tfe.ErrResourceNotFound { 108 return fmt.Errorf("Error deleting workspace %s: %v", r.workspace, err) 109 } 110 111 return nil 112 } 113 114 // Lock the remote state. 115 func (r *remoteClient) Lock(info *state.LockInfo) (string, error) { 116 ctx := context.Background() 117 118 lockErr := &state.LockError{Info: r.lockInfo} 119 120 // Retrieve the workspace to lock. 121 w, err := r.client.Workspaces.Read(ctx, r.organization, r.workspace) 122 if err != nil { 123 lockErr.Err = err 124 return "", lockErr 125 } 126 127 // Check if the workspace is already locked. 128 if w.Locked { 129 lockErr.Err = fmt.Errorf( 130 "remote state already\nlocked (lock ID: \"%s/%s\")", r.organization, r.workspace) 131 return "", lockErr 132 } 133 134 // Lock the workspace. 135 w, err = r.client.Workspaces.Lock(ctx, w.ID, tfe.WorkspaceLockOptions{ 136 Reason: tfe.String("Locked by Terraform"), 137 }) 138 if err != nil { 139 lockErr.Err = err 140 return "", lockErr 141 } 142 143 r.lockInfo = info 144 145 return r.lockInfo.ID, nil 146 } 147 148 // Unlock the remote state. 149 func (r *remoteClient) Unlock(id string) error { 150 ctx := context.Background() 151 152 lockErr := &state.LockError{Info: r.lockInfo} 153 154 // Verify the expected lock ID. 155 if r.lockInfo != nil && r.lockInfo.ID != id { 156 lockErr.Err = fmt.Errorf("lock ID does not match existing lock") 157 return lockErr 158 } 159 160 // Verify the optional force-unlock lock ID. 161 if r.lockInfo == nil && r.organization+"/"+r.workspace != id { 162 lockErr.Err = fmt.Errorf("lock ID does not match existing lock") 163 return lockErr 164 } 165 166 // Retrieve the workspace to lock. 167 w, err := r.client.Workspaces.Read(ctx, r.organization, r.workspace) 168 if err != nil { 169 lockErr.Err = err 170 return lockErr 171 } 172 173 // Unlock the workspace. 174 w, err = r.client.Workspaces.Unlock(ctx, w.ID) 175 if err != nil { 176 lockErr.Err = err 177 return lockErr 178 } 179 180 return nil 181 }