diff --git a/backend/remote/backend_state.go b/backend/remote/backend_state.go index 0f4375e7d..5089c4075 100644 --- a/backend/remote/backend_state.go +++ b/backend/remote/backend_state.go @@ -14,11 +14,12 @@ import ( ) type remoteClient struct { - client *tfe.Client - lockInfo *state.LockInfo - organization string - runID string - workspace *tfe.Workspace + client *tfe.Client + lockInfo *state.LockInfo + organization string + runID string + stateUploadErr bool + workspace *tfe.Workspace } // Get the remote state. @@ -31,12 +32,12 @@ func (r *remoteClient) Get() (*remote.Payload, error) { // If no state exists, then return nil. return nil, nil } - return nil, fmt.Errorf("Error retrieving remote state: %v", err) + return nil, fmt.Errorf("Error retrieving state: %v", err) } state, err := r.client.StateVersions.Download(ctx, sv.DownloadURL) if err != nil { - return nil, fmt.Errorf("Error downloading remote state: %v", err) + return nil, fmt.Errorf("Error downloading state: %v", err) } // If the state is empty, then return nil. @@ -79,7 +80,8 @@ func (r *remoteClient) Put(state []byte) error { // Create the new state. _, err = r.client.StateVersions.Create(ctx, r.workspace.ID, options) if err != nil { - return fmt.Errorf("Error creating remote state: %v", err) + r.stateUploadErr = true + return fmt.Errorf("Error uploading state: %v", err) } return nil @@ -106,6 +108,9 @@ func (r *remoteClient) Lock(info *state.LockInfo) (string, error) { Reason: tfe.String("Locked by Terraform"), }) if err != nil { + if err == tfe.ErrWorkspaceLocked { + err = fmt.Errorf("%s (lock ID: \"%s/%s\")", err, r.organization, r.workspace.Name) + } lockErr.Err = err return "", lockErr } @@ -119,6 +124,13 @@ func (r *remoteClient) Lock(info *state.LockInfo) (string, error) { func (r *remoteClient) Unlock(id string) error { ctx := context.Background() + // We first check if there was an error while uploading the latest + // state. If so, we will not unlock the workspace to prevent any + // changes from being applied until the correct state is uploaded. + if r.stateUploadErr { + return nil + } + lockErr := &state.LockError{Info: r.lockInfo} // With lock info this should be treated as a normal unlock. @@ -141,7 +153,12 @@ func (r *remoteClient) Unlock(id string) error { // Verify the optional force-unlock lock ID. if r.organization+"/"+r.workspace.Name != id { - lockErr.Err = fmt.Errorf("lock ID does not match existing lock") + lockErr.Err = fmt.Errorf( + "lock ID %q does not match existing lock ID \"%s/%s\"", + id, + r.organization, + r.workspace.Name, + ) return lockErr }