From f962d6c2ec43bfd9387b90bc98bb526e84839550 Mon Sep 17 00:00:00 2001 From: Paddy Date: Thu, 23 Apr 2020 04:37:57 -0700 Subject: [PATCH] Bring test runs back into a single process. Historically, tests ran by starting up the Go test framework, which would spin up a shim of Terraform (importing half of Terraform in the process), which would drive the provider code, then the test framework would inspect the state. This had some drawbacks, though; the shim of Terraform wasn't quite Terraform, and could behave in different ways sometimes. As the plugin SDK got separated from the Terraform codebase, this drift potential became worse. So we came up with the Binary Acceptance Testing driver, which would start up the Go test framework, which would shell out to a Terraform binary, which would start up a provider process to drive, and then the test framework would inspect the state back in the test process. This solved the issue it was addressing--we were now testing against production versions of Terraform, guaranteeing the tests would match what users would see--but it created some problems of its own. Our original model ran in a single process, and as a happy accident of that, it meant that go test could report coverage information and delve could be used to debug tests. Our binary acceptance testing model split everything across multiple processes, which broke coverage information (go test couldn't see the provider code being run anymore) and delve (delve also couldn't see the provider code). We didn't test, but suspect, that other tooling like race detection would also break. This led to coordinating with the Terraform core team to come up with a new process. We would still start up the Go test framework, it would still shell out to the Terraform binary, but instead of Terraform spinning up a new process for the provider, a provider server would be started in the same process as the Go test framework, and Terraform would be told to reconnect to it. This kept us using a production Terraform binary, so we don't need to worry about our shim drifting, but also kept our provider and test code in the same process, where it can be reached by delve or any of the other standard testing tooling available. This PR is adding support for that third approach. The pieces needed in go-plugin and terraform-plugin-test have been merged and released. The pieces in Terraform that are required were included in 0.12.26 and 0.13.0-beta.1. This is the final piece of the puzzle, the code that drives the tests. As a result of this reworking, the acctest package that was called from TestMain in providers is no longer needed, and is being removed as part of v2's breaking changes. Also, as a side effect from this work, providers can be debugged even outside of testing. To support that, the plugin.DebugServe helper was added, though a more friendly API may be needed. Providers can include that in their main package, with the recommendation being to switch between plugin.DebugServe and plugin.Serve based on a flag, with the default being plugin.Serve. This would allow production versions of binaries to have debuggers attached to them, by starting the binary in debug mode with the debugger attached, then having Terraform reconnect and drive it. There are a few differences under debug mode. Terraform performs several graph walks per command, and usually starts a provider process at the beginning of each graph walk and kills the process at the end of the graph walk. Because these graph walks have no hooks, it's impossible to support this when the server is started outside of Terraform's control. So when in debug mode, Terraform will not care about the provider lifecycle at all; it will not start the provider, nor will it kill the provider process after graph walks. This means global mutable state will be longer lived than it would be in non-debug mode. Terraform init also will not do anything with providers that are in debug mode; it will not attempt to fetch binaries, match versions, or find binaries on the file system. The information provided out of band when the provider is in debug mode is all Terraform needs to operate, so it will not bother trying to find any other information. --- .circleci/config.yml | 2 + acctest/doc.go | 24 ---- acctest/helper.go | 26 ---- go.mod | 10 +- go.sum | 29 +++-- helper/resource/plugin.go | 131 ++++++++++++++++++++ helper/resource/testing.go | 32 +++-- helper/resource/testing_new.go | 41 ++++-- helper/resource/testing_new_config.go | 75 +++++++++-- helper/resource/testing_new_import_state.go | 36 ++++-- plugin/debug.go | 102 +++++++++++++++ plugin/serve.go | 14 ++- terraform/state.go | 9 +- 13 files changed, 417 insertions(+), 114 deletions(-) delete mode 100644 acctest/doc.go delete mode 100644 acctest/helper.go create mode 100644 helper/resource/plugin.go create mode 100644 plugin/debug.go diff --git a/.circleci/config.yml b/.circleci/config.yml index 02e089ba744..c1d748dd469 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -23,6 +23,8 @@ jobs: "docker-go114 test": docker: - image: circleci/golang:1.14 + environment: + TF_ACC_TERRAFORM_VERSION: "0.12.26" parameters: test_results: type: string diff --git a/acctest/doc.go b/acctest/doc.go deleted file mode 100644 index b4baab1b436..00000000000 --- a/acctest/doc.go +++ /dev/null @@ -1,24 +0,0 @@ -// Package acctest provides the ability to use the binary test driver. The binary -// test driver allows you to run your acceptance tests with a binary of Terraform. -// This is currently the only mechanism for driving tests. It provides a realistic testing -// experience and matrix testing against multiple versions of Terraform CLI, -// as long as they are >= 0.12.0 -// -// The driver must be enabled by initialising the test helper in your TestMain -// function in all provider packages that run acceptance tests. Most providers have only -// one package. -// -// After importing this package, you must define a TestMain and have the following: -// -// func TestMain(m *testing.M) { -// acctest.UseBinaryDriver("provider_name", Provider) -// resource.TestMain(m) -// } -// -// Where `Provider` is the function that returns the instance of a configured `*schema.Provider` -// Some providers already have a TestMain defined, usually for the purpose of enabling test -// sweepers. These additional occurrences should be removed. -// -// It is no longer necessary to import other Terraform providers as Go modules: these -// imports should be removed. -package acctest diff --git a/acctest/helper.go b/acctest/helper.go deleted file mode 100644 index 9853512d65b..00000000000 --- a/acctest/helper.go +++ /dev/null @@ -1,26 +0,0 @@ -package acctest - -import ( - "os" - - "github.com/hashicorp/terraform-plugin-sdk/v2/plugin" - tftest "github.com/hashicorp/terraform-plugin-test" -) - -var TestHelper *tftest.Helper - -func UseBinaryDriver(name string, providerFunc plugin.ProviderFunc) { - sourceDir, err := os.Getwd() - if err != nil { - panic(err) - } - - if tftest.RunningAsPlugin() { - plugin.Serve(&plugin.ServeOpts{ - ProviderFunc: providerFunc, - }) - os.Exit(0) - } else { - TestHelper = tftest.AutoInitProviderHelper(name, sourceDir) - } -} diff --git a/go.mod b/go.mod index 1598f95b3c8..a23844ca0e6 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 // indirect github.com/apparentlymart/go-cidr v1.0.1 github.com/apparentlymart/go-dump v0.0.0-20190214190832-042adf3cf4a0 + github.com/aws/aws-sdk-go v1.25.3 // indirect github.com/davecgh/go-spew v1.1.1 github.com/go-test/deep v1.0.3 github.com/golang/mock v1.3.1 @@ -16,15 +17,16 @@ require ( github.com/hashicorp/errwrap v1.0.0 github.com/hashicorp/go-cleanhttp v0.5.1 github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 - github.com/hashicorp/go-hclog v0.9.2 // indirect + github.com/hashicorp/go-getter v1.4.2-0.20200106182914-9813cbd4eb02 // indirect + github.com/hashicorp/go-hclog v0.9.2 github.com/hashicorp/go-multierror v1.0.0 - github.com/hashicorp/go-plugin v1.2.0 + github.com/hashicorp/go-plugin v1.3.0 github.com/hashicorp/go-uuid v1.0.1 github.com/hashicorp/go-version v1.2.0 - github.com/hashicorp/hcl/v2 v2.0.0 + github.com/hashicorp/hcl/v2 v2.3.0 github.com/hashicorp/logutils v1.0.0 github.com/hashicorp/terraform-json v0.4.0 - github.com/hashicorp/terraform-plugin-test v1.3.0 + github.com/hashicorp/terraform-plugin-test v1.4.0 github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d // indirect github.com/keybase/go-crypto v0.0.0-20161004153544-93f5b35093ba github.com/kylelemons/godebug v1.1.0 // indirect diff --git a/go.sum b/go.sum index 6476bb1f30d..19fabbffbec 100644 --- a/go.sum +++ b/go.sum @@ -16,7 +16,6 @@ github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 h1:w1UutsfOrms1J05zt7I github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412/go.mod h1:WPjqKcmVOxf0XSf3YxCJs6N6AOSrOx3obionmG7T0y0= github.com/apparentlymart/go-cidr v1.0.1 h1:NmIwLZ/KdsjIUlhf+/Np40atNXm/+lZ5txfTJ/SpF+U= github.com/apparentlymart/go-cidr v1.0.1/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc= -github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3 h1:ZSTrOEhiM5J5RFxEaFvMZVEAM1KvT1YzbEOwB2EAGjA= github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM= github.com/apparentlymart/go-dump v0.0.0-20190214190832-042adf3cf4a0 h1:MzVXffFUye+ZcSR6opIgz9Co7WcDx6ZcY+RjfFHoA0I= github.com/apparentlymart/go-dump v0.0.0-20190214190832-042adf3cf4a0/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM= @@ -25,6 +24,8 @@ github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/ github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec= github.com/aws/aws-sdk-go v1.15.78 h1:LaXy6lWR0YK7LKyuU0QWy2ws/LWTPfYV/UgfiBu4tvY= github.com/aws/aws-sdk-go v1.15.78/go.mod h1:E3/ieXAlvM0XWO57iftYVDLLvQ824smPP3ATZkfNZeM= +github.com/aws/aws-sdk-go v1.25.3 h1:uM16hIw9BotjZKMZlX05SN2EFtaWfi/NonPKIARiBLQ= +github.com/aws/aws-sdk-go v1.25.3/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas= github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -35,6 +36,7 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= @@ -47,7 +49,6 @@ github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFU github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.4 h1:87PNWwrRvUSnqS4dlcBU/ftvOIBep4sYuBLlh6rX2wk= @@ -76,13 +77,15 @@ github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 h1:1/D3zfFHttUK github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320/go.mod h1:EiZBMaudVLy8fmjf9Npq1dq9RalhveqZG5w/yz3mHWs= github.com/hashicorp/go-getter v1.4.0 h1:ENHNi8494porjD0ZhIrjlAHnveSFhY7hvOJrV/fsKkw= github.com/hashicorp/go-getter v1.4.0/go.mod h1:7qxyCd8rBfcShwsvxgIguu4KbS3l8bUCwg2Umn7RjeY= +github.com/hashicorp/go-getter v1.4.2-0.20200106182914-9813cbd4eb02 h1:l1KB3bHVdvegcIf5upQ5mjcHjs2qsWnKh4Yr9xgIuu8= +github.com/hashicorp/go-getter v1.4.2-0.20200106182914-9813cbd4eb02/go.mod h1:7qxyCd8rBfcShwsvxgIguu4KbS3l8bUCwg2Umn7RjeY= github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-plugin v1.2.0 h1:CUfYokW0EJNDcGecVrHZK//Cp1GFlHwoqtcUIEiU6BY= -github.com/hashicorp/go-plugin v1.2.0/go.mod h1:F9eH4LrE/ZsRdbwhfjs9k9HoDUwAHnYtXdgmf1AVNs0= +github.com/hashicorp/go-plugin v1.3.0 h1:4d/wJojzvHV1I4i/rrjVaeuyxWrLzDE1mDCyDy8fXS8= +github.com/hashicorp/go-plugin v1.3.0/go.mod h1:F9eH4LrE/ZsRdbwhfjs9k9HoDUwAHnYtXdgmf1AVNs0= github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo= github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I= github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE= @@ -93,14 +96,14 @@ github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09 github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/hcl/v2 v2.0.0 h1:efQznTz+ydmQXq3BOnRa3AXzvCeTq1P4dKj/z5GLlY8= -github.com/hashicorp/hcl/v2 v2.0.0/go.mod h1:oVVDG71tEinNGYCxinCYadcmKU9bglqW9pV3txagJ90= +github.com/hashicorp/hcl/v2 v2.3.0 h1:iRly8YaMwTBAKhn1Ybk7VSdzbnopghktCD031P8ggUE= +github.com/hashicorp/hcl/v2 v2.3.0/go.mod h1:d+FwDBbOLvpAM3Z6J7gPj/VoAGkNe/gm352ZhjJ/Zv8= github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/terraform-json v0.4.0 h1:KNh29iNxozP5adfUFBJ4/fWd0Cu3taGgjHB38JYqOF4= github.com/hashicorp/terraform-json v0.4.0/go.mod h1:eAbqb4w0pSlRmdvl8fOyHAi/+8jnkVYN28gJkSJrLhU= -github.com/hashicorp/terraform-plugin-test v1.3.0 h1:hU5LoxrOn9qvOo+LTKN6mSav2J+dAMprbdxJPEQvp4U= -github.com/hashicorp/terraform-plugin-test v1.3.0/go.mod h1:QIJHYz8j+xJtdtLrFTlzQVC0ocr3rf/OjIpgZLK56Hs= +github.com/hashicorp/terraform-plugin-test v1.4.0 h1:9/eoY48ZGgcjkSkxIvUD7MGopFYeSv/2wmS09wSZg2I= +github.com/hashicorp/terraform-plugin-test v1.4.0/go.mod h1:QIJHYz8j+xJtdtLrFTlzQVC0ocr3rf/OjIpgZLK56Hs= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ= github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= @@ -108,6 +111,8 @@ github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZ github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8 h1:12VvqtR6Aowv3l/EQUlocDHW2Cp4G9WJVH7uyH8QFJE= github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/keybase/go-crypto v0.0.0-20161004153544-93f5b35093ba h1:NARVGAAgEXvoMeNPHhPFt1SBt1VMznA3Gnz9d0qj+co= github.com/keybase/go-crypto v0.0.0-20161004153544-93f5b35093ba/go.mod h1:ghbZscTyKdM07+Fw3KSi0hcJm+AlEUWj8QLlPtijN/M= @@ -155,15 +160,12 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/ulikunitz/xz v0.5.5 h1:pFrO0lVpTBXLpYw+pnLj6TbvHuyjXMfjGeCwSqCVwok= github.com/ulikunitz/xz v0.5.5/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= -github.com/vmihailenco/msgpack v3.3.3+incompatible h1:wapg9xDUZDzGCNFlwc5SqI1rvcciqcxEHac4CYj89xI= github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/vmihailenco/msgpack v4.0.1+incompatible h1:RMF1enSPeKTlXrXdOcqjFUElywVZjjC6pqse21bKbEU= github.com/vmihailenco/msgpack v4.0.1+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= -github.com/zclconf/go-cty v1.1.0 h1:uJwc9HiBOCpoKIObTQaLR+tsEXx1HBHnOsOOpcdhZgw= -github.com/zclconf/go-cty v1.1.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s= +github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= github.com/zclconf/go-cty v1.2.1 h1:vGMsygfmeCl4Xb6OA5U5XVAaQZ69FvoG7X2jUtQujb8= github.com/zclconf/go-cty v1.2.1/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= -go.opencensus.io v0.21.0 h1:mU6zScU4U1YAFPHEHYk+3JC4SY7JxgkqS10ZOSyksNg= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= @@ -229,7 +231,6 @@ golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135 h1:5Beo0mZN8dRzgrMMkDp0jc8YXQKx9DiJ2k1dkvGsn5A= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0 h1:Dh6fw+p6FyRl5x/FvNswO1ji0lIGzm3KP8Y9VkS9PTE= @@ -251,7 +252,6 @@ google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRn google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200310143817-43be25429f5a h1:lRlI5zu6AFy3iU/F8YWyNrAmn/tPCnhiTxfwhWb76eU= google.golang.org/genproto v0.0.0-20200310143817-43be25429f5a/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= @@ -259,7 +259,6 @@ google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEd google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.23.0 h1:AzbTB6ux+okLTzP8Ru1Xs41C303zdcfEht7MQnYJt5A= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1 h1:zvIju4sqAGvwKspUQOhwnpcqSbzi7/H6QomNNjTL4sk= diff --git a/helper/resource/plugin.go b/helper/resource/plugin.go new file mode 100644 index 00000000000..a980a3072ae --- /dev/null +++ b/helper/resource/plugin.go @@ -0,0 +1,131 @@ +package resource + +import ( + "context" + "encoding/json" + "io/ioutil" + "log" + "os" + "strings" + + "github.com/hashicorp/go-hclog" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + grpcplugin "github.com/hashicorp/terraform-plugin-sdk/v2/internal/helper/plugin" + proto "github.com/hashicorp/terraform-plugin-sdk/v2/internal/tfplugin5" + "github.com/hashicorp/terraform-plugin-sdk/v2/plugin" + tftest "github.com/hashicorp/terraform-plugin-test" +) + +func runProviderCommand(f func() error, wd *tftest.WorkingDir, opts *plugin.ServeOpts) error { + // Run the provider in the same process as the test runner using the + // reattach behavior in Terraform. This ensures we get test coverage + // and enables the use of delve as a debugger. + + // the provider name is technically supposed to be specified in the + // format returned by addrs.Provider.GetDisplay(), but 1. I'm not + // importing the entire addrs package for this and 2. we only get the + // provider name here. Fortunately, when only a provider name is + // specified in a provider block--which is how the config file we + // generate does things--Terraform just automatically assumes it's in + // the legacy namespace and the default registry.terraform.io host, + // so we can just construct the output of GetDisplay() ourselves, based + // on the provider name. + providerName := wd.GetHelper().GetPluginName() + + // providerName gets returned as terraform-provider-foo, and we need + // just foo. So let's fix that. + providerName = strings.TrimPrefix(providerName, "terraform-provider-") + + // if we didn't override the logger, let's set a default one. + if opts.Logger == nil { + opts.Logger = hclog.New(&hclog.LoggerOptions{ + Name: "plugintest", + Level: hclog.Trace, + Output: ioutil.Discard, + }) + } + + // this is needed so Terraform doesn't default to expecting protocol 4; + // we're skipping the handshake because Terraform didn't launch the + // plugin. + os.Setenv("PLUGIN_PROTOCOL_VERSIONS", "5") + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + config, closeCh, err := plugin.DebugServe(ctx, opts) + if err != nil { + return err + } + + reattachInfo := map[string]plugin.ReattachConfig{} + var namespaces []string + host := "registry.terraform.io" + if v := os.Getenv("TF_ACC_PROVIDER_NAMESPACE"); v != "" { + namespaces = append(namespaces, v) + } else { + // unfortunately, we need to populate both of them + // Terraform 0.12.26 and higher uses the legacy mode ("-") + // Terraform 0.13.0 and higher uses the default mode ("hashicorp") + // because of the change in how providers are addressed in 0.13 + namespaces = append(namespaces, "-", "hashicorp") + } + if v := os.Getenv("TF_ACC_PROVIDER_HOST"); v != "" { + host = v + } + + for _, ns := range namespaces { + reattachInfo[strings.TrimSuffix(host, "/")+"/"+ + strings.TrimSuffix(ns, "/")+"/"+ + providerName] = config + } + + reattachStr, err := json.Marshal(reattachInfo) + if err != nil { + return err + } + wd.Setenv("TF_REATTACH_PROVIDERS", string(reattachStr)) + + // ok, let's call whatever Terraform command the test was trying to + // call, now that we know it'll attach back to that server we just + // started. + err = f() + if err != nil { + log.Printf("[WARN] Got error running Terraform: %s", err) + } + + // cancel the server so it'll return. Otherwise, this closeCh won't get + // closed, and we'll hang here. + cancel() + + // wait for the server to actually shut down; it may take a moment for + // it to clean up, or whatever. + <-closeCh + + // once we've run the Terraform command, let's remove the reattach + // information from the WorkingDir's environment. The WorkingDir will + // persist until the next call, but the server in the reattach info + // doesn't exist anymore at this point, so the reattach info is no + // longer valid. In theory it should be overwritten in the next call, + // but just to avoid any confusing bug reports, let's just unset the + // environment variable altogether. + wd.Unsetenv("TF_REATTACH_PROVIDERS") + + // return any error returned from the orchestration code running + // Terraform commands + return err +} + +// defaultPluginServeOpts builds ths *plugin.ServeOpts that you usually want to +// use when running runProviderCommand. It just sets the ProviderFunc to return +// the provider under test. +func defaultPluginServeOpts(wd *tftest.WorkingDir, providers map[string]*schema.Provider) *plugin.ServeOpts { + var provider *schema.Provider + for _, p := range providers { + provider = p + } + return &plugin.ServeOpts{ + GRPCProviderFunc: func() proto.ProviderServer { + return grpcplugin.NewGRPCProviderServer(provider) + }, + } +} diff --git a/helper/resource/testing.go b/helper/resource/testing.go index 6f74bcdc456..3eb59891bdc 100644 --- a/helper/resource/testing.go +++ b/helper/resource/testing.go @@ -16,9 +16,9 @@ import ( "github.com/hashicorp/go-multierror" "github.com/hashicorp/logutils" + tftest "github.com/hashicorp/terraform-plugin-test" testing "github.com/mitchellh/go-testing-interface" - "github.com/hashicorp/terraform-plugin-sdk/v2/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/logging" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/internal/addrs" @@ -102,16 +102,7 @@ func TestMain(m interface { os.Exit(1) } } else { - if acctest.TestHelper == nil { - log.Fatal("Please configure the acctest binary driver") - } - exitCode := m.Run() - err := acctest.TestHelper.Close() - if err != nil { - log.Printf("Error cleaning up temporary test files: %s", err) - } - os.Exit(exitCode) } } @@ -544,15 +535,22 @@ func Test(t testing.T, c TestCase) { // get instances of all providers, so we can use the individual // resources to shim the state during the tests. providers := make(map[string]*schema.Provider) + var provider string for name, pf := range c.ProviderFactories { p, err := pf() if err != nil { t.Fatal(err) } providers[name] = p + provider = name } for name, p := range c.Providers { providers[name] = p + provider = name + } + + if len(providers) != 1 { + t.Fatalf("Only the provider under test should be set in TestCase, got %d providers. Other providers can be used by adding their provider blocks to their config; they will automatically be downloaded as part of terraform init.", len(providers)) } // Auto-configure all providers. @@ -570,11 +568,19 @@ func Test(t testing.T, c TestCase) { c.PreCheck() } - if acctest.TestHelper == nil { - t.Fatal("Please configure the acctest binary driver") + sourceDir, err := os.Getwd() + if err != nil { + t.Fatalf("Error getting working dir: %s", err) } + helper := tftest.AutoInitProviderHelper(provider, sourceDir) + defer func(helper *tftest.Helper) { + err := helper.Close() + if err != nil { + log.Printf("Error cleaning up temporary test files: %s", err) + } + }(helper) - runNewTest(t, c, providers) + runNewTest(t, c, providers, helper) } // testProviderConfig takes the list of Providers in a TestCase and returns a diff --git a/helper/resource/testing_new.go b/helper/resource/testing_new.go index 42a6dcf37de..fe0bb388dc9 100644 --- a/helper/resource/testing_new.go +++ b/helper/resource/testing_new.go @@ -11,16 +11,22 @@ import ( tftest "github.com/hashicorp/terraform-plugin-test" testing "github.com/mitchellh/go-testing-interface" - "github.com/hashicorp/terraform-plugin-sdk/v2/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) -func runPostTestDestroy(t testing.T, c TestCase, wd *tftest.WorkingDir) error { - wd.RequireDestroy(t) +func runPostTestDestroy(t testing.T, c TestCase, wd *tftest.WorkingDir, providers map[string]*schema.Provider) error { + runProviderCommand(func() error { + wd.RequireDestroy(t) + return nil + }, wd, defaultPluginServeOpts(wd, providers)) if c.CheckDestroy != nil { - statePostDestroy := getState(t, wd) + var statePostDestroy *terraform.State + runProviderCommand(func() error { + statePostDestroy = getState(t, wd) + return nil + }, wd, defaultPluginServeOpts(wd, providers)) if err := c.CheckDestroy(statePostDestroy); err != nil { return err @@ -30,16 +36,20 @@ func runPostTestDestroy(t testing.T, c TestCase, wd *tftest.WorkingDir) error { return nil } -func runNewTest(t testing.T, c TestCase, providers map[string]*schema.Provider) { +func runNewTest(t testing.T, c TestCase, providers map[string]*schema.Provider, helper *tftest.Helper) { spewConf := spew.NewDefaultConfig() spewConf.SortKeys = true - wd := acctest.TestHelper.RequireNewWorkingDir(t) + wd := helper.RequireNewWorkingDir(t) defer func() { - statePreDestroy := getState(t, wd) + var statePreDestroy *terraform.State + runProviderCommand(func() error { + statePreDestroy = getState(t, wd) + return nil + }, wd, defaultPluginServeOpts(wd, providers)) if !stateIsEmpty(statePreDestroy) { - runPostTestDestroy(t, c, wd) + runPostTestDestroy(t, c, wd, providers) } wd.Close() @@ -48,14 +58,16 @@ func runNewTest(t testing.T, c TestCase, providers map[string]*schema.Provider) providerCfg := testProviderConfig(c) wd.RequireSetConfig(t, providerCfg) - wd.RequireInit(t) + runProviderCommand(func() error { + wd.RequireInit(t) + return nil + }, wd, defaultPluginServeOpts(wd, providers)) // use this to track last step succesfully applied // acts as default for import tests var appliedCfg string for i, step := range c.Steps { - if step.PreConfig != nil { step.PreConfig() } @@ -70,10 +82,10 @@ func runNewTest(t testing.T, c TestCase, providers map[string]*schema.Provider) continue } } + step.providers = providers if step.ImportState { - step.providers = providers - err := testStepNewImportState(t, c, wd, step, appliedCfg) + err := testStepNewImportState(t, c, helper, wd, step, appliedCfg) if err != nil { t.Fatal(err) } @@ -149,7 +161,10 @@ func testIDRefresh(c TestCase, t testing.T, wd *tftest.WorkingDir, step TestStep defer wd.RequireSetConfig(t, step.Config) // Refresh! - wd.RequireRefresh(t) + runProviderCommand(func() error { + wd.RequireRefresh(t) + return nil + }, wd, defaultPluginServeOpts(wd, step.providers)) state = getState(t, wd) // Verify attribute equivalence. diff --git a/helper/resource/testing_new_config.go b/helper/resource/testing_new_config.go index 285d50e608f..434428237b9 100644 --- a/helper/resource/testing_new_config.go +++ b/helper/resource/testing_new_config.go @@ -2,6 +2,7 @@ package resource import ( "github.com/davecgh/go-spew/spew" + tfjson "github.com/hashicorp/terraform-json" tftest "github.com/hashicorp/terraform-plugin-test" testing "github.com/mitchellh/go-testing-interface" @@ -16,7 +17,14 @@ func testStepNewConfig(t testing.T, c TestCase, wd *tftest.WorkingDir, step Test idRefresh := c.IDRefreshName != "" if !step.Destroy { - state := getState(t, wd) + var state *terraform.State + err := runProviderCommand(func() error { + state = getState(t, wd) + return nil + }, wd, defaultPluginServeOpts(wd, step.providers)) + if err != nil { + return err + } if err := testStepTaint(state, step); err != nil { t.Fatalf("Error when tainting resources: %s", err) } @@ -25,12 +33,21 @@ func testStepNewConfig(t testing.T, c TestCase, wd *tftest.WorkingDir, step Test wd.RequireSetConfig(t, step.Config) if !step.PlanOnly { - err := wd.Apply() + err := runProviderCommand(func() error { + return wd.Apply() + }, wd, defaultPluginServeOpts(wd, step.providers)) if err != nil { return err } - state := getState(t, wd) + var state *terraform.State + err = runProviderCommand(func() error { + state = getState(t, wd) + return nil + }, wd, defaultPluginServeOpts(wd, step.providers)) + if err != nil { + return err + } if step.Check != nil { state.IsBinaryDrivenTest = true if err := step.Check(state); err != nil { @@ -42,8 +59,22 @@ func testStepNewConfig(t testing.T, c TestCase, wd *tftest.WorkingDir, step Test // Test for perpetual diffs by performing a plan, a refresh, and another plan // do a plan - wd.RequireCreatePlan(t) - plan := wd.RequireSavedPlan(t) + err := runProviderCommand(func() error { + wd.RequireCreatePlan(t) + return nil + }, wd, defaultPluginServeOpts(wd, step.providers)) + if err != nil { + return err + } + + var plan *tfjson.Plan + err = runProviderCommand(func() error { + plan = wd.RequireSavedPlan(t) + return nil + }, wd, defaultPluginServeOpts(wd, step.providers)) + if err != nil { + return err + } if !planIsEmpty(plan) { if step.ExpectNonEmptyPlan { @@ -56,12 +87,31 @@ func testStepNewConfig(t testing.T, c TestCase, wd *tftest.WorkingDir, step Test // do a refresh if !c.PreventPostDestroyRefresh { - wd.RequireRefresh(t) + err := runProviderCommand(func() error { + wd.RequireRefresh(t) + return nil + }, wd, defaultPluginServeOpts(wd, step.providers)) + if err != nil { + return err + } } // do another plan - wd.RequireCreatePlan(t) - plan = wd.RequireSavedPlan(t) + err = runProviderCommand(func() error { + wd.RequireCreatePlan(t) + return nil + }, wd, defaultPluginServeOpts(wd, step.providers)) + if err != nil { + return err + } + + err = runProviderCommand(func() error { + plan = wd.RequireSavedPlan(t) + return nil + }, wd, defaultPluginServeOpts(wd, step.providers)) + if err != nil { + return err + } // check if plan is empty if !planIsEmpty(plan) { @@ -76,7 +126,14 @@ func testStepNewConfig(t testing.T, c TestCase, wd *tftest.WorkingDir, step Test // ID-ONLY REFRESH // If we've never checked an id-only refresh and our state isn't // empty, find the first resource and test it. - state := getState(t, wd) + var state *terraform.State + err = runProviderCommand(func() error { + state = getState(t, wd) + return nil + }, wd, defaultPluginServeOpts(wd, step.providers)) + if err != nil { + return err + } if idRefresh && idRefreshCheck == nil && !state.Empty() { // Find the first non-nil resource in the state for _, m := range state.Modules { diff --git a/helper/resource/testing_new_import_state.go b/helper/resource/testing_new_import_state.go index ec1bc35b2d3..9ce1bb8b25f 100644 --- a/helper/resource/testing_new_import_state.go +++ b/helper/resource/testing_new_import_state.go @@ -8,11 +8,10 @@ import ( tftest "github.com/hashicorp/terraform-plugin-test" testing "github.com/mitchellh/go-testing-interface" - "github.com/hashicorp/terraform-plugin-sdk/v2/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) -func testStepNewImportState(t testing.T, c TestCase, wd *tftest.WorkingDir, step TestStep, cfg string) error { +func testStepNewImportState(t testing.T, c TestCase, helper *tftest.Helper, wd *tftest.WorkingDir, step TestStep, cfg string) error { spewConf := spew.NewDefaultConfig() spewConf.SortKeys = true @@ -21,7 +20,14 @@ func testStepNewImportState(t testing.T, c TestCase, wd *tftest.WorkingDir, step } // get state from check sequence - state := getState(t, wd) + var state *terraform.State + err := runProviderCommand(func() error { + state = getState(t, wd) + return nil + }, wd, defaultPluginServeOpts(wd, step.providers)) + if err != nil { + t.Fatalf("Error getting state: %s", err) + } // Determine the ID to import var importId string @@ -50,12 +56,28 @@ func testStepNewImportState(t testing.T, c TestCase, wd *tftest.WorkingDir, step t.Fatal("Cannot import state with no specified config") } } - importWd := acctest.TestHelper.RequireNewWorkingDir(t) + importWd := helper.RequireNewWorkingDir(t) defer importWd.Close() importWd.RequireSetConfig(t, step.Config) - importWd.RequireInit(t) - importWd.RequireImport(t, step.ResourceName, importId) - importState := getState(t, wd) + runProviderCommand(func() error { + importWd.RequireInit(t) + return nil + }, importWd, defaultPluginServeOpts(importWd, step.providers)) + err = runProviderCommand(func() error { + importWd.RequireImport(t, step.ResourceName, importId) + return nil + }, importWd, defaultPluginServeOpts(importWd, step.providers)) + if err != nil { + t.Fatalf("Error running import: %s", err) + } + var importState *terraform.State + err = runProviderCommand(func() error { + importState = getState(t, wd) + return nil + }, wd, defaultPluginServeOpts(wd, step.providers)) + if err != nil { + t.Fatalf("Error getting state: %s", err) + } // Go through the imported state and verify if step.ImportStateCheck != nil { diff --git a/plugin/debug.go b/plugin/debug.go new file mode 100644 index 00000000000..6f30f1d1e9e --- /dev/null +++ b/plugin/debug.go @@ -0,0 +1,102 @@ +package plugin + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "os" + "os/signal" + "time" + + "github.com/hashicorp/go-plugin" +) + +// ReattachConfig holds the information Terraform needs to be able to attach +// itself to a provider process, so it can drive the process. +type ReattachConfig struct { + Protocol string + Pid int + Test bool + Addr ReattachConfigAddr +} + +// ReattachConfigAddr is a JSON-encoding friendly version of net.Addr. +type ReattachConfigAddr struct { + Network string + String string +} + +// DebugServe starts a plugin server in debug mode; this should only be used +// when the provider will manage its own lifecycle. It is not recommended for +// normal usage; Serve is the correct function for that. +func DebugServe(ctx context.Context, opts *ServeOpts) (ReattachConfig, <-chan struct{}, error) { + reattachCh := make(chan *plugin.ReattachConfig) + closeCh := make(chan struct{}) + + opts.TestConfig = &plugin.ServeTestConfig{ + Context: ctx, + ReattachConfigCh: reattachCh, + CloseCh: closeCh, + } + + go Serve(opts) + + var config *plugin.ReattachConfig + select { + case config = <-reattachCh: + case <-time.After(2 * time.Second): + return ReattachConfig{}, closeCh, errors.New("timeout waiting on reattach config") + } + + if config == nil { + return ReattachConfig{}, closeCh, errors.New("nil reattach config received") + } + + return ReattachConfig{ + Protocol: string(config.Protocol), + Pid: config.Pid, + Test: config.Test, + Addr: ReattachConfigAddr{ + Network: config.Addr.Network(), + String: config.Addr.String(), + }, + }, closeCh, nil +} + +// Debug starts a debug server and controls its lifecycle, printing the +// information needed for Terraform to connect to the provider to stdout. +// os.Interrupt will be captured and used to stop the server. +func Debug(ctx context.Context, providerAddr string, opts *ServeOpts) error { + ctx, cancel := context.WithCancel(ctx) + // Ctrl-C will stop the server + sigCh := make(chan os.Signal, 1) + signal.Notify(sigCh, os.Interrupt) + defer func() { + signal.Stop(sigCh) + cancel() + }() + config, closeCh, err := DebugServe(ctx, opts) + if err != nil { + return fmt.Errorf("Error launching debug server: %w", err) + } + go func() { + select { + case <-sigCh: + cancel() + case <-ctx.Done(): + } + }() + reattachStr, err := json.Marshal(map[string]ReattachConfig{ + providerAddr: config, + }) + if err != nil { + return fmt.Errorf("Error building reattach string: %w", err) + } + + fmt.Printf("Provider server started; to attach Terraform, set TF_REATTACH_PROVIDERS to the following:\n%s\n", string(reattachStr)) + + // wait for the server to be done + <-closeCh + return nil +} diff --git a/plugin/serve.go b/plugin/serve.go index ca49542bfcb..e5649d07097 100644 --- a/plugin/serve.go +++ b/plugin/serve.go @@ -3,6 +3,7 @@ package plugin import ( "context" + hclog "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-plugin" "google.golang.org/grpc" @@ -34,6 +35,16 @@ type ServeOpts struct { // Wrapped versions of the above plugins will automatically shimmed and // added to the GRPC functions when possible. GRPCProviderFunc GRPCProviderFunc + + // Logger is the logger that go-plugin will use. + Logger hclog.Logger + + // TestConfig should only be set when the provider is being tested; it + // will opt out of go-plugin's lifecycle management and other features, + // and will use the supplied configuration options to control the + // plugin's lifecycle and communicate connection information. See the + // go-plugin GoDoc for more information. + TestConfig *plugin.ServeTestConfig } // Serve serves a plugin. This function never returns and should be the final @@ -48,7 +59,6 @@ func Serve(opts *ServeOpts) { } provider := opts.GRPCProviderFunc() - plugin.Serve(&plugin.ServeConfig{ HandshakeConfig: Handshake, VersionedPlugins: map[int]plugin.PluginSet{ @@ -66,5 +76,7 @@ func Serve(opts *ServeOpts) { return handler(ctx, req) }))...) }, + Logger: opts.Logger, + Test: opts.TestConfig, }) } diff --git a/terraform/state.go b/terraform/state.go index 56e974acb7a..87f7610a93d 100644 --- a/terraform/state.go +++ b/terraform/state.go @@ -6,6 +6,7 @@ import ( "encoding/json" "fmt" "log" + "os" "reflect" "sort" "strconv" @@ -611,9 +612,13 @@ func (s *State) ensureHasLineage() { panic(fmt.Errorf("Failed to generate lineage: %v", err)) } s.Lineage = lineage - log.Printf("[DEBUG] New state was assigned lineage %q\n", s.Lineage) + if os.Getenv("TF_ACC") == "" || os.Getenv("TF_ACC_STATE_LINEAGE") == "1" { + log.Printf("[DEBUG] New state was assigned lineage %q\n", s.Lineage) + } } else { - log.Printf("[TRACE] Preserving existing state lineage %q\n", s.Lineage) + if os.Getenv("TF_ACC") == "" || os.Getenv("TF_ACC_STATE_LINEAGE") == "1" { + log.Printf("[TRACE] Preserving existing state lineage %q\n", s.Lineage) + } } }