From eed605ac05cbed466ffe663607e869fde75cc531 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Mon, 29 Apr 2019 10:03:25 -0700 Subject: [PATCH] [WIP] Re-enable the end-to-end tests (#20044) * internal/initwd: Allow deprecated relative module paths In Terraform 0.11 we deprecated this form but didn't have any explicit warning for it. Now we'll still accept it but generate a warning. In a future major release we will drop this form altogether, since it is ambiguous with registry module source addresses. This codepath is covered by the command/e2etest suite. * e2e: Skip copying .exists file, if present We use this only in the "empty" test fixture in order to let git know that the directory exists. We need to skip copying it so that we can test "terraform init -from-module=...", which expects to find an empty directory. * command/e2etests: Re-enable and fix up the e2etest "acctests" We disabled all of the tests that accessed remote services like the Terraform Registry while they were being updated to support the new protocols we now expect. With those services now in place, we can re-enable these tests. Some details of exactly what output we print, etc, have intentionally changed since these tests were last updated. * e2e: refactor for modern states and plans * command/e2etest: re-enable e2etests and update for tf 0.12 compatibility plugin/discovery: mkdirAll instead of mkdir when creating cache dir --- command/e2etest/automation_test.go | 28 +++++------ command/e2etest/init_test.go | 10 ++-- command/e2etest/main_test.go | 6 --- command/e2etest/primary_test.go | 17 +++---- ... => terraform-provider-template_v2.1.0_x4} | 0 .../test-fixtures/plugin-cache/main.tf | 4 +- e2e/e2e.go | 50 +++++++++++++------ plugin/discovery/get.go | 2 +- 8 files changed, 60 insertions(+), 57 deletions(-) rename command/e2etest/test-fixtures/plugin-cache/cache/os_arch/{terraform-provider-template_v0.1.0_x4 => terraform-provider-template_v2.1.0_x4} (100%) diff --git a/command/e2etest/automation_test.go b/command/e2etest/automation_test.go index 8c51f381b..80495fa1e 100644 --- a/command/e2etest/automation_test.go +++ b/command/e2etest/automation_test.go @@ -7,7 +7,6 @@ import ( "strings" "testing" - "github.com/davecgh/go-spew/spew" "github.com/hashicorp/terraform/e2e" ) @@ -41,11 +40,11 @@ func TestPlanApplyInAutomation(t *testing.T) { // Make sure we actually downloaded the plugins, rather than picking up // copies that might be already installed globally on the system. - if !strings.Contains(stdout, "- Downloading plugin for provider \"template\"") { + if !strings.Contains(stdout, "- Downloading plugin for provider \"template") { t.Errorf("template provider download message is missing from init output:\n%s", stdout) t.Logf("(this can happen if you have a copy of the plugin in one of the global plugin search dirs)") } - if !strings.Contains(stdout, "- Downloading plugin for provider \"null\"") { + if !strings.Contains(stdout, "- Downloading plugin for provider \"null") { t.Errorf("null provider download message is missing from init output:\n%s", stdout) t.Logf("(this can happen if you have a copy of the plugin in one of the global plugin search dirs)") } @@ -71,14 +70,11 @@ func TestPlanApplyInAutomation(t *testing.T) { t.Fatalf("failed to read plan file: %s", err) } - stateResources := plan.State.RootModule().Resources - diffResources := plan.Diff.RootModule().Resources + // stateResources := plan.Changes.Resources + diffResources := plan.Changes.Resources - if len(stateResources) != 1 || stateResources["data.template_file.test"] == nil { - t.Errorf("incorrect state in plan; want just data.template_file.test to have been rendered, but have:\n%s", spew.Sdump(stateResources)) - } - if len(diffResources) != 1 || diffResources["null_resource.test"] == nil { - t.Errorf("incorrect diff in plan; want just null_resource.test to have been rendered, but have:\n%s", spew.Sdump(diffResources)) + if len(diffResources) != 1 || diffResources[0].Addr.String() != "null_resource.test" { + t.Errorf("incorrect number of resources in plan") } //// APPLY @@ -96,9 +92,9 @@ func TestPlanApplyInAutomation(t *testing.T) { t.Fatalf("failed to read state file: %s", err) } - stateResources = state.RootModule().Resources + stateResources := state.RootModule().Resources var gotResources []string - for n := range stateResources { + for n, _ := range stateResources { gotResources = append(gotResources, n) } sort.Strings(gotResources) @@ -139,11 +135,11 @@ func TestAutoApplyInAutomation(t *testing.T) { // Make sure we actually downloaded the plugins, rather than picking up // copies that might be already installed globally on the system. - if !strings.Contains(stdout, "- Downloading plugin for provider \"template\"") { + if !strings.Contains(stdout, "- Downloading plugin for provider \"template") { t.Errorf("template provider download message is missing from init output:\n%s", stdout) t.Logf("(this can happen if you have a copy of the plugin in one of the global plugin search dirs)") } - if !strings.Contains(stdout, "- Downloading plugin for provider \"null\"") { + if !strings.Contains(stdout, "- Downloading plugin for provider \"null") { t.Errorf("null provider download message is missing from init output:\n%s", stdout) t.Logf("(this can happen if you have a copy of the plugin in one of the global plugin search dirs)") } @@ -206,11 +202,11 @@ func TestPlanOnlyInAutomation(t *testing.T) { // Make sure we actually downloaded the plugins, rather than picking up // copies that might be already installed globally on the system. - if !strings.Contains(stdout, "- Downloading plugin for provider \"template\"") { + if !strings.Contains(stdout, "- Downloading plugin for provider \"template") { t.Errorf("template provider download message is missing from init output:\n%s", stdout) t.Logf("(this can happen if you have a copy of the plugin in one of the global plugin search dirs)") } - if !strings.Contains(stdout, "- Downloading plugin for provider \"null\"") { + if !strings.Contains(stdout, "- Downloading plugin for provider \"null") { t.Errorf("null provider download message is missing from init output:\n%s", stdout) t.Logf("(this can happen if you have a copy of the plugin in one of the global plugin search dirs)") } diff --git a/command/e2etest/init_test.go b/command/e2etest/init_test.go index a80ba7db3..50e1fe3e9 100644 --- a/command/e2etest/init_test.go +++ b/command/e2etest/init_test.go @@ -39,7 +39,7 @@ func TestInitProviders(t *testing.T) { t.Errorf("success message is missing from output:\n%s", stdout) } - if !strings.Contains(stdout, "- Downloading plugin for provider \"template\"") { + if !strings.Contains(stdout, "- Downloading plugin for provider \"template\" (terraform-providers/template)") { t.Errorf("provider download message is missing from output:\n%s", stdout) t.Logf("(this can happen if you have a copy of the plugin in one of the global plugin search dirs)") } @@ -112,10 +112,10 @@ func TestInitProviders_pluginCache(t *testing.T) { stderr := cmd.Stderr.(*bytes.Buffer).String() if stderr != "" { - t.Errorf("unexpected stderr output:\n%s", stderr) + t.Errorf("unexpected stderr output:\n%s\n", stderr) } - path := fmt.Sprintf(".terraform/plugins/%s_%s/terraform-provider-template_v0.1.0_x4", runtime.GOOS, runtime.GOARCH) + path := fmt.Sprintf(".terraform/plugins/%s_%s/terraform-provider-template_v2.1.0_x4", runtime.GOOS, runtime.GOARCH) content, err := tf.ReadFile(path) if err != nil { t.Fatalf("failed to read installed plugin from %s: %s", path, err) @@ -124,11 +124,11 @@ func TestInitProviders_pluginCache(t *testing.T) { t.Errorf("template plugin was not installed from local cache") } - if !tf.FileExists(fmt.Sprintf(".terraform/plugins/%s_%s/terraform-provider-null_v0.1.0_x4", runtime.GOOS, runtime.GOARCH)) { + if !tf.FileExists(fmt.Sprintf(".terraform/plugins/%s_%s/terraform-provider-null_v2.1.0_x4", runtime.GOOS, runtime.GOARCH)) { t.Errorf("null plugin was not installed") } - if !tf.FileExists(fmt.Sprintf("cache/%s_%s/terraform-provider-null_v0.1.0_x4", runtime.GOOS, runtime.GOARCH)) { + if !tf.FileExists(fmt.Sprintf("cache/%s_%s/terraform-provider-null_v2.1.0_x4", runtime.GOOS, runtime.GOARCH)) { t.Errorf("null plugin is not in cache after install") } } diff --git a/command/e2etest/main_test.go b/command/e2etest/main_test.go index a053e501e..64dc8f148 100644 --- a/command/e2etest/main_test.go +++ b/command/e2etest/main_test.go @@ -56,10 +56,4 @@ func skipIfCannotAccessNetwork(t *testing.T) { if !canAccessNetwork() { t.Skip("network access not allowed; use TF_ACC=1 to enable") } - - // During the early part of the Terraform v0.12 release process, certain - // upstream resources are not yet ready to support it and so these - // tests cannot be run. These will be re-enabled prior to Terraform v0.12.0 - // final. - t.Skip("all tests with external network access are temporarily disabled until upstream services are updated") } diff --git a/command/e2etest/primary_test.go b/command/e2etest/primary_test.go index fb825e477..dd5b1160a 100644 --- a/command/e2etest/primary_test.go +++ b/command/e2etest/primary_test.go @@ -38,11 +38,11 @@ func TestPrimarySeparatePlan(t *testing.T) { // Make sure we actually downloaded the plugins, rather than picking up // copies that might be already installed globally on the system. - if !strings.Contains(stdout, "- Downloading plugin for provider \"template\"") { + if !strings.Contains(stdout, "- Downloading plugin for provider \"template") { t.Errorf("template provider download message is missing from init output:\n%s", stdout) t.Logf("(this can happen if you have a copy of the plugin in one of the global plugin search dirs)") } - if !strings.Contains(stdout, "- Downloading plugin for provider \"null\"") { + if !strings.Contains(stdout, "- Downloading plugin for provider \"null") { t.Errorf("null provider download message is missing from init output:\n%s", stdout) t.Logf("(this can happen if you have a copy of the plugin in one of the global plugin search dirs)") } @@ -69,13 +69,8 @@ func TestPrimarySeparatePlan(t *testing.T) { t.Fatalf("failed to read plan file: %s", err) } - stateResources := plan.State.RootModule().Resources - diffResources := plan.Diff.RootModule().Resources - - if len(stateResources) != 1 || stateResources["data.template_file.test"] == nil { - t.Errorf("incorrect state in plan; want just data.template_file.test to have been rendered, but have:\n%s", spew.Sdump(stateResources)) - } - if len(diffResources) != 1 || diffResources["null_resource.test"] == nil { + diffResources := plan.Changes.Resources + if len(diffResources) != 1 || diffResources[0].Addr.String() != "null_resource.test" { t.Errorf("incorrect diff in plan; want just null_resource.test to have been rendered, but have:\n%s", spew.Sdump(diffResources)) } @@ -94,9 +89,9 @@ func TestPrimarySeparatePlan(t *testing.T) { t.Fatalf("failed to read state file: %s", err) } - stateResources = state.RootModule().Resources + stateResources := state.RootModule().Resources var gotResources []string - for n := range stateResources { + for n, _ := range stateResources { gotResources = append(gotResources, n) } sort.Strings(gotResources) diff --git a/command/e2etest/test-fixtures/plugin-cache/cache/os_arch/terraform-provider-template_v0.1.0_x4 b/command/e2etest/test-fixtures/plugin-cache/cache/os_arch/terraform-provider-template_v2.1.0_x4 similarity index 100% rename from command/e2etest/test-fixtures/plugin-cache/cache/os_arch/terraform-provider-template_v0.1.0_x4 rename to command/e2etest/test-fixtures/plugin-cache/cache/os_arch/terraform-provider-template_v2.1.0_x4 diff --git a/command/e2etest/test-fixtures/plugin-cache/main.tf b/command/e2etest/test-fixtures/plugin-cache/main.tf index 94bae0fa2..f52d48294 100644 --- a/command/e2etest/test-fixtures/plugin-cache/main.tf +++ b/command/e2etest/test-fixtures/plugin-cache/main.tf @@ -1,7 +1,7 @@ provider "template" { - version = "0.1.0" + version = "2.1.0" } provider "null" { - version = "0.1.0" + version = "2.1.0" } diff --git a/e2e/e2e.go b/e2e/e2e.go index 23ebc121c..2262f5a1e 100644 --- a/e2e/e2e.go +++ b/e2e/e2e.go @@ -9,7 +9,10 @@ import ( "os/exec" "path/filepath" - tfcore "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/terraform/plans" + "github.com/hashicorp/terraform/plans/planfile" + "github.com/hashicorp/terraform/states" + "github.com/hashicorp/terraform/states/statefile" ) // Type binary represents the combination of a compiled binary @@ -48,6 +51,12 @@ func NewBinary(binaryPath, workingDir string) *binary { return nil } + if filepath.Base(path) == ".exists" { + // We use this file just to let git know the "empty" fixture + // exists. It is not used by any test. + return nil + } + srcFn := path path, err = filepath.Rel(workingDir, path) @@ -170,43 +179,52 @@ func (b *binary) FileExists(path ...string) bool { // LocalState is a helper for easily reading the local backend's state file // terraform.tfstate from the working directory. -func (b *binary) LocalState() (*tfcore.State, error) { +func (b *binary) LocalState() (*states.State, error) { f, err := b.OpenFile("terraform.tfstate") if err != nil { return nil, err } defer f.Close() - return tfcore.ReadState(f) + + stateFile, err := statefile.Read(f) + if err != nil { + return nil, fmt.Errorf("Error reading statefile: %s", err) + } + return stateFile.State, nil } // Plan is a helper for easily reading a plan file from the working directory. -func (b *binary) Plan(path ...string) (*tfcore.Plan, error) { - f, err := b.OpenFile(path...) +func (b *binary) Plan(path string) (*plans.Plan, error) { + path = b.Path(path) + pr, err := planfile.Open(path) if err != nil { return nil, err } - defer f.Close() - return tfcore.ReadPlan(f) + plan, err := pr.ReadPlan() + if err != nil { + return nil, err + } + return plan, nil } // SetLocalState is a helper for easily writing to the file the local backend // uses for state in the working directory. This does not go through the // actual local backend code, so processing such as management of serials // does not apply and the given state will simply be written verbatim. -func (b *binary) SetLocalState(state *tfcore.State) error { +func (b *binary) SetLocalState(state *states.State) error { path := b.Path("terraform.tfstate") f, err := os.OpenFile(path, os.O_TRUNC|os.O_CREATE|os.O_WRONLY, os.ModePerm) if err != nil { - return err + return fmt.Errorf("failed to create temporary state file %s: %s", path, err) } - defer func() { - err := f.Close() - if err != nil { - panic(fmt.Sprintf("failed to close state file after writing: %s", err)) - } - }() + defer f.Close() - return tfcore.WriteState(state, f) + sf := &statefile.File{ + Serial: 0, + Lineage: "fake-for-testing", + State: state, + } + return statefile.Write(sf, f) } // Close cleans up the temporary resources associated with the object, diff --git a/plugin/discovery/get.go b/plugin/discovery/get.go index 1b70c5178..b1d01fb9a 100644 --- a/plugin/discovery/get.go +++ b/plugin/discovery/get.go @@ -298,7 +298,7 @@ func (i *ProviderInstaller) install(provider string, version Version, url string // check if the target dir exists, and create it if not var err error if _, StatErr := os.Stat(i.Dir); os.IsNotExist(StatErr) { - err = os.Mkdir(i.Dir, 0700) + err = os.MkdirAll(i.Dir, 0700) } if err != nil { return err