diff --git a/tfexec/force_unlock.go b/tfexec/force_unlock.go new file mode 100644 index 00000000..c8dddffa --- /dev/null +++ b/tfexec/force_unlock.go @@ -0,0 +1,50 @@ +package tfexec + +import ( + "context" + "os/exec" +) + +type forceUnlockConfig struct { + dir string +} + +var defaultForceUnlockOptions = forceUnlockConfig{} + +type ForceUnlockOption interface { + configureForceUnlock(*forceUnlockConfig) +} + +func (opt *DirOption) configureForceUnlock(conf *forceUnlockConfig) { + conf.dir = opt.path +} + +// 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...) + + if err := tf.runTerraformCmd(ctx, unlockCmd); err != nil { + return err + } + + return nil +} + +func (tf *Terraform) forceUnlockCmd(ctx context.Context, lockID string, opts ...ForceUnlockOption) *exec.Cmd { + c := defaultForceUnlockOptions + + for _, o := range opts { + o.configureForceUnlock(&c) + } + args := []string{"force-unlock", "-force"} + + // positional arguments + args = append(args, lockID) + + // optional positional arguments + if c.dir != "" { + args = append(args, c.dir) + } + + return tf.buildTerraformCmd(ctx, nil, args...) +} diff --git a/tfexec/internal/e2etest/force_unlock_test.go b/tfexec/internal/e2etest/force_unlock_test.go new file mode 100644 index 00000000..83bbd75b --- /dev/null +++ b/tfexec/internal/e2etest/force_unlock_test.go @@ -0,0 +1,43 @@ +package e2etest + +import ( + "context" + "testing" + + "github.com/hashicorp/go-version" + + "github.com/hashicorp/terraform-exec/tfexec" +) + +// LockID set in the test fixture +const inmemLockID = "2b6a6738-5dd5-50d6-c0ae-f6352977666b" + +var forceUnlockDirArgMaxVersion = version.Must(version.NewVersion("0.15.0")) + +func TestForceUnlock(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("error running Init: %v", err) + } + + err = tf.ForceUnlock(context.Background(), inmemLockID) + if err != nil { + t.Fatalf("error running ForceUnlock: %v", err) + } + }) + runTest(t, "inmem-backend-locked", func(t *testing.T, tfv *version.Version, tf *tfexec.Terraform) { + if tfv.GreaterThanOrEqual(forceUnlockDirArgMaxVersion) { + t.Skip("legacy positional path argument deprecated in favor of global -chdir flag") + } + err := tf.Init(context.Background()) + if err != nil { + t.Fatalf("error running Init: %v", err) + } + + err = tf.ForceUnlock(context.Background(), inmemLockID, tfexec.Dir(tf.WorkingDir())) + if err != nil { + t.Fatalf("error running ForceUnlock: %v", err) + } + }) +} diff --git a/tfexec/internal/e2etest/testdata/inmem-backend-locked/main.tf b/tfexec/internal/e2etest/testdata/inmem-backend-locked/main.tf new file mode 100644 index 00000000..9fb065d7 --- /dev/null +++ b/tfexec/internal/e2etest/testdata/inmem-backend-locked/main.tf @@ -0,0 +1,5 @@ +terraform { + backend "inmem" { + lock_id = "2b6a6738-5dd5-50d6-c0ae-f6352977666b" + } +}