backend/remote: do not unlock after a failed upload

When changes are made and we failed to upload the state, we should not
try to unlock the workspace. Leaving the workspace locked is a good
indication something went wrong and also prevents other changes from
being applied before the newest state is properly uploaded.

Additionally we now output the lock ID when a lock or force-unlock
action failed.
This commit is contained in:
Sander van Harmelen 2019-04-29 20:19:23 +02:00
parent 394cf7f25e
commit 394f20f59c

View File

@ -14,11 +14,12 @@ import (
) )
type remoteClient struct { type remoteClient struct {
client *tfe.Client client *tfe.Client
lockInfo *state.LockInfo lockInfo *state.LockInfo
organization string organization string
runID string runID string
workspace *tfe.Workspace stateUploadErr bool
workspace *tfe.Workspace
} }
// Get the remote state. // Get the remote state.
@ -31,12 +32,12 @@ func (r *remoteClient) Get() (*remote.Payload, error) {
// If no state exists, then return nil. // If no state exists, then return nil.
return nil, 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) state, err := r.client.StateVersions.Download(ctx, sv.DownloadURL)
if err != nil { 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. // If the state is empty, then return nil.
@ -79,7 +80,8 @@ func (r *remoteClient) Put(state []byte) error {
// Create the new state. // Create the new state.
_, err = r.client.StateVersions.Create(ctx, r.workspace.ID, options) _, err = r.client.StateVersions.Create(ctx, r.workspace.ID, options)
if err != nil { 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 return nil
@ -106,6 +108,9 @@ func (r *remoteClient) Lock(info *state.LockInfo) (string, error) {
Reason: tfe.String("Locked by Terraform"), Reason: tfe.String("Locked by Terraform"),
}) })
if err != nil { if err != nil {
if err == tfe.ErrWorkspaceLocked {
err = fmt.Errorf("%s (lock ID: \"%s/%s\")", err, r.organization, r.workspace.Name)
}
lockErr.Err = err lockErr.Err = err
return "", lockErr return "", lockErr
} }
@ -119,6 +124,13 @@ func (r *remoteClient) Lock(info *state.LockInfo) (string, error) {
func (r *remoteClient) Unlock(id string) error { func (r *remoteClient) Unlock(id string) error {
ctx := context.Background() 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} lockErr := &state.LockError{Info: r.lockInfo}
// With lock info this should be treated as a normal unlock. // 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. // Verify the optional force-unlock lock ID.
if r.organization+"/"+r.workspace.Name != 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 return lockErr
} }