From 262db8b7946dade345adabdcedf082021c46c513 Mon Sep 17 00:00:00 2001 From: Paul Tyng Date: Fri, 10 Jul 2020 21:41:33 -0400 Subject: [PATCH 1/3] Honor RemoveAll defer --- tfexec/terraform_test.go | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/tfexec/terraform_test.go b/tfexec/terraform_test.go index 45fc2e23..a6a2b547 100644 --- a/tfexec/terraform_test.go +++ b/tfexec/terraform_test.go @@ -19,20 +19,20 @@ const testTerraformStateFileName = "terraform.tfstate" var tfPath string func TestMain(m *testing.M) { - var err error - td, err := ioutil.TempDir("", "tfinstall") - if err != nil { - panic(err) - } - defer os.RemoveAll(td) - - tfPath, err = tfinstall.Find(tfinstall.LookPath(), tfinstall.LatestVersion(td, true)) - if err != nil { - panic(err) - } - exitCode := m.Run() - os.Exit(exitCode) - + os.Exit(func() int { + var err error + td, err := ioutil.TempDir("", "tfinstall") + if err != nil { + panic(err) + } + defer os.RemoveAll(td) + + tfPath, err = tfinstall.Find(tfinstall.LookPath(), tfinstall.LatestVersion(td, true)) + if err != nil { + panic(err) + } + return m.Run() + }()) } func TestCheckpointDisablePropagation(t *testing.T) { From a2868565c6a90d4ec0f5fb8f03ef436fb453bc05 Mon Sep 17 00:00:00 2001 From: Paul Tyng Date: Mon, 13 Jul 2020 10:45:24 -0400 Subject: [PATCH 2/3] Rework version fetching to enable multiple versions Also reorg test fixtures to prep for more fixtures --- tfexec/apply_test.go | 6 +- tfexec/destroy_test.go | 2 +- tfexec/init_test.go | 2 +- tfexec/output_test.go | 2 +- tfexec/plan_test.go | 2 +- tfexec/providers_schema_test.go | 2 +- tfexec/show_test.go | 16 ++-- tfexec/terraform_test.go | 81 ++++++++++++++++--- tfexec/testdata/{ => basic}/main.tf | 0 tfexec/testdata/{ => basic}/terraform.tfstate | 0 tfexec/testdata/show.json | 1 - 11 files changed, 84 insertions(+), 30 deletions(-) rename tfexec/testdata/{ => basic}/main.tf (100%) rename tfexec/testdata/{ => basic}/terraform.tfstate (100%) delete mode 100644 tfexec/testdata/show.json diff --git a/tfexec/apply_test.go b/tfexec/apply_test.go index e3ad561a..988aee65 100644 --- a/tfexec/apply_test.go +++ b/tfexec/apply_test.go @@ -12,12 +12,12 @@ func TestApply(t *testing.T) { td := testTempDir(t) defer os.RemoveAll(td) - tf, err := NewTerraform(td, tfPath) + tf, err := NewTerraform(td, tfVersion(t, "0.12.28")) if err != nil { t.Fatal(err) } - err = copyFile(filepath.Join(testFixtureDir, testConfigFileName), filepath.Join(td, testConfigFileName)) + err = copyFile(filepath.Join(testFixtureDir, "basic/main.tf"), td) if err != nil { t.Fatalf("error copying config file into test dir: %s", err) } @@ -37,7 +37,7 @@ func TestApplyCmd(t *testing.T) { td := testTempDir(t) defer os.RemoveAll(td) - tf, err := NewTerraform(td, tfPath) + tf, err := NewTerraform(td, tfVersion(t, "0.12.28")) if err != nil { t.Fatal(err) } diff --git a/tfexec/destroy_test.go b/tfexec/destroy_test.go index bda9972f..f35ac1bd 100644 --- a/tfexec/destroy_test.go +++ b/tfexec/destroy_test.go @@ -11,7 +11,7 @@ func TestDestroyCmd(t *testing.T) { td := testTempDir(t) defer os.RemoveAll(td) - tf, err := NewTerraform(td, tfPath) + tf, err := NewTerraform(td, tfVersion(t, "0.12.28")) if err != nil { t.Fatal(err) } diff --git a/tfexec/init_test.go b/tfexec/init_test.go index d86a6833..7031220c 100644 --- a/tfexec/init_test.go +++ b/tfexec/init_test.go @@ -11,7 +11,7 @@ func TestInitCmd(t *testing.T) { td := testTempDir(t) defer os.RemoveAll(td) - tf, err := NewTerraform(td, tfPath) + tf, err := NewTerraform(td, tfVersion(t, "0.12.28")) if err != nil { t.Fatal(err) } diff --git a/tfexec/output_test.go b/tfexec/output_test.go index bb488a61..7f6fa8f8 100644 --- a/tfexec/output_test.go +++ b/tfexec/output_test.go @@ -11,7 +11,7 @@ func TestOutputCmd(t *testing.T) { td := testTempDir(t) defer os.RemoveAll(td) - tf, err := NewTerraform(td, tfPath) + tf, err := NewTerraform(td, tfVersion(t, "0.12.28")) if err != nil { t.Fatal(err) } diff --git a/tfexec/plan_test.go b/tfexec/plan_test.go index b125c7a7..f26eb1ee 100644 --- a/tfexec/plan_test.go +++ b/tfexec/plan_test.go @@ -11,7 +11,7 @@ func TestPlanCmd(t *testing.T) { td := testTempDir(t) defer os.RemoveAll(td) - tf, err := NewTerraform(td, tfPath) + tf, err := NewTerraform(td, tfVersion(t, "0.12.28")) if err != nil { t.Fatal(err) } diff --git a/tfexec/providers_schema_test.go b/tfexec/providers_schema_test.go index 71f97d2f..e0f89a47 100644 --- a/tfexec/providers_schema_test.go +++ b/tfexec/providers_schema_test.go @@ -11,7 +11,7 @@ func TestProvidersSchemaCmd(t *testing.T) { td := testTempDir(t) defer os.RemoveAll(td) - tf, err := NewTerraform(td, tfPath) + tf, err := NewTerraform(td, tfVersion(t, "0.12.28")) if err != nil { t.Fatal(err) } diff --git a/tfexec/show_test.go b/tfexec/show_test.go index 1d7fe691..75b69b0c 100644 --- a/tfexec/show_test.go +++ b/tfexec/show_test.go @@ -16,19 +16,15 @@ func TestStateShow(t *testing.T) { td := testTempDir(t) defer os.RemoveAll(td) - tf, err := NewTerraform(td, tfPath) + tf, err := NewTerraform(td, tfVersion(t, "0.12.28")) if err != nil { t.Fatal(err) } // copy state and config files into test dir - err = copyFile(filepath.Join(testFixtureDir, testTerraformStateFileName), filepath.Join(td, testTerraformStateFileName)) + err = copyFiles(filepath.Join(testFixtureDir, "basic"), td) if err != nil { - t.Fatalf("error copying state file into test dir: %s", err) - } - err = copyFile(filepath.Join(testFixtureDir, testConfigFileName), filepath.Join(td, testConfigFileName)) - if err != nil { - t.Fatalf("error copying config file into test dir: %s", err) + t.Fatalf("error copying files into test dir: %s", err) } expected := tfjson.State{ @@ -70,12 +66,12 @@ func TestShow_errInitRequired(t *testing.T) { td := testTempDir(t) defer os.RemoveAll(td) - tf, err := NewTerraform(td, tfPath) + tf, err := NewTerraform(td, tfVersion(t, "0.12.28")) if err != nil { t.Fatal(err) } - err = copyFile(filepath.Join(testFixtureDir, testTerraformStateFileName), filepath.Join(td, testTerraformStateFileName)) + err = copyFile(filepath.Join(testFixtureDir, "basic", testTerraformStateFileName), td) _, err = tf.StateShow(context.Background()) if err == nil { @@ -92,7 +88,7 @@ func TestStateShowCmd(t *testing.T) { td := testTempDir(t) defer os.RemoveAll(td) - tf, err := NewTerraform(td, tfPath) + tf, err := NewTerraform(td, tfVersion(t, "0.12.28")) if err != nil { t.Fatal(err) } diff --git a/tfexec/terraform_test.go b/tfexec/terraform_test.go index a6a2b547..49de5472 100644 --- a/tfexec/terraform_test.go +++ b/tfexec/terraform_test.go @@ -5,32 +5,26 @@ import ( "io" "io/ioutil" "os" + "path/filepath" "reflect" + "sync" "testing" "github.com/hashicorp/terraform-exec/tfinstall" ) const testFixtureDir = "testdata" -const testConfigFileName = "main.tf" -const testStateJsonFileName = "state.json" const testTerraformStateFileName = "terraform.tfstate" -var tfPath string - func TestMain(m *testing.M) { os.Exit(func() int { var err error - td, err := ioutil.TempDir("", "tfinstall") + installDir, err = ioutil.TempDir("", "tfinstall") if err != nil { panic(err) } - defer os.RemoveAll(td) + defer os.RemoveAll(installDir) - tfPath, err = tfinstall.Find(tfinstall.LookPath(), tfinstall.LatestVersion(td, true)) - if err != nil { - panic(err) - } return m.Run() }()) } @@ -39,7 +33,7 @@ func TestCheckpointDisablePropagation(t *testing.T) { td := testTempDir(t) defer os.RemoveAll(td) - tf, err := NewTerraform(td, tfPath) + tf, err := NewTerraform(td, tfVersion(t, "0.12.28")) if err != nil { t.Fatal(err) } @@ -81,6 +75,25 @@ func testTempDir(t *testing.T) string { return d } +func copyFiles(path string, dstPath string) error { + infos, err := ioutil.ReadDir(path) + if err != nil { + return err + } + + for _, info := range infos { + if info.IsDir() { + // TODO: make recursive with filepath.Walk? + continue + } + err = copyFile(filepath.Join(path, info.Name()), dstPath) + if err != nil { + return err + } + } + return nil +} + func copyFile(path string, dstPath string) error { srcF, err := os.Open(path) if err != nil { @@ -88,6 +101,15 @@ func copyFile(path string, dstPath string) error { } defer srcF.Close() + di, err := os.Stat(dstPath) + if err != nil { + return err + } + if di.IsDir() { + _, file := filepath.Split(path) + dstPath = filepath.Join(dstPath, file) + } + dstF, err := os.Create(dstPath) if err != nil { return err @@ -100,3 +122,40 @@ func copyFile(path string, dstPath string) error { return nil } + +type installedVersion struct { + path string + err error +} + +var ( + installDir string + installedVersionLock sync.Mutex + installedVersions = map[string]installedVersion{} +) + +func tfVersion(t *testing.T, v string) string { + if installDir == "" { + t.Fatalf("installDir not yet configured, TestMain must run first") + } + + installedVersionLock.Lock() + defer installedVersionLock.Unlock() + + iv, ok := installedVersions[v] + if !ok { + dir := filepath.Join(installDir, v) + err := os.MkdirAll(dir, 0777) + if err != nil { + t.Fatal(err) + } + iv.path, iv.err = tfinstall.Find(tfinstall.ExactVersion(v, dir)) + installedVersions[v] = iv + } + + if iv.err != nil { + t.Fatalf("error installing terraform version %q: %s", v, iv.err) + } + + return iv.path +} diff --git a/tfexec/testdata/main.tf b/tfexec/testdata/basic/main.tf similarity index 100% rename from tfexec/testdata/main.tf rename to tfexec/testdata/basic/main.tf diff --git a/tfexec/testdata/terraform.tfstate b/tfexec/testdata/basic/terraform.tfstate similarity index 100% rename from tfexec/testdata/terraform.tfstate rename to tfexec/testdata/basic/terraform.tfstate diff --git a/tfexec/testdata/show.json b/tfexec/testdata/show.json deleted file mode 100644 index a7509e5a..00000000 --- a/tfexec/testdata/show.json +++ /dev/null @@ -1 +0,0 @@ -{"format_version":"0.1","terraform_version":"0.12.24","values":{"root_module":{"resources":[{"address":"null_resource.foo","mode":"managed","type":"null_resource","name":"foo","provider_name":"null","schema_version":0,"values":{"id":"5510719323588825107","triggers":null}}]}}} From 67f8ff0927d47b3bbd511b74326b0d87bcd0b49f Mon Sep 17 00:00:00 2001 From: Paul Tyng Date: Mon, 13 Jul 2020 10:47:45 -0400 Subject: [PATCH 3/3] Make import addr and ID required Add runtime testing of import across multiple versions of TF --- tfexec/import.go | 17 +++---- tfexec/import_test.go | 82 +++++++++++++++++++++++++++++++--- tfexec/options.go | 21 +++------ tfexec/testdata/import/main.tf | 5 +++ 4 files changed, 92 insertions(+), 33 deletions(-) create mode 100644 tfexec/testdata/import/main.tf diff --git a/tfexec/import.go b/tfexec/import.go index bd204ca2..c1de8ba1 100644 --- a/tfexec/import.go +++ b/tfexec/import.go @@ -31,14 +31,6 @@ type ImportOption interface { configureImport(*importConfig) } -func (opt *AddrOption) configureImport(conf *importConfig) { - conf.addr = opt.addr -} - -func (opt *IdOption) configureImport(conf *importConfig) { - conf.id = opt.id -} - func (opt *BackupOption) configureImport(conf *importConfig) { conf.backup = opt.path } @@ -75,8 +67,8 @@ func (opt *VarFileOption) configureImport(conf *importConfig) { conf.varFile = opt.path } -func (t *Terraform) Import(ctx context.Context, opts ...ImportOption) error { - importCmd := t.ImportCmd(ctx, opts...) +func (t *Terraform) Import(ctx context.Context, address, id string, opts ...ImportOption) error { + importCmd := t.ImportCmd(ctx, address, id, opts...) var errBuf strings.Builder importCmd.Stderr = &errBuf @@ -89,7 +81,7 @@ func (t *Terraform) Import(ctx context.Context, opts ...ImportOption) error { return nil } -func (tf *Terraform) ImportCmd(ctx context.Context, opts ...ImportOption) *exec.Cmd { +func (tf *Terraform) ImportCmd(ctx context.Context, address, id string, opts ...ImportOption) *exec.Cmd { c := defaultImportOptions for _, o := range opts { @@ -133,5 +125,8 @@ func (tf *Terraform) ImportCmd(ctx context.Context, opts ...ImportOption) *exec. } } + // required args, always pass + args = append(args, address, id) + return tf.buildTerraformCmd(ctx, args...) } diff --git a/tfexec/import_test.go b/tfexec/import_test.go index 9cdb81d0..0b55346e 100644 --- a/tfexec/import_test.go +++ b/tfexec/import_test.go @@ -3,32 +3,101 @@ package tfexec import ( "context" "os" + "path/filepath" "strings" "testing" ) +func TestImport(t *testing.T) { + const ( + expectedID = "asdlfjksdlfkjsdlfk" + resourceAddress = "random_string.random_string" + ) + ctx := context.Background() + + for _, tfv := range []string{ + // "0.11.14", doesn't support show JSON output, but does support import + "0.12.28", + "0.13.0-beta3", + } { + t.Run(tfv, func(t *testing.T) { + td := testTempDir(t) + defer os.RemoveAll(td) + + err := copyFiles(filepath.Join(testFixtureDir, "import"), td) + if err != nil { + t.Fatal(err) + } + + tf, err := NewTerraform(td, tfVersion(t, tfv)) + if err != nil { + t.Fatal(err) + } + + err = tf.Init(ctx, Lock(false)) + if err != nil { + t.Fatal(err) + } + + err = tf.Import(ctx, resourceAddress, expectedID, DisableBackup(), Lock(false)) + if err != nil { + t.Fatal(err) + } + + state, err := tf.StateShow(ctx) + if err != nil { + t.Fatal(err) + } + + for _, r := range state.Values.RootModule.Resources { + if r.Address != resourceAddress { + continue + } + + raw, ok := r.AttributeValues["id"] + if !ok { + t.Fatal("value not found for \"id\" attribute") + } + actual, ok := raw.(string) + if !ok { + t.Fatalf("unable to cast %T to string: %#v", raw, raw) + } + + if actual != expectedID { + t.Fatalf("expected %q, got %q", expectedID, actual) + } + + // success + return + } + + t.Fatalf("imported resource %q not found", resourceAddress) + }) + } +} + func TestImportCmd(t *testing.T) { td := testTempDir(t) defer os.RemoveAll(td) - tf, err := NewTerraform(td, tfPath) + tf, err := NewTerraform(td, tfVersion(t, "0.12.28")) if err != nil { t.Fatal(err) } // defaults - importCmd := tf.ImportCmd(context.Background()) + importCmd := tf.ImportCmd(context.Background(), "my-addr", "my-id") actual := strings.TrimPrefix(cmdString(importCmd), importCmd.Path+" ") - expected := "import -no-color -input=false -lock-timeout=0s -lock=true" + expected := "import -no-color -input=false -lock-timeout=0s -lock=true my-addr my-id" if actual != expected { t.Fatalf("expected default arguments of ImportCmd:\n%s\n actual arguments:\n%s\n", expected, actual) } // override all defaults - importCmd = tf.ImportCmd(context.Background(), + importCmd = tf.ImportCmd(context.Background(), "my-addr2", "my-id2", Backup("testbackup"), LockTimeout("200s"), State("teststate"), @@ -37,11 +106,12 @@ func TestImportCmd(t *testing.T) { Lock(false), Var("var1=foo"), Var("var2=bar"), - AllowMissingConfig(true)) + AllowMissingConfig(true), + ) actual = strings.TrimPrefix(cmdString(importCmd), importCmd.Path+" ") - expected = "import -no-color -input=false -backup=testbackup -lock-timeout=200s -state=teststate -state-out=teststateout -var-file=testvarfile -lock=false -allow-missing-config -var 'var1=foo' -var 'var2=bar'" + expected = "import -no-color -input=false -backup=testbackup -lock-timeout=200s -state=teststate -state-out=teststateout -var-file=testvarfile -lock=false -allow-missing-config -var 'var1=foo' -var 'var2=bar' my-addr2 my-id2" if actual != expected { t.Fatalf("expected arguments of ImportCmd:\n%s\n actual arguments:\n%s\n", expected, actual) diff --git a/tfexec/options.go b/tfexec/options.go index 970e92fa..035268ea 100644 --- a/tfexec/options.go +++ b/tfexec/options.go @@ -1,13 +1,5 @@ package tfexec -type AddrOption struct { - addr string -} - -func Addr(addr string) *AddrOption { - return &AddrOption{addr} -} - type AllowMissingConfigOption struct { allowMissingConfig bool } @@ -40,6 +32,11 @@ func Backup(path string) *BackupOption { return &BackupOption{path} } +// DisableBackup is a convenience method for Backup("-"), indicating backup state should be disabled. +func DisableBackup() *BackupOption { + return &BackupOption{"-"} +} + type ConfigOption struct { path string } @@ -97,14 +94,6 @@ func GetPlugins(getPlugins bool) *GetPluginsOption { return &GetPluginsOption{getPlugins} } -type IdOption struct { - id string -} - -func Id(id string) *IdOption { - return &IdOption{id} -} - type LockOption struct { lock bool } diff --git a/tfexec/testdata/import/main.tf b/tfexec/testdata/import/main.tf new file mode 100644 index 00000000..848a0d72 --- /dev/null +++ b/tfexec/testdata/import/main.tf @@ -0,0 +1,5 @@ +provider "random" { +} + +resource "random_string" "random_string" { +} \ No newline at end of file