From 23c89bc8937d220de5ab5f64e7b6a5d129f2894e Mon Sep 17 00:00:00 2001 From: Katy Moe Date: Fri, 19 Aug 2022 16:26:30 +0200 Subject: [PATCH 1/3] add tests for ForceUnlock --- tfexec/exit_errors.go | 13 ++++++ tfexec/force_unlock_test.go | 42 ++++++++++++++++++++ tfexec/internal/e2etest/force_unlock_test.go | 16 ++++++++ 3 files changed, 71 insertions(+) create mode 100644 tfexec/force_unlock_test.go diff --git a/tfexec/exit_errors.go b/tfexec/exit_errors.go index ea25b2a5..974e2513 100644 --- a/tfexec/exit_errors.go +++ b/tfexec/exit_errors.go @@ -46,6 +46,7 @@ var ( statePlanReadErrRegexp = regexp.MustCompile( `Terraform couldn't read the given file as a state or plan file.|` + `Error: Failed to read the given file as a state or plan file`) + lockIdInvalidErrRegexp = regexp.MustCompile(`Failed to unlock state: Lock ID should be numerical value`) ) func (tf *Terraform) wrapExitError(ctx context.Context, err error, stderr string) error { @@ -160,6 +161,8 @@ func (tf *Terraform) wrapExitError(ctx context.Context, err error, stderr string } case statePlanReadErrRegexp.MatchString(stderr): return &ErrStatePlanRead{stderr: stderr} + case lockIdInvalidErrRegexp.MatchString(stderr): + return &ErrLockIdInvalid{stderr: stderr} } return fmt.Errorf("%w\n%s", &unwrapper{exitErr, ctxErr}, stderr) @@ -256,6 +259,16 @@ func (e *ErrNoConfig) Error() string { return e.stderr } +type ErrLockIdInvalid struct { + unwrapper + + stderr string +} + +func (e *ErrLockIdInvalid) Error() string { + return e.stderr +} + // ErrCLIUsage is returned when the combination of flags or arguments is incorrect. // // CLI indicates usage errors in three different ways: either diff --git a/tfexec/force_unlock_test.go b/tfexec/force_unlock_test.go new file mode 100644 index 00000000..b5dc1778 --- /dev/null +++ b/tfexec/force_unlock_test.go @@ -0,0 +1,42 @@ +package tfexec + +import ( + "context" + "testing" + + "github.com/hashicorp/terraform-exec/tfexec/internal/testutil" +) + +func TestForceUnlockCmd(t *testing.T) { + td := t.TempDir() + + tf, err := NewTerraform(td, tfVersion(t, testutil.Latest_v1_1)) + if err != nil { + t.Fatal(err) + } + + // empty env, to avoid environ mismatch in testing + tf.SetEnv(map[string]string{}) + + t.Run("defaults", func(t *testing.T) { + forceUnlockCmd := tf.forceUnlockCmd(context.Background(), "12345") + + assertCmd(t, []string{ + "force-unlock", + "-no-color", + "-force", + "12345", + }, nil, forceUnlockCmd) + }) + + t.Run("override all defaults", func(t *testing.T) { + forceUnlockCmd := tf.forceUnlockCmd(context.Background(), "12345", Dir("mydir")) + + assertCmd(t, []string{ + "force-unlock", + "-no-color", + "-force", + "mydir", + }, nil, forceUnlockCmd) + }) +} diff --git a/tfexec/internal/e2etest/force_unlock_test.go b/tfexec/internal/e2etest/force_unlock_test.go index 46657275..42d26350 100644 --- a/tfexec/internal/e2etest/force_unlock_test.go +++ b/tfexec/internal/e2etest/force_unlock_test.go @@ -2,6 +2,7 @@ package e2etest import ( "context" + "errors" "testing" "github.com/hashicorp/go-version" @@ -40,4 +41,19 @@ func TestForceUnlock(t *testing.T) { t.Fatalf("error running ForceUnlock: %v", err) } }) + runTest(t, "invalid lock id", func(t *testing.T, tfv *version.Version, tf *tfexec.Terraform) { + err := tf.Init(context.Background()) + if err != nil { + t.Fatalf("error running Init: %v", err) + } + + err = tf.ForceUnlock(context.Background(), "badlockid") + if err == nil { + t.Fatalf("expected error when running ForceUnlock with invalid lock id") + } + var foErr *tfexec.ErrLockIdInvalid + if !errors.As(err, &foErr) { + t.Fatalf("expected ErrLockIdInvalid, %T returned: %s", err, err) + } + }) } From e25be3d1e3faea803b48c2855ab6ed9773761360 Mon Sep 17 00:00:00 2001 From: Katy Moe Date: Sat, 20 Aug 2022 00:01:27 +0200 Subject: [PATCH 2/3] add compatibility code for dir arg --- tfexec/force_unlock.go | 14 +++++++++++--- tfexec/force_unlock_test.go | 25 +++++++++++++++++++++++-- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/tfexec/force_unlock.go b/tfexec/force_unlock.go index e501baf5..de95f547 100644 --- a/tfexec/force_unlock.go +++ b/tfexec/force_unlock.go @@ -2,6 +2,7 @@ package tfexec import ( "context" + "fmt" "os/exec" ) @@ -21,7 +22,10 @@ func (opt *DirOption) configureForceUnlock(conf *forceUnlockConfig) { // ForceUnlock represents the `terraform force-unlock` command func (tf *Terraform) ForceUnlock(ctx context.Context, lockID string, opts ...ForceUnlockOption) error { - unlockCmd := tf.forceUnlockCmd(ctx, lockID, opts...) + unlockCmd, err := tf.forceUnlockCmd(ctx, lockID, opts...) + if err != nil { + return err + } if err := tf.runTerraformCmd(ctx, unlockCmd); err != nil { return err @@ -30,7 +34,7 @@ func (tf *Terraform) ForceUnlock(ctx context.Context, lockID string, opts ...For return nil } -func (tf *Terraform) forceUnlockCmd(ctx context.Context, lockID string, opts ...ForceUnlockOption) *exec.Cmd { +func (tf *Terraform) forceUnlockCmd(ctx context.Context, lockID string, opts ...ForceUnlockOption) (*exec.Cmd, error) { c := defaultForceUnlockOptions for _, o := range opts { @@ -43,8 +47,12 @@ func (tf *Terraform) forceUnlockCmd(ctx context.Context, lockID string, opts ... // optional positional arguments if c.dir != "" { + err := tf.compatible(ctx, nil, tf0_15_0) + if err != nil { + return nil, fmt.Errorf("[DIR] option was removed in Terraform v0.15.0") + } args = append(args, c.dir) } - return tf.buildTerraformCmd(ctx, nil, args...) + return tf.buildTerraformCmd(ctx, nil, args...), nil } diff --git a/tfexec/force_unlock_test.go b/tfexec/force_unlock_test.go index b5dc1778..b817979b 100644 --- a/tfexec/force_unlock_test.go +++ b/tfexec/force_unlock_test.go @@ -19,7 +19,10 @@ func TestForceUnlockCmd(t *testing.T) { tf.SetEnv(map[string]string{}) t.Run("defaults", func(t *testing.T) { - forceUnlockCmd := tf.forceUnlockCmd(context.Background(), "12345") + forceUnlockCmd, err := tf.forceUnlockCmd(context.Background(), "12345") + if err != nil { + t.Fatal(err) + } assertCmd(t, []string{ "force-unlock", @@ -28,14 +31,32 @@ func TestForceUnlockCmd(t *testing.T) { "12345", }, nil, forceUnlockCmd) }) +} + +// The optional final positional [DIR] argument is available +// until v0.15.0. +func TestForceUnlockCmd_pre015(t *testing.T) { + td := t.TempDir() + + tf, err := NewTerraform(td, tfVersion(t, testutil.Latest014)) + if err != nil { + t.Fatal(err) + } + + // empty env, to avoid environ mismatch in testing + tf.SetEnv(map[string]string{}) t.Run("override all defaults", func(t *testing.T) { - forceUnlockCmd := tf.forceUnlockCmd(context.Background(), "12345", Dir("mydir")) + forceUnlockCmd, err := tf.forceUnlockCmd(context.Background(), "12345", Dir("mydir")) + if err != nil { + t.Fatal(err) + } assertCmd(t, []string{ "force-unlock", "-no-color", "-force", + "12345", "mydir", }, nil, forceUnlockCmd) }) From c83f5ef112a3e0d8b650c3154b628724f7521c96 Mon Sep 17 00:00:00 2001 From: Katy Moe Date: Wed, 31 Aug 2022 11:36:40 +0100 Subject: [PATCH 3/3] generate lock id error for all backend types --- tfexec/exit_errors.go | 2 +- tfexec/internal/e2etest/force_unlock_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tfexec/exit_errors.go b/tfexec/exit_errors.go index 974e2513..9fc152dd 100644 --- a/tfexec/exit_errors.go +++ b/tfexec/exit_errors.go @@ -46,7 +46,7 @@ var ( statePlanReadErrRegexp = regexp.MustCompile( `Terraform couldn't read the given file as a state or plan file.|` + `Error: Failed to read the given file as a state or plan file`) - lockIdInvalidErrRegexp = regexp.MustCompile(`Failed to unlock state: Lock ID should be numerical value`) + lockIdInvalidErrRegexp = regexp.MustCompile(`Failed to unlock state: `) ) func (tf *Terraform) wrapExitError(ctx context.Context, err error, stderr string) error { diff --git a/tfexec/internal/e2etest/force_unlock_test.go b/tfexec/internal/e2etest/force_unlock_test.go index 42d26350..c5d626ac 100644 --- a/tfexec/internal/e2etest/force_unlock_test.go +++ b/tfexec/internal/e2etest/force_unlock_test.go @@ -41,7 +41,7 @@ func TestForceUnlock(t *testing.T) { t.Fatalf("error running ForceUnlock: %v", err) } }) - runTest(t, "invalid lock id", func(t *testing.T, tfv *version.Version, tf *tfexec.Terraform) { + 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("error running Init: %v", err)