Skip to content

Commit

Permalink
add error wrapping for locked state (#221)
Browse files Browse the repository at this point in the history
* add error wrapping for locked state

* use strings.Builder instead of bytes.Buffer

* parse date for LockInfo created field

* revert date parsing; fix LockInfo regexp

* add test for StateLockErr; update build tags

* replace build tags

* Update tfexec/exit_errors.go

Co-authored-by: Radek Simko <radek.simko@gmail.com>
  • Loading branch information
alec-rabold and radeksimko authored Oct 4, 2021
1 parent 9230b19 commit a0da02b
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 0 deletions.
48 changes: 48 additions & 0 deletions tfexec/exit_errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"os/exec"
"regexp"
"strings"
"text/template"
)

// this file contains errors parsed from stderr
Expand All @@ -30,6 +31,9 @@ var (
tfVersionMismatchErrRegexp = regexp.MustCompile(`Error: The currently running version of Terraform doesn't meet the|Error: Unsupported Terraform Core version`)
tfVersionMismatchConstraintRegexp = regexp.MustCompile(`required_version = "(.+)"|Required version: (.+)\b`)
configInvalidErrRegexp = regexp.MustCompile(`There are some problems with the configuration, described below.`)

stateLockErrRegexp = regexp.MustCompile(`Error acquiring the state lock`)
stateLockInfoRegexp = regexp.MustCompile(`Lock Info:\n\s*ID:\s*([^\n]+)\n\s*Path:\s*([^\n]+)\n\s*Operation:\s*([^\n]+)\n\s*Who:\s*([^\n]+)\n\s*Version:\s*([^\n]+)\n\s*Created:\s*([^\n]+)\n`)
)

func (tf *Terraform) wrapExitError(ctx context.Context, err error, stderr string) error {
Expand Down Expand Up @@ -128,6 +132,20 @@ func (tf *Terraform) wrapExitError(ctx context.Context, err error, stderr string
}
case configInvalidErrRegexp.MatchString(stderr):
return &ErrConfigInvalid{stderr: stderr}
case stateLockErrRegexp.MatchString(stderr):
submatches := stateLockInfoRegexp.FindStringSubmatch(stderr)
if len(submatches) == 7 {
return &ErrStateLocked{
unwrapper: unwrapper{exitErr, ctxErr},

ID: submatches[1],
Path: submatches[2],
Operation: submatches[3],
Who: submatches[4],
Version: submatches[5],
Created: submatches[6],
}
}
}

return fmt.Errorf("%w\n%s", &unwrapper{exitErr, ctxErr}, stderr)
Expand Down Expand Up @@ -257,3 +275,33 @@ func (e *ErrTFVersionMismatch) Error() string {
return fmt.Sprintf("terraform %s not supported by configuration%s",
version, requirement)
}

// ErrStateLocked is returned when the state lock is already held by another process.
type ErrStateLocked struct {
unwrapper

ID string
Path string
Operation string
Who string
Version string
Created string
}

func (e *ErrStateLocked) Error() string {
tmpl := `Lock Info:
ID: {{.ID}}
Path: {{.Path}}
Operation: {{.Operation}}
Who: {{.Who}}
Version: {{.Version}}
Created: {{.Created}}
`

t := template.Must(template.New("LockInfo").Parse(tmpl))
var out strings.Builder
if err := t.Execute(&out, e); err != nil {
return "error acquiring the state lock"
}
return fmt.Sprintf("error acquiring the state lock: %v", out.String())
}
19 changes: 19 additions & 0 deletions tfexec/internal/e2etest/errors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,25 @@ func TestTFVersionMismatch(t *testing.T) {
})
}

func TestLockedState(t *testing.T) {
runTest(t, "inmem-backend-locked", func(t *testing.T, tfv *version.Version, tf *tfexec.Terraform) {
err := tf.Init(context.Background())
if err != nil {
t.Fatalf("err during init: %s", err)
}

err = tf.Apply(context.Background())
if err == nil {
t.Fatal("expected error, but didn't find one")
}

var stateLockedErr *tfexec.ErrStateLocked
if !errors.As(err, &stateLockedErr) {
t.Fatalf("expected ErrTFVersionMismatch, got %T, %s", err, err)
}
})
}

func TestContext_alreadyPastDeadline(t *testing.T) {
runTest(t, "", func(t *testing.T, tfv *version.Version, tf *tfexec.Terraform) {
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(-1*time.Second))
Expand Down
5 changes: 5 additions & 0 deletions tfexec/internal/e2etest/testdata/inmem-backend-locked/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
terraform {
backend "inmem" {
lock_id = "2b6a6738-5dd5-50d6-c0ae-f6352977666b"
}
}

0 comments on commit a0da02b

Please # to comment.