diff --git a/.travis.yml b/.travis.yml index d37048d92f..505baa95bd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,8 +2,7 @@ dist: trusty sudo: false language: go go: -- 1.8.3 -- 1.9rc1 +- 1.9 # add TF_CONSUL_TEST=1 to run consul tests # they were causing timouts in travis diff --git a/CHANGELOG.md b/CHANGELOG.md index b96efb6258..a086d26369 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,11 @@ IMPROVEMENTS: * backend/consul: can now set the path to a specific CA certificate file, client certificate file, and client key file that will be used when configuring the underlying Consul client. [GH-15405] * backend/http: now has optional support for locking, with special support from the target server. Additionally, the update operation can now optionally be implemented via `PUT` rather than `POST`. [GH-15793] +BUG FIXES: + +* cli: `terraform init` now verifies the required Terraform version from the root module config. Previously this was verified only on subsequent commands, after initialization. [GH-15935] +* cli: `terraform validate` now consults `terraform.tfvars`, if present, to set variable values. This is now consistent with the behavior of other commands. [GH-15938] + ## 0.10.2 (August 16, 2017) BUG FIXES: diff --git a/Makefile b/Makefile index c51b77e1d1..e5ca1dfbaa 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -TEST?=$$(go list ./... | grep -v '/terraform/vendor/' | grep -v '/builtin/bins/') +TEST?=./... GOFMT_FILES?=$$(find . -name '*.go' | grep -v vendor) default: test vet @@ -27,10 +27,11 @@ plugin-dev: generate mv $(GOPATH)/bin/$(PLUGIN) $(GOPATH)/bin/terraform-$(PLUGIN) # test runs the unit tests +# we run this one package at a time here because running the entire suite in +# one command creates memory usage issues when running in Travis-CI. test: fmtcheck generate go test -i $(TEST) || exit 1 - echo $(TEST) | \ - xargs -t -n4 go test $(TESTARGS) -timeout=60s -parallel=4 + go list $(TEST) | xargs -t -n4 go test $(TESTARGS) -timeout=60s -parallel=4 # testacc runs acceptance tests testacc: fmtcheck generate @@ -64,8 +65,8 @@ cover: # vet runs the Go source code static analysis tool `vet` to find # any common errors. vet: - @echo 'go vet $$(go list ./... | grep -v /terraform/vendor/)' - @go vet $$(go list ./... | grep -v /terraform/vendor/) ; if [ $$? -eq 1 ]; then \ + @echo 'go vet ./...' + @go vet ./... ; if [ $$? -eq 1 ]; then \ echo ""; \ echo "Vet found suspicious constructs. Please check the reported constructs"; \ echo "and fix them if necessary before submitting the code for review."; \ @@ -78,7 +79,7 @@ generate: @which stringer > /dev/null; if [ $$? -ne 0 ]; then \ go get -u golang.org/x/tools/cmd/stringer; \ fi - go generate $$(go list ./... | grep -v /terraform/vendor/) + go generate ./... @go fmt command/internal_plugin_list.go > /dev/null fmt: diff --git a/README.md b/README.md index a30ccff497..fb1e609d79 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ All documentation is available on the [Terraform website](http://www.terraform.i Developing Terraform -------------------- -If you wish to work on Terraform itself or any of its built-in providers, you'll first need [Go](http://www.golang.org) installed on your machine (version 1.8+ is *required*). Alternatively, you can use the Vagrantfile in the root of this repo to stand up a virtual machine with the appropriate dev tooling already set up for you. +If you wish to work on Terraform itself or any of its built-in providers, you'll first need [Go](http://www.golang.org) installed on your machine (version 1.9+ is *required*). Alternatively, you can use the Vagrantfile in the root of this repo to stand up a virtual machine with the appropriate dev tooling already set up for you. This repository contains only Terraform core, which includes the command line interface and the main graph engine. Providers are implemented as plugins that each have their own repository in [the `terraform-providers` organization](https://github.com/terraform-providers) on GitHub. Instructions for developing each provider are in the associated README file. For more information, see [the provider development overview](https://www.terraform.io/docs/plugins/provider.html). diff --git a/Vagrantfile b/Vagrantfile index 69d72df1bb..d140efb918 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -5,7 +5,7 @@ VAGRANTFILE_API_VERSION = "2" # Software version variables -GOVERSION = "1.8.3" +GOVERSION = "1.9" UBUNTUVERSION = "16.04" # CPU and RAM can be adjusted depending on your system diff --git a/backend/testing.go b/backend/testing.go index feac1c870b..deebfc00a0 100644 --- a/backend/testing.go +++ b/backend/testing.go @@ -13,6 +13,8 @@ import ( // TestBackendConfig validates and configures the backend with the // given configuration. func TestBackendConfig(t *testing.T, b Backend, c map[string]interface{}) Backend { + t.Helper() + // Get the proper config structure rc, err := config.NewRawConfig(c) if err != nil { @@ -45,6 +47,8 @@ func TestBackendConfig(t *testing.T, b Backend, c map[string]interface{}) Backen // If you want to test locking, two backends must be given. If b2 is nil, // then state lockign won't be tested. func TestBackend(t *testing.T, b1, b2 Backend) { + t.Helper() + testBackendStates(t, b1) if b2 != nil { @@ -53,6 +57,8 @@ func TestBackend(t *testing.T, b1, b2 Backend) { } func testBackendStates(t *testing.T, b Backend) { + t.Helper() + states, err := b.States() if err == ErrNamedStatesNotSupported { t.Logf("TestBackend: named states not supported in %T, skipping", b) @@ -231,6 +237,8 @@ func testBackendStates(t *testing.T, b Backend) { } func testBackendStateLock(t *testing.T, b1, b2 Backend) { + t.Helper() + // Get the default state for each b1StateMgr, err := b1.State(DefaultStateName) if err != nil { diff --git a/command/command_test.go b/command/command_test.go index 55bea29df7..598805a2cf 100644 --- a/command/command_test.go +++ b/command/command_test.go @@ -54,6 +54,8 @@ func TestMain(m *testing.M) { } func tempDir(t *testing.T) string { + t.Helper() + dir, err := ioutil.TempDir("", "tf") if err != nil { t.Fatalf("err: %s", err) @@ -99,6 +101,8 @@ func metaOverridesForProviderAndProvisioner(p terraform.ResourceProvider, pr ter } func testModule(t *testing.T, name string) *module.Tree { + t.Helper() + mod, err := module.NewTreeModule("", filepath.Join(fixtureDir, name)) if err != nil { t.Fatalf("err: %s", err) @@ -114,6 +118,8 @@ func testModule(t *testing.T, name string) *module.Tree { // testPlan returns a non-nil noop plan. func testPlan(t *testing.T) *terraform.Plan { + t.Helper() + state := terraform.NewState() state.RootModule().Outputs["foo"] = &terraform.OutputState{ Type: "string", @@ -127,6 +133,8 @@ func testPlan(t *testing.T) *terraform.Plan { } func testPlanFile(t *testing.T, plan *terraform.Plan) string { + t.Helper() + path := testTempFile(t) f, err := os.Create(path) @@ -143,6 +151,8 @@ func testPlanFile(t *testing.T, plan *terraform.Plan) string { } func testReadPlan(t *testing.T, path string) *terraform.Plan { + t.Helper() + f, err := os.Open(path) if err != nil { t.Fatalf("err: %s", err) @@ -180,6 +190,8 @@ func testState() *terraform.State { } func testStateFile(t *testing.T, s *terraform.State) string { + t.Helper() + path := testTempFile(t) f, err := os.Create(path) @@ -198,6 +210,8 @@ func testStateFile(t *testing.T, s *terraform.State) string { // testStateFileDefault writes the state out to the default statefile // in the cwd. Use `testCwd` to change into a temp cwd. func testStateFileDefault(t *testing.T, s *terraform.State) string { + t.Helper() + f, err := os.Create(DefaultStateFilename) if err != nil { t.Fatalf("err: %s", err) @@ -214,6 +228,8 @@ func testStateFileDefault(t *testing.T, s *terraform.State) string { // testStateFileRemote writes the state out to the remote statefile // in the cwd. Use `testCwd` to change into a temp cwd. func testStateFileRemote(t *testing.T, s *terraform.State) string { + t.Helper() + path := filepath.Join(DefaultDataDir, DefaultStateFilename) if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { t.Fatalf("err: %s", err) @@ -234,6 +250,8 @@ func testStateFileRemote(t *testing.T, s *terraform.State) string { // testStateRead reads the state from a file func testStateRead(t *testing.T, path string) *terraform.State { + t.Helper() + f, err := os.Open(path) if err != nil { t.Fatalf("err: %s", err) @@ -251,6 +269,8 @@ func testStateRead(t *testing.T, path string) *terraform.State { // testStateOutput tests that the state at the given path contains // the expected state string. func testStateOutput(t *testing.T, path string, expected string) { + t.Helper() + newState := testStateRead(t, path) actual := strings.TrimSpace(newState.String()) expected = strings.TrimSpace(expected) @@ -277,10 +297,14 @@ func testProvider() *terraform.MockResourceProvider { } func testTempFile(t *testing.T) string { + t.Helper() + return filepath.Join(testTempDir(t), "state.tfstate") } func testTempDir(t *testing.T) string { + t.Helper() + d, err := ioutil.TempDir("", "tf") if err != nil { t.Fatalf("err: %s", err) @@ -292,6 +316,8 @@ func testTempDir(t *testing.T) string { // testRename renames the path to new and returns a function to defer to // revert the rename. func testRename(t *testing.T, base, path, new string) func() { + t.Helper() + if base != "" { path = filepath.Join(base, path) new = filepath.Join(base, new) @@ -310,6 +336,8 @@ func testRename(t *testing.T, base, path, new string) func() { // testChdir changes the directory and returns a function to defer to // revert the old cwd. func testChdir(t *testing.T, new string) func() { + t.Helper() + old, err := os.Getwd() if err != nil { t.Fatalf("err: %s", err) @@ -328,6 +356,8 @@ func testChdir(t *testing.T, new string) func() { // testCwd is used to change the current working directory // into a test directory that should be remoted after func testCwd(t *testing.T) (string, string) { + t.Helper() + tmp, err := ioutil.TempDir("", "tf") if err != nil { t.Fatalf("err: %v", err) @@ -347,6 +377,8 @@ func testCwd(t *testing.T) (string, string) { // testFixCwd is used to as a defer to testDir func testFixCwd(t *testing.T, tmp, cwd string) { + t.Helper() + if err := os.Chdir(cwd); err != nil { t.Fatalf("err: %v", err) } @@ -362,6 +394,8 @@ func testFixCwd(t *testing.T, tmp, cwd string) { // The returned function should be deferred to properly clean up and restore // the original stdin. func testStdinPipe(t *testing.T, src io.Reader) func() { + t.Helper() + r, w, err := os.Pipe() if err != nil { t.Fatalf("err: %s", err) @@ -390,6 +424,8 @@ func testStdinPipe(t *testing.T, src io.Reader) func() { // not useful since the commands are configured to write to a cli.Ui, not // Stdout directly. Commands like `console` though use the raw stdout. func testStdoutCapture(t *testing.T, dst io.Writer) func() { + t.Helper() + r, w, err := os.Pipe() if err != nil { t.Fatalf("err: %s", err) @@ -424,6 +460,8 @@ func testStdoutCapture(t *testing.T, dst io.Writer) func() { // in order to interactive prompts. The returned function must be called // in a defer to clean up. func testInteractiveInput(t *testing.T, answers []string) func() { + t.Helper() + // Disable test mode so input is called test = false @@ -443,6 +481,8 @@ func testInteractiveInput(t *testing.T, answers []string) func() { // for calls to Input when the right question is asked. The key is the // question "Id" that is used. func testInputMap(t *testing.T, answers map[string]string) func() { + t.Helper() + // Disable test mode so input is called test = false @@ -465,6 +505,8 @@ func testInputMap(t *testing.T, answers map[string]string) func() { // backend. This returns the complete state that can be saved. Use // `testStateFileRemote` to write the returned state. func testBackendState(t *testing.T, s *terraform.State, c int) (*terraform.State, *httptest.Server) { + t.Helper() + var b64md5 string buf := bytes.NewBuffer(nil) @@ -507,6 +549,8 @@ func testBackendState(t *testing.T, s *terraform.State, c int) (*terraform.State // testRemoteState is used to make a test HTTP server to return a given // state file that can be used for testing legacy remote state. func testRemoteState(t *testing.T, s *terraform.State, c int) (*terraform.RemoteState, *httptest.Server) { + t.Helper() + var b64md5 string buf := bytes.NewBuffer(nil) diff --git a/command/e2etest/main_test.go b/command/e2etest/main_test.go index 61c64a30b9..64dc8f148e 100644 --- a/command/e2etest/main_test.go +++ b/command/e2etest/main_test.go @@ -51,6 +51,8 @@ func canAccessNetwork() bool { } func skipIfCannotAccessNetwork(t *testing.T) { + t.Helper() + if !canAccessNetwork() { t.Skip("network access not allowed; use TF_ACC=1 to enable") } diff --git a/command/init.go b/command/init.go index 3665b5b1d7..2de7d701d9 100644 --- a/command/init.go +++ b/command/init.go @@ -288,6 +288,11 @@ func (c *InitCommand) getProviders(path string, state *terraform.State, upgrade return err } + if err := terraform.CheckRequiredVersion(mod); err != nil { + c.Ui.Error(err.Error()) + return err + } + var available discovery.PluginMetaSet if upgrade { // If we're in upgrade mode, we ignore any auto-installed plugins diff --git a/command/init_test.go b/command/init_test.go index ef192b7e54..8b013bdaec 100644 --- a/command/init_test.go +++ b/command/init_test.go @@ -855,6 +855,27 @@ func TestInit_getProviderHaveLegacyVersion(t *testing.T) { } } +func TestInit_getProviderCheckRequiredVersion(t *testing.T) { + // Create a temporary working directory that is empty + td := tempDir(t) + copy.CopyDir(testFixturePath("init-check-required-version"), td) + defer os.RemoveAll(td) + defer testChdir(t, td)() + + ui := new(cli.MockUi) + c := &InitCommand{ + Meta: Meta{ + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, + }, + } + + args := []string{} + if code := c.Run(args); code != 1 { + t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) + } +} + func TestInit_providerLockFile(t *testing.T) { // Create a temporary working directory that is empty td := tempDir(t) diff --git a/command/test-fixtures/init-check-required-version/main.tf b/command/test-fixtures/init-check-required-version/main.tf new file mode 100644 index 0000000000..8f65421946 --- /dev/null +++ b/command/test-fixtures/init-check-required-version/main.tf @@ -0,0 +1,3 @@ +terraform { + required_version = "~> 0.9.0" +} diff --git a/command/test-fixtures/validate-valid/with-tfvars-file/main.tf b/command/test-fixtures/validate-valid/with-tfvars-file/main.tf new file mode 100644 index 0000000000..86a19b8fbc --- /dev/null +++ b/command/test-fixtures/validate-valid/with-tfvars-file/main.tf @@ -0,0 +1,4 @@ +variable "var_without_default" { + type = "string" +} + diff --git a/command/test-fixtures/validate-valid/with-tfvars-file/terraform.tfvars b/command/test-fixtures/validate-valid/with-tfvars-file/terraform.tfvars new file mode 100644 index 0000000000..5b6bd874a0 --- /dev/null +++ b/command/test-fixtures/validate-valid/with-tfvars-file/terraform.tfvars @@ -0,0 +1 @@ +var_without_default = "foo" diff --git a/command/validate.go b/command/validate.go index 04ae2dddb3..d5ec580fe0 100644 --- a/command/validate.go +++ b/command/validate.go @@ -17,7 +17,7 @@ type ValidateCommand struct { const defaultPath = "." func (c *ValidateCommand) Run(args []string) int { - args, err := c.Meta.process(args, false) + args, err := c.Meta.process(args, true) if err != nil { return 1 } diff --git a/command/validate_test.go b/command/validate_test.go index 77bdfed973..a52b956ff8 100644 --- a/command/validate_test.go +++ b/command/validate_test.go @@ -1,9 +1,11 @@ package command import ( + "os" "strings" "testing" + "github.com/hashicorp/terraform/helper/copy" "github.com/mitchellh/cli" ) @@ -28,6 +30,28 @@ func TestValidateCommand(t *testing.T) { } } +func TestValidateCommandWithTfvarsFile(t *testing.T) { + // Create a temporary working directory that is empty because this test + // requires scanning the current working directory by validate command. + td := tempDir(t) + copy.CopyDir(testFixturePath("validate-valid/with-tfvars-file"), td) + defer os.RemoveAll(td) + defer testChdir(t, td)() + + ui := new(cli.MockUi) + c := &ValidateCommand{ + Meta: Meta{ + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, + }, + } + + args := []string{} + if code := c.Run(args); code != 0 { + t.Fatalf("bad %d\n\n%s", code, ui.ErrorWriter.String()) + } +} + func TestValidateFailingCommand(t *testing.T) { if ui, code := setupTest("validate-invalid"); code != 1 { t.Fatalf("Should have failed: %d\n\n%s", code, ui.ErrorWriter.String()) diff --git a/config/config_string.go b/config/config_string.go index b57a09fb58..a6933c2a5f 100644 --- a/config/config_string.go +++ b/config/config_string.go @@ -143,6 +143,10 @@ func outputsStr(os []*Output) string { result += fmt.Sprintf(" %s: %s\n", kind, str) } } + + if o.Description != "" { + result += fmt.Sprintf(" description\n %s\n", o.Description) + } } return strings.TrimSpace(result) diff --git a/config/loader_hcl.go b/config/loader_hcl.go index 3110837445..a88406291b 100644 --- a/config/loader_hcl.go +++ b/config/loader_hcl.go @@ -499,6 +499,7 @@ func loadOutputsHcl(list *ast.ObjectList) ([]*Output, error) { // Delete special keys delete(config, "depends_on") + delete(config, "description") rawConfig, err := NewRawConfig(config) if err != nil { @@ -520,10 +521,23 @@ func loadOutputsHcl(list *ast.ObjectList) ([]*Output, error) { } } + // If we have a description field, then filter that + var description string + if o := listVal.Filter("description"); len(o.Items) > 0 { + err := hcl.DecodeObject(&description, o.Items[0].Val) + if err != nil { + return nil, fmt.Errorf( + "Error reading description for output %q: %s", + n, + err) + } + } + result = append(result, &Output{ - Name: n, - RawConfig: rawConfig, - DependsOn: dependsOn, + Name: n, + RawConfig: rawConfig, + DependsOn: dependsOn, + Description: description, }) } diff --git a/config/loader_test.go b/config/loader_test.go index 30a3f24446..b9dca782ae 100644 --- a/config/loader_test.go +++ b/config/loader_test.go @@ -1058,6 +1058,11 @@ aws_instance.test (x1) ` const basicOutputsStr = ` +web_id + vars + resource: aws_instance.web.id + description + The ID web_ip vars resource: aws_instance.web.private_ip diff --git a/config/test-fixtures/basic.tf b/config/test-fixtures/basic.tf index f64d6a8d51..a49b8ba3eb 100644 --- a/config/test-fixtures/basic.tf +++ b/config/test-fixtures/basic.tf @@ -85,6 +85,11 @@ output "web_ip" { value = "${aws_instance.web.private_ip}" } +output "web_id" { + description = "The ID" + value = "${aws_instance.web.id}" +} + atlas { name = "mitchellh/foo" } diff --git a/config/test-fixtures/basic.tf.json b/config/test-fixtures/basic.tf.json index 6541beae6d..39b9692c71 100644 --- a/config/test-fixtures/basic.tf.json +++ b/config/test-fixtures/basic.tf.json @@ -88,6 +88,10 @@ }, "output": { + "web_id": { + "description": "The ID", + "value": "${aws_instance.web.id}" + }, "web_ip": { "value": "${aws_instance.web.private_ip}" } diff --git a/config/testing.go b/config/testing.go index f7bfadd9e8..831fc77867 100644 --- a/config/testing.go +++ b/config/testing.go @@ -6,6 +6,8 @@ import ( // TestRawConfig is used to create a RawConfig for testing. func TestRawConfig(t *testing.T, c map[string]interface{}) *RawConfig { + t.Helper() + cfg, err := NewRawConfig(c) if err != nil { t.Fatalf("err: %s", err) diff --git a/helper/schema/testing.go b/helper/schema/testing.go index 9765bdbc6d..353c9366af 100644 --- a/helper/schema/testing.go +++ b/helper/schema/testing.go @@ -10,6 +10,8 @@ import ( // TestResourceDataRaw creates a ResourceData from a raw configuration map. func TestResourceDataRaw( t *testing.T, schema map[string]*Schema, raw map[string]interface{}) *ResourceData { + t.Helper() + c, err := config.NewRawConfig(raw) if err != nil { t.Fatalf("err: %s", err) diff --git a/helper/validation/validation.go b/helper/validation/validation.go index def3ca24f1..421c91442f 100644 --- a/helper/validation/validation.go +++ b/helper/validation/validation.go @@ -3,6 +3,7 @@ package validation import ( "fmt" "net" + "regexp" "strings" "github.com/hashicorp/terraform/helper/schema" @@ -138,6 +139,8 @@ func CIDRNetwork(min, max int) schema.SchemaValidateFunc { } } +// ValidateJsonString is a SchemaValidateFunc which tests to make sure the +// supplied string is valid JSON. func ValidateJsonString(v interface{}, k string) (ws []string, errors []error) { if _, err := structure.NormalizeJsonString(v); err != nil { errors = append(errors, fmt.Errorf("%q contains an invalid JSON: %s", k, err)) @@ -156,5 +159,13 @@ func ValidateListUniqueStrings(v interface{}, k string) (ws []string, errors []e } } } - return } + +// ValidateRegexp returns a SchemaValidateFunc which tests to make sure the +// supplied string is a valid regular expression. +func ValidateRegexp(v interface{}, k string) (ws []string, errors []error) { + if _, err := regexp.Compile(v.(string)); err != nil { + errors = append(errors, fmt.Errorf("%q: %s", k, err)) + } + return +} \ No newline at end of file diff --git a/helper/validation/validation_test.go b/helper/validation/validation_test.go index fccffe805d..5fbd02a329 100644 --- a/helper/validation/validation_test.go +++ b/helper/validation/validation_test.go @@ -111,6 +111,20 @@ func TestValidationStringInSlice(t *testing.T) { }) } +func TestValidationRegexp(t *testing.T) { + runTestCases(t, []testCase{ + { + val: ".*foo.*", + f: ValidateRegexp, + }, + { + val: "foo(bar", + f: ValidateRegexp, + expectedErr: regexp.MustCompile(regexp.QuoteMeta("error parsing regexp: missing closing ): `foo(bar`")), + }, + }) +} + func TestValidateJsonString(t *testing.T) { type testCases struct { Value string diff --git a/state/testing.go b/state/testing.go index 4826159911..3b92b33c90 100644 --- a/state/testing.go +++ b/state/testing.go @@ -11,6 +11,8 @@ import ( // that the given implementation is pre-loaded with the TestStateInitial // state. func TestState(t *testing.T, s State) { + t.Helper() + if err := s.RefreshState(); err != nil { t.Fatalf("err: %s", err) } diff --git a/terraform/context.go b/terraform/context.go index a814a85ddc..28ca14cbf4 100644 --- a/terraform/context.go +++ b/terraform/context.go @@ -12,7 +12,6 @@ import ( "github.com/hashicorp/hcl" "github.com/hashicorp/terraform/config" "github.com/hashicorp/terraform/config/module" - "github.com/hashicorp/terraform/helper/experiment" ) // InputMode defines what sort of input will be asked for when Input @@ -123,7 +122,7 @@ type Context struct { func NewContext(opts *ContextOpts) (*Context, error) { // Validate the version requirement if it is given if opts.Module != nil { - if err := checkRequiredVersion(opts.Module); err != nil { + if err := CheckRequiredVersion(opts.Module); err != nil { return nil, err } } @@ -465,7 +464,7 @@ func (c *Context) Input(mode InputMode) error { } // Do the walk - if _, err := c.walk(graph, nil, walkInput); err != nil { + if _, err := c.walk(graph, walkInput); err != nil { return err } } @@ -506,7 +505,7 @@ func (c *Context) Apply() (*State, error) { } // Walk the graph - walker, err := c.walk(graph, graph, operation) + walker, err := c.walk(graph, operation) if len(walker.ValidationErrors) > 0 { err = multierror.Append(err, walker.ValidationErrors...) } @@ -575,7 +574,7 @@ func (c *Context) Plan() (*Plan, error) { } // Do the walk - walker, err := c.walk(graph, graph, operation) + walker, err := c.walk(graph, operation) if err != nil { return nil, err } @@ -630,7 +629,7 @@ func (c *Context) Refresh() (*State, error) { } // Do the walk - if _, err := c.walk(graph, graph, walkRefresh); err != nil { + if _, err := c.walk(graph, walkRefresh); err != nil { return nil, err } @@ -705,7 +704,7 @@ func (c *Context) Validate() ([]string, []error) { } // Walk - walker, err := c.walk(graph, graph, walkValidate) + walker, err := c.walk(graph, walkValidate) if err != nil { return nil, multierror.Append(errs, err).Errors } @@ -792,33 +791,11 @@ func (c *Context) releaseRun() { c.runContext = nil } -func (c *Context) walk( - graph, shadow *Graph, operation walkOperation) (*ContextGraphWalker, error) { +func (c *Context) walk(graph *Graph, operation walkOperation) (*ContextGraphWalker, error) { // Keep track of the "real" context which is the context that does // the real work: talking to real providers, modifying real state, etc. realCtx := c - // If we don't want shadowing, remove it - if !experiment.Enabled(experiment.X_shadow) { - shadow = nil - } - - // Just log this so we can see it in a debug log - if !c.shadow { - log.Printf("[WARN] terraform: shadow graph disabled") - shadow = nil - } - - // If we have a shadow graph, walk that as well - var shadowCtx *Context - var shadowCloser Shadow - if shadow != nil { - // Build the shadow context. In the process, override the real context - // with the one that is wrapped so that the shadow context can verify - // the results of the real. - realCtx, shadowCtx, shadowCloser = newShadowContext(c) - } - log.Printf("[DEBUG] Starting graph walk: %s", operation.String()) walker := &ContextGraphWalker{ @@ -837,90 +814,6 @@ func (c *Context) walk( close(watchStop) <-watchWait - // If we have a shadow graph and we interrupted the real graph, then - // we just close the shadow and never verify it. It is non-trivial to - // recreate the exact execution state up until an interruption so this - // isn't supported with shadows at the moment. - if shadowCloser != nil && c.sh.Stopped() { - // Ignore the error result, there is nothing we could care about - shadowCloser.CloseShadow() - - // Set it to nil so we don't do anything - shadowCloser = nil - } - - // If we have a shadow graph, wait for that to complete. - if shadowCloser != nil { - // Build the graph walker for the shadow. We also wrap this in - // a panicwrap so that panics are captured. For the shadow graph, - // we just want panics to be normal errors rather than to crash - // Terraform. - shadowWalker := GraphWalkerPanicwrap(&ContextGraphWalker{ - Context: shadowCtx, - Operation: operation, - }) - - // Kick off the shadow walk. This will block on any operations - // on the real walk so it is fine to start first. - log.Printf("[INFO] Starting shadow graph walk: %s", operation.String()) - shadowCh := make(chan error) - go func() { - shadowCh <- shadow.Walk(shadowWalker) - }() - - // Notify the shadow that we're done - if err := shadowCloser.CloseShadow(); err != nil { - c.shadowErr = multierror.Append(c.shadowErr, err) - } - - // Wait for the walk to end - log.Printf("[DEBUG] Waiting for shadow graph to complete...") - shadowWalkErr := <-shadowCh - - // Get any shadow errors - if err := shadowCloser.ShadowError(); err != nil { - c.shadowErr = multierror.Append(c.shadowErr, err) - } - - // Verify the contexts (compare) - if err := shadowContextVerify(realCtx, shadowCtx); err != nil { - c.shadowErr = multierror.Append(c.shadowErr, err) - } - - // At this point, if we're supposed to fail on error, then - // we PANIC. Some tests just verify that there is an error, - // so simply appending it to realErr and returning could hide - // shadow problems. - // - // This must be done BEFORE appending shadowWalkErr since the - // shadowWalkErr may include expected errors. - // - // We only do this if we don't have a real error. In the case of - // a real error, we can't guarantee what nodes were and weren't - // traversed in parallel scenarios so we can't guarantee no - // shadow errors. - if c.shadowErr != nil && contextFailOnShadowError && realErr == nil { - panic(multierror.Prefix(c.shadowErr, "shadow graph:")) - } - - // Now, if we have a walk error, we append that through - if shadowWalkErr != nil { - c.shadowErr = multierror.Append(c.shadowErr, shadowWalkErr) - } - - if c.shadowErr == nil { - log.Printf("[INFO] Shadow graph success!") - } else { - log.Printf("[ERROR] Shadow graph error: %s", c.shadowErr) - - // If we're supposed to fail on shadow errors, then report it - if contextFailOnShadowError { - realErr = multierror.Append(realErr, multierror.Prefix( - c.shadowErr, "shadow graph:")) - } - } - } - return walker, realErr } diff --git a/terraform/context_import.go b/terraform/context_import.go index f1d57760df..e940143197 100644 --- a/terraform/context_import.go +++ b/terraform/context_import.go @@ -66,7 +66,7 @@ func (c *Context) Import(opts *ImportOpts) (*State, error) { } // Walk it - if _, err := c.walk(graph, nil, walkImport); err != nil { + if _, err := c.walk(graph, walkImport); err != nil { return c.state, err } diff --git a/terraform/context_validate_test.go b/terraform/context_validate_test.go index 60cef142d8..34a0c8615d 100644 --- a/terraform/context_validate_test.go +++ b/terraform/context_validate_test.go @@ -1005,7 +1005,7 @@ func TestContext2Validate_PlanGraphBuilder(t *testing.T) { t.Fatalf("error attmepting to Build PlanGraphBuilder: %s", err) } defer c.acquireRun("validate-test")() - walker, err := c.walk(graph, graph, walkValidate) + walker, err := c.walk(graph, walkValidate) if err != nil { t.Fatal(err) } diff --git a/terraform/shadow.go b/terraform/shadow.go deleted file mode 100644 index 46325595f1..0000000000 --- a/terraform/shadow.go +++ /dev/null @@ -1,28 +0,0 @@ -package terraform - -// Shadow is the interface that any "shadow" structures must implement. -// -// A shadow structure is an interface implementation (typically) that -// shadows a real implementation and verifies that the same behavior occurs -// on both. The semantics of this behavior are up to the interface itself. -// -// A shadow NEVER modifies real values or state. It must always be safe to use. -// -// For example, a ResourceProvider shadow ensures that the same operations -// are done on the same resources with the same configurations. -// -// The typical usage of a shadow following this interface is to complete -// the real operations, then call CloseShadow which tells the shadow that -// the real side is done. Then, once the shadow is also complete, call -// ShadowError to find any errors that may have been caught. -type Shadow interface { - // CloseShadow tells the shadow that the REAL implementation is - // complete. Therefore, any calls that would block should now return - // immediately since no more changes will happen to the real side. - CloseShadow() error - - // ShadowError returns the errors that the shadow has found. - // This should be called AFTER CloseShadow and AFTER the shadow is - // known to be complete (no more calls to it). - ShadowError() error -} diff --git a/terraform/shadow_components.go b/terraform/shadow_components.go deleted file mode 100644 index 116cf84f97..0000000000 --- a/terraform/shadow_components.go +++ /dev/null @@ -1,273 +0,0 @@ -package terraform - -import ( - "fmt" - "sync" - - "github.com/hashicorp/go-multierror" - "github.com/hashicorp/terraform/helper/shadow" -) - -// newShadowComponentFactory creates a shadowed contextComponentFactory -// so that requests to create new components result in both a real and -// shadow side. -func newShadowComponentFactory( - f contextComponentFactory) (contextComponentFactory, *shadowComponentFactory) { - // Create the shared data - shared := &shadowComponentFactoryShared{contextComponentFactory: f} - - // Create the real side - real := &shadowComponentFactory{ - shadowComponentFactoryShared: shared, - } - - // Create the shadow - shadow := &shadowComponentFactory{ - shadowComponentFactoryShared: shared, - Shadow: true, - } - - return real, shadow -} - -// shadowComponentFactory is the shadow side. Any components created -// with this factory are fake and will not cause real work to happen. -// -// Unlike other shadowers, the shadow component factory will allow the -// shadow to create _any_ component even if it is never requested on the -// real side. This is because errors will happen later downstream as function -// calls are made to the shadows that are never matched on the real side. -type shadowComponentFactory struct { - *shadowComponentFactoryShared - - Shadow bool // True if this should return the shadow - lock sync.Mutex -} - -func (f *shadowComponentFactory) ResourceProvider( - n, uid string) (ResourceProvider, error) { - f.lock.Lock() - defer f.lock.Unlock() - - real, shadow, err := f.shadowComponentFactoryShared.ResourceProvider(n, uid) - var result ResourceProvider = real - if f.Shadow { - result = shadow - } - - return result, err -} - -func (f *shadowComponentFactory) ResourceProvisioner( - n, uid string) (ResourceProvisioner, error) { - f.lock.Lock() - defer f.lock.Unlock() - - real, shadow, err := f.shadowComponentFactoryShared.ResourceProvisioner(n, uid) - var result ResourceProvisioner = real - if f.Shadow { - result = shadow - } - - return result, err -} - -// CloseShadow is called when the _real_ side is complete. This will cause -// all future blocking operations to return immediately on the shadow to -// ensure the shadow also completes. -func (f *shadowComponentFactory) CloseShadow() error { - // If we aren't the shadow, just return - if !f.Shadow { - return nil - } - - // Lock ourselves so we don't modify state - f.lock.Lock() - defer f.lock.Unlock() - - // Grab our shared state - shared := f.shadowComponentFactoryShared - - // If we're already closed, its an error - if shared.closed { - return fmt.Errorf("component factory shadow already closed") - } - - // Close all the providers and provisioners and return the error - var result error - for _, n := range shared.providerKeys { - _, shadow, err := shared.ResourceProvider(n, n) - if err == nil && shadow != nil { - if err := shadow.CloseShadow(); err != nil { - result = multierror.Append(result, err) - } - } - } - - for _, n := range shared.provisionerKeys { - _, shadow, err := shared.ResourceProvisioner(n, n) - if err == nil && shadow != nil { - if err := shadow.CloseShadow(); err != nil { - result = multierror.Append(result, err) - } - } - } - - // Mark ourselves as closed - shared.closed = true - - return result -} - -func (f *shadowComponentFactory) ShadowError() error { - // If we aren't the shadow, just return - if !f.Shadow { - return nil - } - - // Lock ourselves so we don't modify state - f.lock.Lock() - defer f.lock.Unlock() - - // Grab our shared state - shared := f.shadowComponentFactoryShared - - // If we're not closed, its an error - if !shared.closed { - return fmt.Errorf("component factory must be closed to retrieve errors") - } - - // Close all the providers and provisioners and return the error - var result error - for _, n := range shared.providerKeys { - _, shadow, err := shared.ResourceProvider(n, n) - if err == nil && shadow != nil { - if err := shadow.ShadowError(); err != nil { - result = multierror.Append(result, err) - } - } - } - - for _, n := range shared.provisionerKeys { - _, shadow, err := shared.ResourceProvisioner(n, n) - if err == nil && shadow != nil { - if err := shadow.ShadowError(); err != nil { - result = multierror.Append(result, err) - } - } - } - - return result -} - -// shadowComponentFactoryShared is shared data between the two factories. -// -// It is NOT SAFE to run any function on this struct in parallel. Lock -// access to this struct. -type shadowComponentFactoryShared struct { - contextComponentFactory - - closed bool - providers shadow.KeyedValue - providerKeys []string - provisioners shadow.KeyedValue - provisionerKeys []string -} - -// shadowResourceProviderFactoryEntry is the entry that is stored in -// the Shadows key/value for a provider. -type shadowComponentFactoryProviderEntry struct { - Real ResourceProvider - Shadow shadowResourceProvider - Err error -} - -type shadowComponentFactoryProvisionerEntry struct { - Real ResourceProvisioner - Shadow shadowResourceProvisioner - Err error -} - -func (f *shadowComponentFactoryShared) ResourceProvider( - n, uid string) (ResourceProvider, shadowResourceProvider, error) { - // Determine if we already have a value - raw, ok := f.providers.ValueOk(uid) - if !ok { - // Build the entry - var entry shadowComponentFactoryProviderEntry - - // No value, initialize. Create the original - p, err := f.contextComponentFactory.ResourceProvider(n, uid) - if err != nil { - entry.Err = err - p = nil // Just to be sure - } - - if p != nil { - // Create the shadow - real, shadow := newShadowResourceProvider(p) - entry.Real = real - entry.Shadow = shadow - - if f.closed { - shadow.CloseShadow() - } - } - - // Store the value - f.providers.SetValue(uid, &entry) - f.providerKeys = append(f.providerKeys, uid) - raw = &entry - } - - // Read the entry - entry, ok := raw.(*shadowComponentFactoryProviderEntry) - if !ok { - return nil, nil, fmt.Errorf("Unknown value for shadow provider: %#v", raw) - } - - // Return - return entry.Real, entry.Shadow, entry.Err -} - -func (f *shadowComponentFactoryShared) ResourceProvisioner( - n, uid string) (ResourceProvisioner, shadowResourceProvisioner, error) { - // Determine if we already have a value - raw, ok := f.provisioners.ValueOk(uid) - if !ok { - // Build the entry - var entry shadowComponentFactoryProvisionerEntry - - // No value, initialize. Create the original - p, err := f.contextComponentFactory.ResourceProvisioner(n, uid) - if err != nil { - entry.Err = err - p = nil // Just to be sure - } - - if p != nil { - // For now, just create a mock since we don't support provisioners yet - real, shadow := newShadowResourceProvisioner(p) - entry.Real = real - entry.Shadow = shadow - - if f.closed { - shadow.CloseShadow() - } - } - - // Store the value - f.provisioners.SetValue(uid, &entry) - f.provisionerKeys = append(f.provisionerKeys, uid) - raw = &entry - } - - // Read the entry - entry, ok := raw.(*shadowComponentFactoryProvisionerEntry) - if !ok { - return nil, nil, fmt.Errorf("Unknown value for shadow provisioner: %#v", raw) - } - - // Return - return entry.Real, entry.Shadow, entry.Err -} diff --git a/terraform/shadow_context.go b/terraform/shadow_context.go deleted file mode 100644 index 5588af252c..0000000000 --- a/terraform/shadow_context.go +++ /dev/null @@ -1,158 +0,0 @@ -package terraform - -import ( - "fmt" - "strings" - - "github.com/hashicorp/go-multierror" - "github.com/mitchellh/copystructure" -) - -// newShadowContext creates a new context that will shadow the given context -// when walking the graph. The resulting context should be used _only once_ -// for a graph walk. -// -// The returned Shadow should be closed after the graph walk with the -// real context is complete. Errors from the shadow can be retrieved there. -// -// Most importantly, any operations done on the shadow context (the returned -// context) will NEVER affect the real context. All structures are deep -// copied, no real providers or resources are used, etc. -func newShadowContext(c *Context) (*Context, *Context, Shadow) { - // Copy the targets - targetRaw, err := copystructure.Copy(c.targets) - if err != nil { - panic(err) - } - - // Copy the variables - varRaw, err := copystructure.Copy(c.variables) - if err != nil { - panic(err) - } - - // Copy the provider inputs - providerInputRaw, err := copystructure.Copy(c.providerInputConfig) - if err != nil { - panic(err) - } - - // The factories - componentsReal, componentsShadow := newShadowComponentFactory(c.components) - - // Create the shadow - shadow := &Context{ - components: componentsShadow, - destroy: c.destroy, - diff: c.diff.DeepCopy(), - hooks: nil, - meta: c.meta, - module: c.module, - state: c.state.DeepCopy(), - targets: targetRaw.([]string), - variables: varRaw.(map[string]interface{}), - - // NOTE(mitchellh): This is not going to work for shadows that are - // testing that input results in the proper end state. At the time - // of writing, input is not used in any state-changing graph - // walks anyways, so this checks nothing. We set it to this to avoid - // any panics but even a "nil" value worked here. - uiInput: new(MockUIInput), - - // Hardcoded to 4 since parallelism in the shadow doesn't matter - // a ton since we're doing far less compared to the real side - // and our operations are MUCH faster. - parallelSem: NewSemaphore(4), - providerInputConfig: providerInputRaw.(map[string]map[string]interface{}), - } - - // Create the real context. This is effectively just a copy of - // the context given except we need to modify some of the values - // to point to the real side of a shadow so the shadow can compare values. - real := &Context{ - // The fields below are changed. - components: componentsReal, - - // The fields below are direct copies - destroy: c.destroy, - diff: c.diff, - // diffLock - no copy - hooks: c.hooks, - meta: c.meta, - module: c.module, - sh: c.sh, - state: c.state, - // stateLock - no copy - targets: c.targets, - uiInput: c.uiInput, - variables: c.variables, - - // l - no copy - parallelSem: c.parallelSem, - providerInputConfig: c.providerInputConfig, - runContext: c.runContext, - runContextCancel: c.runContextCancel, - shadowErr: c.shadowErr, - } - - return real, shadow, &shadowContextCloser{ - Components: componentsShadow, - } -} - -// shadowContextVerify takes the real and shadow context and verifies they -// have equal diffs and states. -func shadowContextVerify(real, shadow *Context) error { - var result error - - // The states compared must be pruned so they're minimal/clean - real.state.prune() - shadow.state.prune() - - // Compare the states - if !real.state.Equal(shadow.state) { - result = multierror.Append(result, fmt.Errorf( - "Real and shadow states do not match! "+ - "Real state:\n\n%s\n\n"+ - "Shadow state:\n\n%s\n\n", - real.state, shadow.state)) - } - - // Compare the diffs - if !real.diff.Equal(shadow.diff) { - result = multierror.Append(result, fmt.Errorf( - "Real and shadow diffs do not match! "+ - "Real diff:\n\n%s\n\n"+ - "Shadow diff:\n\n%s\n\n", - real.diff, shadow.diff)) - } - - return result -} - -// shadowContextCloser is the io.Closer returned by newShadowContext that -// closes all the shadows and returns the results. -type shadowContextCloser struct { - Components *shadowComponentFactory -} - -// Close closes the shadow context. -func (c *shadowContextCloser) CloseShadow() error { - return c.Components.CloseShadow() -} - -func (c *shadowContextCloser) ShadowError() error { - err := c.Components.ShadowError() - if err == nil { - return nil - } - - // This is a sad edge case: if the configuration contains uuid() at - // any point, we cannot reason aboyt the shadow execution. Tested - // with Context2Plan_shadowUuid. - if strings.Contains(err.Error(), "uuid()") { - err = nil - } - - return err -} diff --git a/terraform/shadow_resource_provider.go b/terraform/shadow_resource_provider.go deleted file mode 100644 index 9741d7e796..0000000000 --- a/terraform/shadow_resource_provider.go +++ /dev/null @@ -1,815 +0,0 @@ -package terraform - -import ( - "fmt" - "log" - "sync" - - "github.com/hashicorp/go-multierror" - "github.com/hashicorp/terraform/helper/shadow" -) - -// shadowResourceProvider implements ResourceProvider for the shadow -// eval context defined in eval_context_shadow.go. -// -// This is used to verify behavior with a real provider. This shouldn't -// be used directly. -type shadowResourceProvider interface { - ResourceProvider - Shadow -} - -// newShadowResourceProvider creates a new shadowed ResourceProvider. -// -// This will assume a well behaved real ResourceProvider. For example, -// it assumes that the `Resources` call underneath doesn't change values -// since once it is called on the real provider, it will be cached and -// returned in the shadow since number of calls to that shouldn't affect -// actual behavior. -// -// However, with calls like Apply, call order is taken into account, -// parameters are checked for equality, etc. -func newShadowResourceProvider(p ResourceProvider) (ResourceProvider, shadowResourceProvider) { - // Create the shared data - shared := shadowResourceProviderShared{} - - // Create the real provider that does actual work - real := &shadowResourceProviderReal{ - ResourceProvider: p, - Shared: &shared, - } - - // Create the shadow that watches the real value - shadow := &shadowResourceProviderShadow{ - Shared: &shared, - - resources: p.Resources(), - dataSources: p.DataSources(), - } - - return real, shadow -} - -// shadowResourceProviderReal is the real resource provider. Function calls -// to this will perform real work. This records the parameters and return -// values and call order for the shadow to reproduce. -type shadowResourceProviderReal struct { - ResourceProvider - - Shared *shadowResourceProviderShared -} - -func (p *shadowResourceProviderReal) Close() error { - var result error - if c, ok := p.ResourceProvider.(ResourceProviderCloser); ok { - result = c.Close() - } - - p.Shared.CloseErr.SetValue(result) - return result -} - -func (p *shadowResourceProviderReal) Input( - input UIInput, c *ResourceConfig) (*ResourceConfig, error) { - cCopy := c.DeepCopy() - - result, err := p.ResourceProvider.Input(input, c) - p.Shared.Input.SetValue(&shadowResourceProviderInput{ - Config: cCopy, - Result: result.DeepCopy(), - ResultErr: err, - }) - - return result, err -} - -func (p *shadowResourceProviderReal) Validate(c *ResourceConfig) ([]string, []error) { - warns, errs := p.ResourceProvider.Validate(c) - p.Shared.Validate.SetValue(&shadowResourceProviderValidate{ - Config: c.DeepCopy(), - ResultWarn: warns, - ResultErr: errs, - }) - - return warns, errs -} - -func (p *shadowResourceProviderReal) Configure(c *ResourceConfig) error { - cCopy := c.DeepCopy() - - err := p.ResourceProvider.Configure(c) - p.Shared.Configure.SetValue(&shadowResourceProviderConfigure{ - Config: cCopy, - Result: err, - }) - - return err -} - -func (p *shadowResourceProviderReal) Stop() error { - return p.ResourceProvider.Stop() -} - -func (p *shadowResourceProviderReal) ValidateResource( - t string, c *ResourceConfig) ([]string, []error) { - key := t - configCopy := c.DeepCopy() - - // Real operation - warns, errs := p.ResourceProvider.ValidateResource(t, c) - - // Initialize to ensure we always have a wrapper with a lock - p.Shared.ValidateResource.Init( - key, &shadowResourceProviderValidateResourceWrapper{}) - - // Get the result - raw := p.Shared.ValidateResource.Value(key) - wrapper, ok := raw.(*shadowResourceProviderValidateResourceWrapper) - if !ok { - // If this fails then we just continue with our day... the shadow - // will fail to but there isn't much we can do. - log.Printf( - "[ERROR] unknown value in ValidateResource shadow value: %#v", raw) - return warns, errs - } - - // Lock the wrapper for writing and record our call - wrapper.Lock() - defer wrapper.Unlock() - - wrapper.Calls = append(wrapper.Calls, &shadowResourceProviderValidateResource{ - Config: configCopy, - Warns: warns, - Errors: errs, - }) - - // With it locked, call SetValue again so that it triggers WaitForChange - p.Shared.ValidateResource.SetValue(key, wrapper) - - // Return the result - return warns, errs -} - -func (p *shadowResourceProviderReal) Apply( - info *InstanceInfo, - state *InstanceState, - diff *InstanceDiff) (*InstanceState, error) { - // Thse have to be copied before the call since call can modify - stateCopy := state.DeepCopy() - diffCopy := diff.DeepCopy() - - result, err := p.ResourceProvider.Apply(info, state, diff) - p.Shared.Apply.SetValue(info.uniqueId(), &shadowResourceProviderApply{ - State: stateCopy, - Diff: diffCopy, - Result: result.DeepCopy(), - ResultErr: err, - }) - - return result, err -} - -func (p *shadowResourceProviderReal) Diff( - info *InstanceInfo, - state *InstanceState, - desired *ResourceConfig) (*InstanceDiff, error) { - // Thse have to be copied before the call since call can modify - stateCopy := state.DeepCopy() - desiredCopy := desired.DeepCopy() - - result, err := p.ResourceProvider.Diff(info, state, desired) - p.Shared.Diff.SetValue(info.uniqueId(), &shadowResourceProviderDiff{ - State: stateCopy, - Desired: desiredCopy, - Result: result.DeepCopy(), - ResultErr: err, - }) - - return result, err -} - -func (p *shadowResourceProviderReal) Refresh( - info *InstanceInfo, - state *InstanceState) (*InstanceState, error) { - // Thse have to be copied before the call since call can modify - stateCopy := state.DeepCopy() - - result, err := p.ResourceProvider.Refresh(info, state) - p.Shared.Refresh.SetValue(info.uniqueId(), &shadowResourceProviderRefresh{ - State: stateCopy, - Result: result.DeepCopy(), - ResultErr: err, - }) - - return result, err -} - -func (p *shadowResourceProviderReal) ValidateDataSource( - t string, c *ResourceConfig) ([]string, []error) { - key := t - configCopy := c.DeepCopy() - - // Real operation - warns, errs := p.ResourceProvider.ValidateDataSource(t, c) - - // Initialize - p.Shared.ValidateDataSource.Init( - key, &shadowResourceProviderValidateDataSourceWrapper{}) - - // Get the result - raw := p.Shared.ValidateDataSource.Value(key) - wrapper, ok := raw.(*shadowResourceProviderValidateDataSourceWrapper) - if !ok { - // If this fails then we just continue with our day... the shadow - // will fail to but there isn't much we can do. - log.Printf( - "[ERROR] unknown value in ValidateDataSource shadow value: %#v", raw) - return warns, errs - } - - // Lock the wrapper for writing and record our call - wrapper.Lock() - defer wrapper.Unlock() - - wrapper.Calls = append(wrapper.Calls, &shadowResourceProviderValidateDataSource{ - Config: configCopy, - Warns: warns, - Errors: errs, - }) - - // Set it - p.Shared.ValidateDataSource.SetValue(key, wrapper) - - // Return the result - return warns, errs -} - -func (p *shadowResourceProviderReal) ReadDataDiff( - info *InstanceInfo, - desired *ResourceConfig) (*InstanceDiff, error) { - // These have to be copied before the call since call can modify - desiredCopy := desired.DeepCopy() - - result, err := p.ResourceProvider.ReadDataDiff(info, desired) - p.Shared.ReadDataDiff.SetValue(info.uniqueId(), &shadowResourceProviderReadDataDiff{ - Desired: desiredCopy, - Result: result.DeepCopy(), - ResultErr: err, - }) - - return result, err -} - -func (p *shadowResourceProviderReal) ReadDataApply( - info *InstanceInfo, - diff *InstanceDiff) (*InstanceState, error) { - // Thse have to be copied before the call since call can modify - diffCopy := diff.DeepCopy() - - result, err := p.ResourceProvider.ReadDataApply(info, diff) - p.Shared.ReadDataApply.SetValue(info.uniqueId(), &shadowResourceProviderReadDataApply{ - Diff: diffCopy, - Result: result.DeepCopy(), - ResultErr: err, - }) - - return result, err -} - -// shadowResourceProviderShadow is the shadow resource provider. Function -// calls never affect real resources. This is paired with the "real" side -// which must be called properly to enable recording. -type shadowResourceProviderShadow struct { - Shared *shadowResourceProviderShared - - // Cached values that are expected to not change - resources []ResourceType - dataSources []DataSource - - Error error // Error is the list of errors from the shadow - ErrorLock sync.Mutex -} - -type shadowResourceProviderShared struct { - // NOTE: Anytime a value is added here, be sure to add it to - // the Close() method so that it is closed. - - CloseErr shadow.Value - Input shadow.Value - Validate shadow.Value - Configure shadow.Value - ValidateResource shadow.KeyedValue - Apply shadow.KeyedValue - Diff shadow.KeyedValue - Refresh shadow.KeyedValue - ValidateDataSource shadow.KeyedValue - ReadDataDiff shadow.KeyedValue - ReadDataApply shadow.KeyedValue -} - -func (p *shadowResourceProviderShared) Close() error { - return shadow.Close(p) -} - -func (p *shadowResourceProviderShadow) CloseShadow() error { - err := p.Shared.Close() - if err != nil { - err = fmt.Errorf("close error: %s", err) - } - - return err -} - -func (p *shadowResourceProviderShadow) ShadowError() error { - return p.Error -} - -func (p *shadowResourceProviderShadow) Resources() []ResourceType { - return p.resources -} - -func (p *shadowResourceProviderShadow) DataSources() []DataSource { - return p.dataSources -} - -func (p *shadowResourceProviderShadow) Close() error { - v := p.Shared.CloseErr.Value() - if v == nil { - return nil - } - - return v.(error) -} - -func (p *shadowResourceProviderShadow) Input( - input UIInput, c *ResourceConfig) (*ResourceConfig, error) { - // Get the result of the input call - raw := p.Shared.Input.Value() - if raw == nil { - return nil, nil - } - - result, ok := raw.(*shadowResourceProviderInput) - if !ok { - p.ErrorLock.Lock() - defer p.ErrorLock.Unlock() - p.Error = multierror.Append(p.Error, fmt.Errorf( - "Unknown 'input' shadow value: %#v", raw)) - return nil, nil - } - - // Compare the parameters, which should be identical - if !c.Equal(result.Config) { - p.ErrorLock.Lock() - p.Error = multierror.Append(p.Error, fmt.Errorf( - "Input had unequal configurations (real, then shadow):\n\n%#v\n\n%#v", - result.Config, c)) - p.ErrorLock.Unlock() - } - - // Return the results - return result.Result, result.ResultErr -} - -func (p *shadowResourceProviderShadow) Validate(c *ResourceConfig) ([]string, []error) { - // Get the result of the validate call - raw := p.Shared.Validate.Value() - if raw == nil { - return nil, nil - } - - result, ok := raw.(*shadowResourceProviderValidate) - if !ok { - p.ErrorLock.Lock() - defer p.ErrorLock.Unlock() - p.Error = multierror.Append(p.Error, fmt.Errorf( - "Unknown 'validate' shadow value: %#v", raw)) - return nil, nil - } - - // Compare the parameters, which should be identical - if !c.Equal(result.Config) { - p.ErrorLock.Lock() - p.Error = multierror.Append(p.Error, fmt.Errorf( - "Validate had unequal configurations (real, then shadow):\n\n%#v\n\n%#v", - result.Config, c)) - p.ErrorLock.Unlock() - } - - // Return the results - return result.ResultWarn, result.ResultErr -} - -func (p *shadowResourceProviderShadow) Configure(c *ResourceConfig) error { - // Get the result of the call - raw := p.Shared.Configure.Value() - if raw == nil { - return nil - } - - result, ok := raw.(*shadowResourceProviderConfigure) - if !ok { - p.ErrorLock.Lock() - defer p.ErrorLock.Unlock() - p.Error = multierror.Append(p.Error, fmt.Errorf( - "Unknown 'configure' shadow value: %#v", raw)) - return nil - } - - // Compare the parameters, which should be identical - if !c.Equal(result.Config) { - p.ErrorLock.Lock() - p.Error = multierror.Append(p.Error, fmt.Errorf( - "Configure had unequal configurations (real, then shadow):\n\n%#v\n\n%#v", - result.Config, c)) - p.ErrorLock.Unlock() - } - - // Return the results - return result.Result -} - -// Stop returns immediately. -func (p *shadowResourceProviderShadow) Stop() error { - return nil -} - -func (p *shadowResourceProviderShadow) ValidateResource(t string, c *ResourceConfig) ([]string, []error) { - // Unique key - key := t - - // Get the initial value - raw := p.Shared.ValidateResource.Value(key) - - // Find a validation with our configuration - var result *shadowResourceProviderValidateResource - for { - // Get the value - if raw == nil { - p.ErrorLock.Lock() - defer p.ErrorLock.Unlock() - p.Error = multierror.Append(p.Error, fmt.Errorf( - "Unknown 'ValidateResource' call for %q:\n\n%#v", - key, c)) - return nil, nil - } - - wrapper, ok := raw.(*shadowResourceProviderValidateResourceWrapper) - if !ok { - p.ErrorLock.Lock() - defer p.ErrorLock.Unlock() - p.Error = multierror.Append(p.Error, fmt.Errorf( - "Unknown 'ValidateResource' shadow value for %q: %#v", key, raw)) - return nil, nil - } - - // Look for the matching call with our configuration - wrapper.RLock() - for _, call := range wrapper.Calls { - if call.Config.Equal(c) { - result = call - break - } - } - wrapper.RUnlock() - - // If we found a result, exit - if result != nil { - break - } - - // Wait for a change so we can get the wrapper again - raw = p.Shared.ValidateResource.WaitForChange(key) - } - - return result.Warns, result.Errors -} - -func (p *shadowResourceProviderShadow) Apply( - info *InstanceInfo, - state *InstanceState, - diff *InstanceDiff) (*InstanceState, error) { - // Unique key - key := info.uniqueId() - raw := p.Shared.Apply.Value(key) - if raw == nil { - p.ErrorLock.Lock() - defer p.ErrorLock.Unlock() - p.Error = multierror.Append(p.Error, fmt.Errorf( - "Unknown 'apply' call for %q:\n\n%#v\n\n%#v", - key, state, diff)) - return nil, nil - } - - result, ok := raw.(*shadowResourceProviderApply) - if !ok { - p.ErrorLock.Lock() - defer p.ErrorLock.Unlock() - p.Error = multierror.Append(p.Error, fmt.Errorf( - "Unknown 'apply' shadow value for %q: %#v", key, raw)) - return nil, nil - } - - // Compare the parameters, which should be identical - if !state.Equal(result.State) { - p.ErrorLock.Lock() - p.Error = multierror.Append(p.Error, fmt.Errorf( - "Apply %q: state had unequal states (real, then shadow):\n\n%#v\n\n%#v", - key, result.State, state)) - p.ErrorLock.Unlock() - } - - if !diff.Equal(result.Diff) { - p.ErrorLock.Lock() - p.Error = multierror.Append(p.Error, fmt.Errorf( - "Apply %q: unequal diffs (real, then shadow):\n\n%#v\n\n%#v", - key, result.Diff, diff)) - p.ErrorLock.Unlock() - } - - return result.Result, result.ResultErr -} - -func (p *shadowResourceProviderShadow) Diff( - info *InstanceInfo, - state *InstanceState, - desired *ResourceConfig) (*InstanceDiff, error) { - // Unique key - key := info.uniqueId() - raw := p.Shared.Diff.Value(key) - if raw == nil { - p.ErrorLock.Lock() - defer p.ErrorLock.Unlock() - p.Error = multierror.Append(p.Error, fmt.Errorf( - "Unknown 'diff' call for %q:\n\n%#v\n\n%#v", - key, state, desired)) - return nil, nil - } - - result, ok := raw.(*shadowResourceProviderDiff) - if !ok { - p.ErrorLock.Lock() - defer p.ErrorLock.Unlock() - p.Error = multierror.Append(p.Error, fmt.Errorf( - "Unknown 'diff' shadow value for %q: %#v", key, raw)) - return nil, nil - } - - // Compare the parameters, which should be identical - if !state.Equal(result.State) { - p.ErrorLock.Lock() - p.Error = multierror.Append(p.Error, fmt.Errorf( - "Diff %q had unequal states (real, then shadow):\n\n%#v\n\n%#v", - key, result.State, state)) - p.ErrorLock.Unlock() - } - if !desired.Equal(result.Desired) { - p.ErrorLock.Lock() - p.Error = multierror.Append(p.Error, fmt.Errorf( - "Diff %q had unequal states (real, then shadow):\n\n%#v\n\n%#v", - key, result.Desired, desired)) - p.ErrorLock.Unlock() - } - - return result.Result, result.ResultErr -} - -func (p *shadowResourceProviderShadow) Refresh( - info *InstanceInfo, - state *InstanceState) (*InstanceState, error) { - // Unique key - key := info.uniqueId() - raw := p.Shared.Refresh.Value(key) - if raw == nil { - p.ErrorLock.Lock() - defer p.ErrorLock.Unlock() - p.Error = multierror.Append(p.Error, fmt.Errorf( - "Unknown 'refresh' call for %q:\n\n%#v", - key, state)) - return nil, nil - } - - result, ok := raw.(*shadowResourceProviderRefresh) - if !ok { - p.ErrorLock.Lock() - defer p.ErrorLock.Unlock() - p.Error = multierror.Append(p.Error, fmt.Errorf( - "Unknown 'refresh' shadow value: %#v", raw)) - return nil, nil - } - - // Compare the parameters, which should be identical - if !state.Equal(result.State) { - p.ErrorLock.Lock() - p.Error = multierror.Append(p.Error, fmt.Errorf( - "Refresh %q had unequal states (real, then shadow):\n\n%#v\n\n%#v", - key, result.State, state)) - p.ErrorLock.Unlock() - } - - return result.Result, result.ResultErr -} - -func (p *shadowResourceProviderShadow) ValidateDataSource( - t string, c *ResourceConfig) ([]string, []error) { - // Unique key - key := t - - // Get the initial value - raw := p.Shared.ValidateDataSource.Value(key) - - // Find a validation with our configuration - var result *shadowResourceProviderValidateDataSource - for { - // Get the value - if raw == nil { - p.ErrorLock.Lock() - defer p.ErrorLock.Unlock() - p.Error = multierror.Append(p.Error, fmt.Errorf( - "Unknown 'ValidateDataSource' call for %q:\n\n%#v", - key, c)) - return nil, nil - } - - wrapper, ok := raw.(*shadowResourceProviderValidateDataSourceWrapper) - if !ok { - p.ErrorLock.Lock() - defer p.ErrorLock.Unlock() - p.Error = multierror.Append(p.Error, fmt.Errorf( - "Unknown 'ValidateDataSource' shadow value: %#v", raw)) - return nil, nil - } - - // Look for the matching call with our configuration - wrapper.RLock() - for _, call := range wrapper.Calls { - if call.Config.Equal(c) { - result = call - break - } - } - wrapper.RUnlock() - - // If we found a result, exit - if result != nil { - break - } - - // Wait for a change so we can get the wrapper again - raw = p.Shared.ValidateDataSource.WaitForChange(key) - } - - return result.Warns, result.Errors -} - -func (p *shadowResourceProviderShadow) ReadDataDiff( - info *InstanceInfo, - desired *ResourceConfig) (*InstanceDiff, error) { - // Unique key - key := info.uniqueId() - raw := p.Shared.ReadDataDiff.Value(key) - if raw == nil { - p.ErrorLock.Lock() - defer p.ErrorLock.Unlock() - p.Error = multierror.Append(p.Error, fmt.Errorf( - "Unknown 'ReadDataDiff' call for %q:\n\n%#v", - key, desired)) - return nil, nil - } - - result, ok := raw.(*shadowResourceProviderReadDataDiff) - if !ok { - p.ErrorLock.Lock() - defer p.ErrorLock.Unlock() - p.Error = multierror.Append(p.Error, fmt.Errorf( - "Unknown 'ReadDataDiff' shadow value for %q: %#v", key, raw)) - return nil, nil - } - - // Compare the parameters, which should be identical - if !desired.Equal(result.Desired) { - p.ErrorLock.Lock() - p.Error = multierror.Append(p.Error, fmt.Errorf( - "ReadDataDiff %q had unequal configs (real, then shadow):\n\n%#v\n\n%#v", - key, result.Desired, desired)) - p.ErrorLock.Unlock() - } - - return result.Result, result.ResultErr -} - -func (p *shadowResourceProviderShadow) ReadDataApply( - info *InstanceInfo, - d *InstanceDiff) (*InstanceState, error) { - // Unique key - key := info.uniqueId() - raw := p.Shared.ReadDataApply.Value(key) - if raw == nil { - p.ErrorLock.Lock() - defer p.ErrorLock.Unlock() - p.Error = multierror.Append(p.Error, fmt.Errorf( - "Unknown 'ReadDataApply' call for %q:\n\n%#v", - key, d)) - return nil, nil - } - - result, ok := raw.(*shadowResourceProviderReadDataApply) - if !ok { - p.ErrorLock.Lock() - defer p.ErrorLock.Unlock() - p.Error = multierror.Append(p.Error, fmt.Errorf( - "Unknown 'ReadDataApply' shadow value for %q: %#v", key, raw)) - return nil, nil - } - - // Compare the parameters, which should be identical - if !d.Equal(result.Diff) { - p.ErrorLock.Lock() - p.Error = multierror.Append(p.Error, fmt.Errorf( - "ReadDataApply: unequal diffs (real, then shadow):\n\n%#v\n\n%#v", - result.Diff, d)) - p.ErrorLock.Unlock() - } - - return result.Result, result.ResultErr -} - -func (p *shadowResourceProviderShadow) ImportState(info *InstanceInfo, id string) ([]*InstanceState, error) { - panic("import not supported by shadow graph") -} - -// The structs for the various function calls are put below. These structs -// are used to carry call information across the real/shadow boundaries. - -type shadowResourceProviderInput struct { - Config *ResourceConfig - Result *ResourceConfig - ResultErr error -} - -type shadowResourceProviderValidate struct { - Config *ResourceConfig - ResultWarn []string - ResultErr []error -} - -type shadowResourceProviderConfigure struct { - Config *ResourceConfig - Result error -} - -type shadowResourceProviderValidateResourceWrapper struct { - sync.RWMutex - - Calls []*shadowResourceProviderValidateResource -} - -type shadowResourceProviderValidateResource struct { - Config *ResourceConfig - Warns []string - Errors []error -} - -type shadowResourceProviderApply struct { - State *InstanceState - Diff *InstanceDiff - Result *InstanceState - ResultErr error -} - -type shadowResourceProviderDiff struct { - State *InstanceState - Desired *ResourceConfig - Result *InstanceDiff - ResultErr error -} - -type shadowResourceProviderRefresh struct { - State *InstanceState - Result *InstanceState - ResultErr error -} - -type shadowResourceProviderValidateDataSourceWrapper struct { - sync.RWMutex - - Calls []*shadowResourceProviderValidateDataSource -} - -type shadowResourceProviderValidateDataSource struct { - Config *ResourceConfig - Warns []string - Errors []error -} - -type shadowResourceProviderReadDataDiff struct { - Desired *ResourceConfig - Result *InstanceDiff - ResultErr error -} - -type shadowResourceProviderReadDataApply struct { - Diff *InstanceDiff - Result *InstanceState - ResultErr error -} diff --git a/terraform/shadow_resource_provider_test.go b/terraform/shadow_resource_provider_test.go deleted file mode 100644 index be2fba0eaa..0000000000 --- a/terraform/shadow_resource_provider_test.go +++ /dev/null @@ -1,531 +0,0 @@ -package terraform - -import ( - "fmt" - "reflect" - "testing" - "time" -) - -func TestShadowResourceProvider_impl(t *testing.T) { - var _ Shadow = new(shadowResourceProviderShadow) -} - -func TestShadowResourceProvider_cachedValues(t *testing.T) { - mock := new(MockResourceProvider) - real, shadow := newShadowResourceProvider(mock) - - // Resources - { - actual := shadow.Resources() - expected := real.Resources() - if !reflect.DeepEqual(actual, expected) { - t.Fatalf("bad:\n\n%#v\n\n%#v", actual, expected) - } - } - - // DataSources - { - actual := shadow.DataSources() - expected := real.DataSources() - if !reflect.DeepEqual(actual, expected) { - t.Fatalf("bad:\n\n%#v\n\n%#v", actual, expected) - } - } -} - -func TestShadowResourceProviderInput(t *testing.T) { - mock := new(MockResourceProvider) - real, shadow := newShadowResourceProvider(mock) - - // Test values - ui := new(MockUIInput) - config := testResourceConfig(t, map[string]interface{}{ - "foo": "bar", - }) - returnConfig := testResourceConfig(t, map[string]interface{}{ - "bar": "baz", - }) - - // Configure the mock - mock.InputReturnConfig = returnConfig - - // Verify that it blocks until the real input is called - var actual *ResourceConfig - var err error - doneCh := make(chan struct{}) - go func() { - defer close(doneCh) - actual, err = shadow.Input(ui, config) - }() - - select { - case <-doneCh: - t.Fatal("should block until finished") - case <-time.After(10 * time.Millisecond): - } - - // Call the real input - realResult, realErr := real.Input(ui, config) - if !realResult.Equal(returnConfig) { - t.Fatalf("bad: %#v", realResult) - } - if realErr != nil { - t.Fatalf("bad: %s", realErr) - } - - // The shadow should finish now - <-doneCh - - // Verify the shadow returned the same values - if !actual.Equal(returnConfig) { - t.Fatalf("bad: %#v", actual) - } - if err != nil { - t.Fatalf("bad: %s", err) - } - - // Verify we have no errors - if err := shadow.CloseShadow(); err != nil { - t.Fatalf("bad: %s", err) - } -} - -func TestShadowResourceProviderInput_badInput(t *testing.T) { - mock := new(MockResourceProvider) - real, shadow := newShadowResourceProvider(mock) - - // Test values - ui := new(MockUIInput) - config := testResourceConfig(t, map[string]interface{}{ - "foo": "bar", - }) - configBad := testResourceConfig(t, map[string]interface{}{ - "foo": "nope", - }) - - // Call the real with one - real.Input(ui, config) - - // Call the shadow with another - _, err := shadow.Input(ui, configBad) - if err != nil { - t.Fatalf("bad: %s", err) - } - - // Verify we have an error - if err := shadow.CloseShadow(); err != nil { - t.Fatalf("bad: %s", err) - } - if err := shadow.ShadowError(); err == nil { - t.Fatal("should error") - } -} - -func TestShadowResourceProviderValidate(t *testing.T) { - mock := new(MockResourceProvider) - real, shadow := newShadowResourceProvider(mock) - - // Test values - config := testResourceConfig(t, map[string]interface{}{ - "foo": "bar", - }) - returnWarns := []string{"foo"} - returnErrs := []error{fmt.Errorf("bar")} - - // Configure the mock - mock.ValidateReturnWarns = returnWarns - mock.ValidateReturnErrors = returnErrs - - // Verify that it blocks until the real func is called - var warns []string - var errs []error - doneCh := make(chan struct{}) - go func() { - defer close(doneCh) - warns, errs = shadow.Validate(config) - }() - - select { - case <-doneCh: - t.Fatal("should block until finished") - case <-time.After(10 * time.Millisecond): - } - - // Call the real func - realWarns, realErrs := real.Validate(config) - if !reflect.DeepEqual(realWarns, returnWarns) { - t.Fatalf("bad: %#v", realWarns) - } - if !reflect.DeepEqual(realErrs, returnErrs) { - t.Fatalf("bad: %#v", realWarns) - } - - // The shadow should finish now - <-doneCh - - // Verify the shadow returned the same values - if !reflect.DeepEqual(warns, returnWarns) { - t.Fatalf("bad: %#v", warns) - } - if !reflect.DeepEqual(errs, returnErrs) { - t.Fatalf("bad: %#v", errs) - } - - // Verify we have no errors - if err := shadow.CloseShadow(); err != nil { - t.Fatalf("bad: %s", err) - } -} - -func TestShadowResourceProviderValidate_badInput(t *testing.T) { - mock := new(MockResourceProvider) - real, shadow := newShadowResourceProvider(mock) - - // Test values - config := testResourceConfig(t, map[string]interface{}{ - "foo": "bar", - }) - configBad := testResourceConfig(t, map[string]interface{}{ - "foo": "nope", - }) - - // Call the real with one - real.Validate(config) - - // Call the shadow with another - shadow.Validate(configBad) - - // Verify we have an error - if err := shadow.CloseShadow(); err != nil { - t.Fatalf("bad: %s", err) - } - if err := shadow.ShadowError(); err == nil { - t.Fatal("should error") - } -} - -func TestShadowResourceProviderConfigure(t *testing.T) { - mock := new(MockResourceProvider) - real, shadow := newShadowResourceProvider(mock) - - // Test values - config := testResourceConfig(t, map[string]interface{}{ - "foo": "bar", - }) - returnErr := fmt.Errorf("bar") - - // Configure the mock - mock.ConfigureReturnError = returnErr - - // Verify that it blocks until the real func is called - var err error - doneCh := make(chan struct{}) - go func() { - defer close(doneCh) - err = shadow.Configure(config) - }() - - select { - case <-doneCh: - t.Fatal("should block until finished") - case <-time.After(10 * time.Millisecond): - } - - // Call the real func - realErr := real.Configure(config) - if !reflect.DeepEqual(realErr, returnErr) { - t.Fatalf("bad: %#v", realErr) - } - - // The shadow should finish now - <-doneCh - - // Verify the shadow returned the same values - if !reflect.DeepEqual(err, returnErr) { - t.Fatalf("bad: %#v", err) - } - - // Verify we have no errors - if err := shadow.CloseShadow(); err != nil { - t.Fatalf("bad: %s", err) - } -} - -func TestShadowResourceProviderConfigure_badInput(t *testing.T) { - mock := new(MockResourceProvider) - real, shadow := newShadowResourceProvider(mock) - - // Test values - config := testResourceConfig(t, map[string]interface{}{ - "foo": "bar", - }) - configBad := testResourceConfig(t, map[string]interface{}{ - "foo": "nope", - }) - - // Call the real with one - real.Configure(config) - - // Call the shadow with another - shadow.Configure(configBad) - - // Verify we have an error - if err := shadow.CloseShadow(); err != nil { - t.Fatalf("bad: %s", err) - } - if err := shadow.ShadowError(); err == nil { - t.Fatal("should error") - } -} - -func TestShadowResourceProviderApply(t *testing.T) { - mock := new(MockResourceProvider) - real, shadow := newShadowResourceProvider(mock) - - // Test values - info := &InstanceInfo{Id: "foo"} - state := &InstanceState{ID: "foo"} - diff := &InstanceDiff{Destroy: true} - mockResult := &InstanceState{ID: "bar"} - - // Configure the mock - mock.ApplyReturn = mockResult - - // Verify that it blocks until the real func is called - var result *InstanceState - var err error - doneCh := make(chan struct{}) - go func() { - defer close(doneCh) - result, err = shadow.Apply(info, state, diff) - }() - - select { - case <-doneCh: - t.Fatal("should block until finished") - case <-time.After(10 * time.Millisecond): - } - - // Call the real func - realResult, realErr := real.Apply(info, state, diff) - if !realResult.Equal(mockResult) { - t.Fatalf("bad: %#v", realResult) - } - if realErr != nil { - t.Fatalf("bad: %#v", realErr) - } - - // The shadow should finish now - <-doneCh - - // Verify the shadow returned the same values - if !result.Equal(mockResult) { - t.Fatalf("bad: %#v", result) - } - if err != nil { - t.Fatalf("bad: %#v", err) - } - - // Verify we have no errors - if err := shadow.CloseShadow(); err != nil { - t.Fatalf("bad: %s", err) - } -} - -func TestShadowResourceProviderApply_modifyDiff(t *testing.T) { - mock := new(MockResourceProvider) - real, shadow := newShadowResourceProvider(mock) - - // Test values - info := &InstanceInfo{Id: "foo"} - state := &InstanceState{ID: "foo"} - diff := &InstanceDiff{} - mockResult := &InstanceState{ID: "foo"} - - // Configure the mock - mock.ApplyFn = func( - info *InstanceInfo, - s *InstanceState, d *InstanceDiff) (*InstanceState, error) { - d.Destroy = true - return s, nil - } - - // Call the real func - realResult, realErr := real.Apply(info, state.DeepCopy(), diff.DeepCopy()) - if !realResult.Equal(mockResult) { - t.Fatalf("bad: %#v", realResult) - } - if realErr != nil { - t.Fatalf("bad: %#v", realErr) - } - - // Verify the shadow returned the same values - result, err := shadow.Apply(info, state.DeepCopy(), diff.DeepCopy()) - if !result.Equal(mockResult) { - t.Fatalf("bad: %#v", result) - } - if err != nil { - t.Fatalf("bad: %#v", err) - } - - // Verify we have no errors - if err := shadow.CloseShadow(); err != nil { - t.Fatalf("bad: %s", err) - } - if err := shadow.ShadowError(); err != nil { - t.Fatalf("bad: %s", err) - } -} - -func TestShadowResourceProviderApply_modifyState(t *testing.T) { - mock := new(MockResourceProvider) - real, shadow := newShadowResourceProvider(mock) - - // Test values - info := &InstanceInfo{Id: "foo"} - state := &InstanceState{ID: ""} - diff := &InstanceDiff{} - mockResult := &InstanceState{ID: "foo"} - - // Configure the mock - mock.ApplyFn = func( - info *InstanceInfo, - s *InstanceState, d *InstanceDiff) (*InstanceState, error) { - s.ID = "foo" - return s, nil - } - - // Call the real func - realResult, realErr := real.Apply(info, state.DeepCopy(), diff) - if !realResult.Equal(mockResult) { - t.Fatalf("bad: %#v", realResult) - } - if realErr != nil { - t.Fatalf("bad: %#v", realErr) - } - - // Verify the shadow returned the same values - result, err := shadow.Apply(info, state.DeepCopy(), diff) - if !result.Equal(mockResult) { - t.Fatalf("bad: %#v", result) - } - if err != nil { - t.Fatalf("bad: %#v", err) - } - - // Verify we have no errors - if err := shadow.CloseShadow(); err != nil { - t.Fatalf("bad: %s", err) - } - if err := shadow.ShadowError(); err != nil { - t.Fatalf("bad: %s", err) - } -} - -func TestShadowResourceProviderDiff(t *testing.T) { - mock := new(MockResourceProvider) - real, shadow := newShadowResourceProvider(mock) - - // Test values - info := &InstanceInfo{Id: "foo"} - state := &InstanceState{ID: "foo"} - desired := testResourceConfig(t, map[string]interface{}{"foo": "bar"}) - mockResult := &InstanceDiff{Destroy: true} - - // Configure the mock - mock.DiffReturn = mockResult - - // Verify that it blocks until the real func is called - var result *InstanceDiff - var err error - doneCh := make(chan struct{}) - go func() { - defer close(doneCh) - result, err = shadow.Diff(info, state, desired) - }() - - select { - case <-doneCh: - t.Fatal("should block until finished") - case <-time.After(10 * time.Millisecond): - } - - // Call the real func - realResult, realErr := real.Diff(info, state, desired) - if !reflect.DeepEqual(realResult, mockResult) { - t.Fatalf("bad: %#v", realResult) - } - if realErr != nil { - t.Fatalf("bad: %#v", realErr) - } - - // The shadow should finish now - <-doneCh - - // Verify the shadow returned the same values - if !reflect.DeepEqual(result, mockResult) { - t.Fatalf("bad: %#v", result) - } - if err != nil { - t.Fatalf("bad: %#v", err) - } - - // Verify we have no errors - if err := shadow.CloseShadow(); err != nil { - t.Fatalf("bad: %s", err) - } -} - -func TestShadowResourceProviderRefresh(t *testing.T) { - mock := new(MockResourceProvider) - real, shadow := newShadowResourceProvider(mock) - - // Test values - info := &InstanceInfo{Id: "foo"} - state := &InstanceState{ID: "foo"} - mockResult := &InstanceState{ID: "bar"} - - // Configure the mock - mock.RefreshReturn = mockResult - - // Verify that it blocks until the real func is called - var result *InstanceState - var err error - doneCh := make(chan struct{}) - go func() { - defer close(doneCh) - result, err = shadow.Refresh(info, state) - }() - - select { - case <-doneCh: - t.Fatal("should block until finished") - case <-time.After(10 * time.Millisecond): - } - - // Call the real func - realResult, realErr := real.Refresh(info, state) - if !realResult.Equal(mockResult) { - t.Fatalf("bad: %#v", realResult) - } - if realErr != nil { - t.Fatalf("bad: %#v", realErr) - } - - // The shadow should finish now - <-doneCh - - // Verify the shadow returned the same values - if !result.Equal(mockResult) { - t.Fatalf("bad: %#v", result) - } - if err != nil { - t.Fatalf("bad: %#v", err) - } - - // Verify we have no errors - if err := shadow.CloseShadow(); err != nil { - t.Fatalf("bad: %s", err) - } -} diff --git a/terraform/shadow_resource_provisioner.go b/terraform/shadow_resource_provisioner.go deleted file mode 100644 index 60a4908896..0000000000 --- a/terraform/shadow_resource_provisioner.go +++ /dev/null @@ -1,282 +0,0 @@ -package terraform - -import ( - "fmt" - "io" - "log" - "sync" - - "github.com/hashicorp/go-multierror" - "github.com/hashicorp/terraform/helper/shadow" -) - -// shadowResourceProvisioner implements ResourceProvisioner for the shadow -// eval context defined in eval_context_shadow.go. -// -// This is used to verify behavior with a real provisioner. This shouldn't -// be used directly. -type shadowResourceProvisioner interface { - ResourceProvisioner - Shadow -} - -// newShadowResourceProvisioner creates a new shadowed ResourceProvisioner. -func newShadowResourceProvisioner( - p ResourceProvisioner) (ResourceProvisioner, shadowResourceProvisioner) { - // Create the shared data - shared := shadowResourceProvisionerShared{ - Validate: shadow.ComparedValue{ - Func: shadowResourceProvisionerValidateCompare, - }, - } - - // Create the real provisioner that does actual work - real := &shadowResourceProvisionerReal{ - ResourceProvisioner: p, - Shared: &shared, - } - - // Create the shadow that watches the real value - shadow := &shadowResourceProvisionerShadow{ - Shared: &shared, - } - - return real, shadow -} - -// shadowResourceProvisionerReal is the real resource provisioner. Function calls -// to this will perform real work. This records the parameters and return -// values and call order for the shadow to reproduce. -type shadowResourceProvisionerReal struct { - ResourceProvisioner - - Shared *shadowResourceProvisionerShared -} - -func (p *shadowResourceProvisionerReal) Close() error { - var result error - if c, ok := p.ResourceProvisioner.(ResourceProvisionerCloser); ok { - result = c.Close() - } - - p.Shared.CloseErr.SetValue(result) - return result -} - -func (p *shadowResourceProvisionerReal) Validate(c *ResourceConfig) ([]string, []error) { - warns, errs := p.ResourceProvisioner.Validate(c) - p.Shared.Validate.SetValue(&shadowResourceProvisionerValidate{ - Config: c, - ResultWarn: warns, - ResultErr: errs, - }) - - return warns, errs -} - -func (p *shadowResourceProvisionerReal) Apply( - output UIOutput, s *InstanceState, c *ResourceConfig) error { - err := p.ResourceProvisioner.Apply(output, s, c) - - // Write the result, grab a lock for writing. This should nver - // block long since the operations below don't block. - p.Shared.ApplyLock.Lock() - defer p.Shared.ApplyLock.Unlock() - - key := s.ID - raw, ok := p.Shared.Apply.ValueOk(key) - if !ok { - // Setup a new value - raw = &shadow.ComparedValue{ - Func: shadowResourceProvisionerApplyCompare, - } - - // Set it - p.Shared.Apply.SetValue(key, raw) - } - - compareVal, ok := raw.(*shadow.ComparedValue) - if !ok { - // Just log and return so that we don't cause the real side - // any side effects. - log.Printf("[ERROR] unknown value in 'apply': %#v", raw) - return err - } - - // Write the resulting value - compareVal.SetValue(&shadowResourceProvisionerApply{ - Config: c, - ResultErr: err, - }) - - return err -} - -func (p *shadowResourceProvisionerReal) Stop() error { - return p.ResourceProvisioner.Stop() -} - -// shadowResourceProvisionerShadow is the shadow resource provisioner. Function -// calls never affect real resources. This is paired with the "real" side -// which must be called properly to enable recording. -type shadowResourceProvisionerShadow struct { - Shared *shadowResourceProvisionerShared - - Error error // Error is the list of errors from the shadow - ErrorLock sync.Mutex -} - -type shadowResourceProvisionerShared struct { - // NOTE: Anytime a value is added here, be sure to add it to - // the Close() method so that it is closed. - - CloseErr shadow.Value - Validate shadow.ComparedValue - Apply shadow.KeyedValue - ApplyLock sync.Mutex // For writing only -} - -func (p *shadowResourceProvisionerShared) Close() error { - closers := []io.Closer{ - &p.CloseErr, - } - - for _, c := range closers { - // This should never happen, but we don't panic because a panic - // could affect the real behavior of Terraform and a shadow should - // never be able to do that. - if err := c.Close(); err != nil { - return err - } - } - - return nil -} - -func (p *shadowResourceProvisionerShadow) CloseShadow() error { - err := p.Shared.Close() - if err != nil { - err = fmt.Errorf("close error: %s", err) - } - - return err -} - -func (p *shadowResourceProvisionerShadow) ShadowError() error { - return p.Error -} - -func (p *shadowResourceProvisionerShadow) Close() error { - v := p.Shared.CloseErr.Value() - if v == nil { - return nil - } - - return v.(error) -} - -func (p *shadowResourceProvisionerShadow) Validate(c *ResourceConfig) ([]string, []error) { - // Get the result of the validate call - raw := p.Shared.Validate.Value(c) - if raw == nil { - return nil, nil - } - - result, ok := raw.(*shadowResourceProvisionerValidate) - if !ok { - p.ErrorLock.Lock() - defer p.ErrorLock.Unlock() - p.Error = multierror.Append(p.Error, fmt.Errorf( - "Unknown 'validate' shadow value: %#v", raw)) - return nil, nil - } - - // We don't need to compare configurations because we key on the - // configuration so just return right away. - return result.ResultWarn, result.ResultErr -} - -func (p *shadowResourceProvisionerShadow) Apply( - output UIOutput, s *InstanceState, c *ResourceConfig) error { - // Get the value based on the key - key := s.ID - raw := p.Shared.Apply.Value(key) - if raw == nil { - return nil - } - - compareVal, ok := raw.(*shadow.ComparedValue) - if !ok { - p.ErrorLock.Lock() - defer p.ErrorLock.Unlock() - p.Error = multierror.Append(p.Error, fmt.Errorf( - "Unknown 'apply' shadow value: %#v", raw)) - return nil - } - - // With the compared value, we compare against our config - raw = compareVal.Value(c) - if raw == nil { - return nil - } - - result, ok := raw.(*shadowResourceProvisionerApply) - if !ok { - p.ErrorLock.Lock() - defer p.ErrorLock.Unlock() - p.Error = multierror.Append(p.Error, fmt.Errorf( - "Unknown 'apply' shadow value: %#v", raw)) - return nil - } - - return result.ResultErr -} - -func (p *shadowResourceProvisionerShadow) Stop() error { - // For the shadow, we always just return nil since a Stop indicates - // that we were interrupted and shadows are disabled during interrupts - // anyways. - return nil -} - -// The structs for the various function calls are put below. These structs -// are used to carry call information across the real/shadow boundaries. - -type shadowResourceProvisionerValidate struct { - Config *ResourceConfig - ResultWarn []string - ResultErr []error -} - -type shadowResourceProvisionerApply struct { - Config *ResourceConfig - ResultErr error -} - -func shadowResourceProvisionerValidateCompare(k, v interface{}) bool { - c, ok := k.(*ResourceConfig) - if !ok { - return false - } - - result, ok := v.(*shadowResourceProvisionerValidate) - if !ok { - return false - } - - return c.Equal(result.Config) -} - -func shadowResourceProvisionerApplyCompare(k, v interface{}) bool { - c, ok := k.(*ResourceConfig) - if !ok { - return false - } - - result, ok := v.(*shadowResourceProvisionerApply) - if !ok { - return false - } - - return c.Equal(result.Config) -} diff --git a/terraform/shadow_resource_provisioner_test.go b/terraform/shadow_resource_provisioner_test.go deleted file mode 100644 index 7e37d264a4..0000000000 --- a/terraform/shadow_resource_provisioner_test.go +++ /dev/null @@ -1,178 +0,0 @@ -package terraform - -import ( - "errors" - "fmt" - "reflect" - "testing" - "time" -) - -func TestShadowResourceProvisioner_impl(t *testing.T) { - var _ Shadow = new(shadowResourceProvisionerShadow) -} - -func TestShadowResourceProvisionerValidate(t *testing.T) { - mock := new(MockResourceProvisioner) - real, shadow := newShadowResourceProvisioner(mock) - - // Test values - config := testResourceConfig(t, map[string]interface{}{ - "foo": "bar", - }) - returnWarns := []string{"foo"} - returnErrs := []error{fmt.Errorf("bar")} - - // Configure the mock - mock.ValidateReturnWarns = returnWarns - mock.ValidateReturnErrors = returnErrs - - // Verify that it blocks until the real func is called - var warns []string - var errs []error - doneCh := make(chan struct{}) - go func() { - defer close(doneCh) - warns, errs = shadow.Validate(config) - }() - - select { - case <-doneCh: - t.Fatal("should block until finished") - case <-time.After(10 * time.Millisecond): - } - - // Call the real func - realWarns, realErrs := real.Validate(config) - if !reflect.DeepEqual(realWarns, returnWarns) { - t.Fatalf("bad: %#v", realWarns) - } - if !reflect.DeepEqual(realErrs, returnErrs) { - t.Fatalf("bad: %#v", realWarns) - } - - // The shadow should finish now - <-doneCh - - // Verify the shadow returned the same values - if !reflect.DeepEqual(warns, returnWarns) { - t.Fatalf("bad: %#v", warns) - } - if !reflect.DeepEqual(errs, returnErrs) { - t.Fatalf("bad: %#v", errs) - } - - // Verify we have no errors - if err := shadow.CloseShadow(); err != nil { - t.Fatalf("bad: %s", err) - } -} - -func TestShadowResourceProvisionerValidate_diff(t *testing.T) { - mock := new(MockResourceProvisioner) - real, shadow := newShadowResourceProvisioner(mock) - - // Test values - config := testResourceConfig(t, map[string]interface{}{ - "foo": "bar", - }) - returnWarns := []string{"foo"} - returnErrs := []error{fmt.Errorf("bar")} - - // Configure the mock - mock.ValidateReturnWarns = returnWarns - mock.ValidateReturnErrors = returnErrs - - // Run a real validation with a config - real.Validate(testResourceConfig(t, map[string]interface{}{"bar": "baz"})) - - // Verify that it blocks until the real func is called - var warns []string - var errs []error - doneCh := make(chan struct{}) - go func() { - defer close(doneCh) - warns, errs = shadow.Validate(config) - }() - - select { - case <-doneCh: - t.Fatal("should block until finished") - case <-time.After(10 * time.Millisecond): - } - - // Call the real func - realWarns, realErrs := real.Validate(config) - if !reflect.DeepEqual(realWarns, returnWarns) { - t.Fatalf("bad: %#v", realWarns) - } - if !reflect.DeepEqual(realErrs, returnErrs) { - t.Fatalf("bad: %#v", realWarns) - } - - // The shadow should finish now - <-doneCh - - // Verify the shadow returned the same values - if !reflect.DeepEqual(warns, returnWarns) { - t.Fatalf("bad: %#v", warns) - } - if !reflect.DeepEqual(errs, returnErrs) { - t.Fatalf("bad: %#v", errs) - } - - // Verify we have no errors - if err := shadow.CloseShadow(); err != nil { - t.Fatalf("bad: %s", err) - } -} - -func TestShadowResourceProvisionerApply(t *testing.T) { - mock := new(MockResourceProvisioner) - real, shadow := newShadowResourceProvisioner(mock) - - // Test values - output := new(MockUIOutput) - state := &InstanceState{ID: "foo"} - config := testResourceConfig(t, map[string]interface{}{"foo": "bar"}) - mockReturn := errors.New("err") - - // Configure the mock - mock.ApplyReturnError = mockReturn - - // Verify that it blocks until the real func is called - var err error - doneCh := make(chan struct{}) - go func() { - defer close(doneCh) - err = shadow.Apply(output, state, config) - }() - - select { - case <-doneCh: - t.Fatal("should block until finished") - case <-time.After(10 * time.Millisecond): - } - - // Call the real func - realErr := real.Apply(output, state, config) - if realErr != mockReturn { - t.Fatalf("bad: %#v", realErr) - } - - // The shadow should finish now - <-doneCh - - // Verify the shadow returned the same values - if err != mockReturn { - t.Errorf("bad: %#v", err) - } - - // Verify we have no errors - if err := shadow.CloseShadow(); err != nil { - t.Fatalf("bad: %s", err) - } - if err := shadow.ShadowError(); err != nil { - t.Fatalf("bad: %s", err) - } -} diff --git a/terraform/terraform_test.go b/terraform/terraform_test.go index 4a640cf1dd..3be9dea163 100644 --- a/terraform/terraform_test.go +++ b/terraform/terraform_test.go @@ -48,6 +48,8 @@ func TestMain(m *testing.M) { } func tempDir(t *testing.T) string { + t.Helper() + dir, err := ioutil.TempDir("", "tf") if err != nil { t.Fatalf("err: %s", err) @@ -63,6 +65,8 @@ func tempDir(t *testing.T) string { // a function to defer to reset the old value. // the old value that should be set via a defer. func tempEnv(t *testing.T, k string, v string) func() { + t.Helper() + old, oldOk := os.LookupEnv(k) os.Setenv(k, v) return func() { @@ -75,6 +79,8 @@ func tempEnv(t *testing.T, k string, v string) func() { } func testConfig(t *testing.T, name string) *config.Config { + t.Helper() + c, err := config.LoadFile(filepath.Join(fixtureDir, name, "main.tf")) if err != nil { t.Fatalf("err: %s", err) @@ -84,6 +90,8 @@ func testConfig(t *testing.T, name string) *config.Config { } func testModule(t *testing.T, name string) *module.Tree { + t.Helper() + mod, err := module.NewTreeModule("", filepath.Join(fixtureDir, name)) if err != nil { t.Fatalf("err: %s", err) @@ -100,6 +108,8 @@ func testModule(t *testing.T, name string) *module.Tree { // testModuleInline takes a map of path -> config strings and yields a config // structure with those files loaded from disk func testModuleInline(t *testing.T, config map[string]string) *module.Tree { + t.Helper() + cfgPath, err := ioutil.TempDir("", "tf-test") if err != nil { t.Errorf("Error creating temporary directory for config: %s", err) @@ -146,6 +156,8 @@ func testModuleInline(t *testing.T, config map[string]string) *module.Tree { } func testStringMatch(t *testing.T, s fmt.Stringer, expected string) { + t.Helper() + actual := strings.TrimSpace(s.String()) expected = strings.TrimSpace(expected) if actual != expected { diff --git a/terraform/version_required.go b/terraform/version_required.go index 3cbbf56085..723ec39632 100644 --- a/terraform/version_required.go +++ b/terraform/version_required.go @@ -8,17 +8,17 @@ import ( "github.com/hashicorp/terraform/config/module" ) -// checkRequiredVersion verifies that any version requirements specified by +// CheckRequiredVersion verifies that any version requirements specified by // the configuration are met. // // This checks the root module as well as any additional version requirements // from child modules. // // This is tested in context_test.go. -func checkRequiredVersion(m *module.Tree) error { +func CheckRequiredVersion(m *module.Tree) error { // Check any children for _, c := range m.Children() { - if err := checkRequiredVersion(c); err != nil { + if err := CheckRequiredVersion(c); err != nil { return err } } diff --git a/website/docs/configuration/providers.html.md b/website/docs/configuration/providers.html.md index 653bbbfee0..4f8629f81a 100644 --- a/website/docs/configuration/providers.html.md +++ b/website/docs/configuration/providers.html.md @@ -110,6 +110,12 @@ This special argument applies to _all_ providers. view the specified version constraints for all providers used in the current configuration. +When `terraform init` is re-run with providers already installed, it will +use an already-installed provider that meets the constraints in preference +to downloading a new version. To upgrade to the latest acceptable version +of each provider, run `terraform init -upgrade`. This command also upgrades +to the latest versions of all Terraform modules. + ## Multiple Provider Instances You can define multiple instances of the same provider in order to support diff --git a/website/docs/modules/usage.html.markdown b/website/docs/modules/usage.html.markdown index 6a098b442a..79782a746f 100644 --- a/website/docs/modules/usage.html.markdown +++ b/website/docs/modules/usage.html.markdown @@ -84,7 +84,7 @@ Additionally, because these map directly to variables, module configuration can ## Outputs -Modules can also specify their own [outputs](/docs/configuration/outputs.html). These outputs can be referenced in other places in your configuration, for example: +Modules encapsulate their resources. A resource in one module cannot directly depend on resources or attributes in other modules, unless those are exported through [outputs](/docs/configuration/outputs.html). These outputs can be referenced in other places in your configuration, for example: ```hcl resource "aws_instance" "client" {