Merge branch 'master' into f-validate-list-no-duplicates
This commit is contained in:
commit
f17e14f576
|
@ -2,8 +2,7 @@ dist: trusty
|
||||||
sudo: false
|
sudo: false
|
||||||
language: go
|
language: go
|
||||||
go:
|
go:
|
||||||
- 1.8.3
|
- 1.9
|
||||||
- 1.9rc1
|
|
||||||
|
|
||||||
# add TF_CONSUL_TEST=1 to run consul tests
|
# add TF_CONSUL_TEST=1 to run consul tests
|
||||||
# they were causing timouts in travis
|
# they were causing timouts in travis
|
||||||
|
|
|
@ -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/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]
|
* 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)
|
## 0.10.2 (August 16, 2017)
|
||||||
|
|
||||||
BUG FIXES:
|
BUG FIXES:
|
||||||
|
|
13
Makefile
13
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)
|
GOFMT_FILES?=$$(find . -name '*.go' | grep -v vendor)
|
||||||
|
|
||||||
default: test vet
|
default: test vet
|
||||||
|
@ -27,10 +27,11 @@ plugin-dev: generate
|
||||||
mv $(GOPATH)/bin/$(PLUGIN) $(GOPATH)/bin/terraform-$(PLUGIN)
|
mv $(GOPATH)/bin/$(PLUGIN) $(GOPATH)/bin/terraform-$(PLUGIN)
|
||||||
|
|
||||||
# test runs the unit tests
|
# 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
|
test: fmtcheck generate
|
||||||
go test -i $(TEST) || exit 1
|
go test -i $(TEST) || exit 1
|
||||||
echo $(TEST) | \
|
go list $(TEST) | xargs -t -n4 go test $(TESTARGS) -timeout=60s -parallel=4
|
||||||
xargs -t -n4 go test $(TESTARGS) -timeout=60s -parallel=4
|
|
||||||
|
|
||||||
# testacc runs acceptance tests
|
# testacc runs acceptance tests
|
||||||
testacc: fmtcheck generate
|
testacc: fmtcheck generate
|
||||||
|
@ -64,8 +65,8 @@ cover:
|
||||||
# vet runs the Go source code static analysis tool `vet` to find
|
# vet runs the Go source code static analysis tool `vet` to find
|
||||||
# any common errors.
|
# any common errors.
|
||||||
vet:
|
vet:
|
||||||
@echo 'go vet $$(go list ./... | grep -v /terraform/vendor/)'
|
@echo 'go vet ./...'
|
||||||
@go vet $$(go list ./... | grep -v /terraform/vendor/) ; if [ $$? -eq 1 ]; then \
|
@go vet ./... ; if [ $$? -eq 1 ]; then \
|
||||||
echo ""; \
|
echo ""; \
|
||||||
echo "Vet found suspicious constructs. Please check the reported constructs"; \
|
echo "Vet found suspicious constructs. Please check the reported constructs"; \
|
||||||
echo "and fix them if necessary before submitting the code for review."; \
|
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 \
|
@which stringer > /dev/null; if [ $$? -ne 0 ]; then \
|
||||||
go get -u golang.org/x/tools/cmd/stringer; \
|
go get -u golang.org/x/tools/cmd/stringer; \
|
||||||
fi
|
fi
|
||||||
go generate $$(go list ./... | grep -v /terraform/vendor/)
|
go generate ./...
|
||||||
@go fmt command/internal_plugin_list.go > /dev/null
|
@go fmt command/internal_plugin_list.go > /dev/null
|
||||||
|
|
||||||
fmt:
|
fmt:
|
||||||
|
|
|
@ -35,7 +35,7 @@ All documentation is available on the [Terraform website](http://www.terraform.i
|
||||||
Developing Terraform
|
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).
|
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).
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
VAGRANTFILE_API_VERSION = "2"
|
VAGRANTFILE_API_VERSION = "2"
|
||||||
|
|
||||||
# Software version variables
|
# Software version variables
|
||||||
GOVERSION = "1.8.3"
|
GOVERSION = "1.9"
|
||||||
UBUNTUVERSION = "16.04"
|
UBUNTUVERSION = "16.04"
|
||||||
|
|
||||||
# CPU and RAM can be adjusted depending on your system
|
# CPU and RAM can be adjusted depending on your system
|
||||||
|
|
|
@ -13,6 +13,8 @@ import (
|
||||||
// TestBackendConfig validates and configures the backend with the
|
// TestBackendConfig validates and configures the backend with the
|
||||||
// given configuration.
|
// given configuration.
|
||||||
func TestBackendConfig(t *testing.T, b Backend, c map[string]interface{}) Backend {
|
func TestBackendConfig(t *testing.T, b Backend, c map[string]interface{}) Backend {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
// Get the proper config structure
|
// Get the proper config structure
|
||||||
rc, err := config.NewRawConfig(c)
|
rc, err := config.NewRawConfig(c)
|
||||||
if err != nil {
|
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,
|
// If you want to test locking, two backends must be given. If b2 is nil,
|
||||||
// then state lockign won't be tested.
|
// then state lockign won't be tested.
|
||||||
func TestBackend(t *testing.T, b1, b2 Backend) {
|
func TestBackend(t *testing.T, b1, b2 Backend) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
testBackendStates(t, b1)
|
testBackendStates(t, b1)
|
||||||
|
|
||||||
if b2 != nil {
|
if b2 != nil {
|
||||||
|
@ -53,6 +57,8 @@ func TestBackend(t *testing.T, b1, b2 Backend) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testBackendStates(t *testing.T, b Backend) {
|
func testBackendStates(t *testing.T, b Backend) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
states, err := b.States()
|
states, err := b.States()
|
||||||
if err == ErrNamedStatesNotSupported {
|
if err == ErrNamedStatesNotSupported {
|
||||||
t.Logf("TestBackend: named states not supported in %T, skipping", b)
|
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) {
|
func testBackendStateLock(t *testing.T, b1, b2 Backend) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
// Get the default state for each
|
// Get the default state for each
|
||||||
b1StateMgr, err := b1.State(DefaultStateName)
|
b1StateMgr, err := b1.State(DefaultStateName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -54,6 +54,8 @@ func TestMain(m *testing.M) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func tempDir(t *testing.T) string {
|
func tempDir(t *testing.T) string {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
dir, err := ioutil.TempDir("", "tf")
|
dir, err := ioutil.TempDir("", "tf")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
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 {
|
func testModule(t *testing.T, name string) *module.Tree {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
mod, err := module.NewTreeModule("", filepath.Join(fixtureDir, name))
|
mod, err := module.NewTreeModule("", filepath.Join(fixtureDir, name))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
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.
|
// testPlan returns a non-nil noop plan.
|
||||||
func testPlan(t *testing.T) *terraform.Plan {
|
func testPlan(t *testing.T) *terraform.Plan {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
state := terraform.NewState()
|
state := terraform.NewState()
|
||||||
state.RootModule().Outputs["foo"] = &terraform.OutputState{
|
state.RootModule().Outputs["foo"] = &terraform.OutputState{
|
||||||
Type: "string",
|
Type: "string",
|
||||||
|
@ -127,6 +133,8 @@ func testPlan(t *testing.T) *terraform.Plan {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testPlanFile(t *testing.T, plan *terraform.Plan) string {
|
func testPlanFile(t *testing.T, plan *terraform.Plan) string {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
path := testTempFile(t)
|
path := testTempFile(t)
|
||||||
|
|
||||||
f, err := os.Create(path)
|
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 {
|
func testReadPlan(t *testing.T, path string) *terraform.Plan {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
f, err := os.Open(path)
|
f, err := os.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
|
@ -180,6 +190,8 @@ func testState() *terraform.State {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testStateFile(t *testing.T, s *terraform.State) string {
|
func testStateFile(t *testing.T, s *terraform.State) string {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
path := testTempFile(t)
|
path := testTempFile(t)
|
||||||
|
|
||||||
f, err := os.Create(path)
|
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
|
// testStateFileDefault writes the state out to the default statefile
|
||||||
// in the cwd. Use `testCwd` to change into a temp cwd.
|
// in the cwd. Use `testCwd` to change into a temp cwd.
|
||||||
func testStateFileDefault(t *testing.T, s *terraform.State) string {
|
func testStateFileDefault(t *testing.T, s *terraform.State) string {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
f, err := os.Create(DefaultStateFilename)
|
f, err := os.Create(DefaultStateFilename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
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
|
// testStateFileRemote writes the state out to the remote statefile
|
||||||
// in the cwd. Use `testCwd` to change into a temp cwd.
|
// in the cwd. Use `testCwd` to change into a temp cwd.
|
||||||
func testStateFileRemote(t *testing.T, s *terraform.State) string {
|
func testStateFileRemote(t *testing.T, s *terraform.State) string {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
path := filepath.Join(DefaultDataDir, DefaultStateFilename)
|
path := filepath.Join(DefaultDataDir, DefaultStateFilename)
|
||||||
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
|
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
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
|
// testStateRead reads the state from a file
|
||||||
func testStateRead(t *testing.T, path string) *terraform.State {
|
func testStateRead(t *testing.T, path string) *terraform.State {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
f, err := os.Open(path)
|
f, err := os.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
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
|
// testStateOutput tests that the state at the given path contains
|
||||||
// the expected state string.
|
// the expected state string.
|
||||||
func testStateOutput(t *testing.T, path string, expected string) {
|
func testStateOutput(t *testing.T, path string, expected string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
newState := testStateRead(t, path)
|
newState := testStateRead(t, path)
|
||||||
actual := strings.TrimSpace(newState.String())
|
actual := strings.TrimSpace(newState.String())
|
||||||
expected = strings.TrimSpace(expected)
|
expected = strings.TrimSpace(expected)
|
||||||
|
@ -277,10 +297,14 @@ func testProvider() *terraform.MockResourceProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testTempFile(t *testing.T) string {
|
func testTempFile(t *testing.T) string {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
return filepath.Join(testTempDir(t), "state.tfstate")
|
return filepath.Join(testTempDir(t), "state.tfstate")
|
||||||
}
|
}
|
||||||
|
|
||||||
func testTempDir(t *testing.T) string {
|
func testTempDir(t *testing.T) string {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
d, err := ioutil.TempDir("", "tf")
|
d, err := ioutil.TempDir("", "tf")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
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
|
// testRename renames the path to new and returns a function to defer to
|
||||||
// revert the rename.
|
// revert the rename.
|
||||||
func testRename(t *testing.T, base, path, new string) func() {
|
func testRename(t *testing.T, base, path, new string) func() {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
if base != "" {
|
if base != "" {
|
||||||
path = filepath.Join(base, path)
|
path = filepath.Join(base, path)
|
||||||
new = filepath.Join(base, new)
|
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
|
// testChdir changes the directory and returns a function to defer to
|
||||||
// revert the old cwd.
|
// revert the old cwd.
|
||||||
func testChdir(t *testing.T, new string) func() {
|
func testChdir(t *testing.T, new string) func() {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
old, err := os.Getwd()
|
old, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
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
|
// testCwd is used to change the current working directory
|
||||||
// into a test directory that should be remoted after
|
// into a test directory that should be remoted after
|
||||||
func testCwd(t *testing.T) (string, string) {
|
func testCwd(t *testing.T) (string, string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
tmp, err := ioutil.TempDir("", "tf")
|
tmp, err := ioutil.TempDir("", "tf")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
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
|
// testFixCwd is used to as a defer to testDir
|
||||||
func testFixCwd(t *testing.T, tmp, cwd string) {
|
func testFixCwd(t *testing.T, tmp, cwd string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
if err := os.Chdir(cwd); err != nil {
|
if err := os.Chdir(cwd); err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
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 returned function should be deferred to properly clean up and restore
|
||||||
// the original stdin.
|
// the original stdin.
|
||||||
func testStdinPipe(t *testing.T, src io.Reader) func() {
|
func testStdinPipe(t *testing.T, src io.Reader) func() {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
r, w, err := os.Pipe()
|
r, w, err := os.Pipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
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
|
// not useful since the commands are configured to write to a cli.Ui, not
|
||||||
// Stdout directly. Commands like `console` though use the raw stdout.
|
// Stdout directly. Commands like `console` though use the raw stdout.
|
||||||
func testStdoutCapture(t *testing.T, dst io.Writer) func() {
|
func testStdoutCapture(t *testing.T, dst io.Writer) func() {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
r, w, err := os.Pipe()
|
r, w, err := os.Pipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
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 order to interactive prompts. The returned function must be called
|
||||||
// in a defer to clean up.
|
// in a defer to clean up.
|
||||||
func testInteractiveInput(t *testing.T, answers []string) func() {
|
func testInteractiveInput(t *testing.T, answers []string) func() {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
// Disable test mode so input is called
|
// Disable test mode so input is called
|
||||||
test = false
|
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
|
// for calls to Input when the right question is asked. The key is the
|
||||||
// question "Id" that is used.
|
// question "Id" that is used.
|
||||||
func testInputMap(t *testing.T, answers map[string]string) func() {
|
func testInputMap(t *testing.T, answers map[string]string) func() {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
// Disable test mode so input is called
|
// Disable test mode so input is called
|
||||||
test = false
|
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
|
// backend. This returns the complete state that can be saved. Use
|
||||||
// `testStateFileRemote` to write the returned state.
|
// `testStateFileRemote` to write the returned state.
|
||||||
func testBackendState(t *testing.T, s *terraform.State, c int) (*terraform.State, *httptest.Server) {
|
func testBackendState(t *testing.T, s *terraform.State, c int) (*terraform.State, *httptest.Server) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
var b64md5 string
|
var b64md5 string
|
||||||
buf := bytes.NewBuffer(nil)
|
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
|
// testRemoteState is used to make a test HTTP server to return a given
|
||||||
// state file that can be used for testing legacy remote state.
|
// 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) {
|
func testRemoteState(t *testing.T, s *terraform.State, c int) (*terraform.RemoteState, *httptest.Server) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
var b64md5 string
|
var b64md5 string
|
||||||
buf := bytes.NewBuffer(nil)
|
buf := bytes.NewBuffer(nil)
|
||||||
|
|
||||||
|
|
|
@ -51,6 +51,8 @@ func canAccessNetwork() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func skipIfCannotAccessNetwork(t *testing.T) {
|
func skipIfCannotAccessNetwork(t *testing.T) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
if !canAccessNetwork() {
|
if !canAccessNetwork() {
|
||||||
t.Skip("network access not allowed; use TF_ACC=1 to enable")
|
t.Skip("network access not allowed; use TF_ACC=1 to enable")
|
||||||
}
|
}
|
||||||
|
|
|
@ -288,6 +288,11 @@ func (c *InitCommand) getProviders(path string, state *terraform.State, upgrade
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := terraform.CheckRequiredVersion(mod); err != nil {
|
||||||
|
c.Ui.Error(err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
var available discovery.PluginMetaSet
|
var available discovery.PluginMetaSet
|
||||||
if upgrade {
|
if upgrade {
|
||||||
// If we're in upgrade mode, we ignore any auto-installed plugins
|
// If we're in upgrade mode, we ignore any auto-installed plugins
|
||||||
|
|
|
@ -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) {
|
func TestInit_providerLockFile(t *testing.T) {
|
||||||
// Create a temporary working directory that is empty
|
// Create a temporary working directory that is empty
|
||||||
td := tempDir(t)
|
td := tempDir(t)
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
terraform {
|
||||||
|
required_version = "~> 0.9.0"
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
variable "var_without_default" {
|
||||||
|
type = "string"
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
var_without_default = "foo"
|
|
@ -17,7 +17,7 @@ type ValidateCommand struct {
|
||||||
const defaultPath = "."
|
const defaultPath = "."
|
||||||
|
|
||||||
func (c *ValidateCommand) Run(args []string) int {
|
func (c *ValidateCommand) Run(args []string) int {
|
||||||
args, err := c.Meta.process(args, false)
|
args, err := c.Meta.process(args, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
package command
|
package command
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/copy"
|
||||||
"github.com/mitchellh/cli"
|
"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) {
|
func TestValidateFailingCommand(t *testing.T) {
|
||||||
if ui, code := setupTest("validate-invalid"); code != 1 {
|
if ui, code := setupTest("validate-invalid"); code != 1 {
|
||||||
t.Fatalf("Should have failed: %d\n\n%s", code, ui.ErrorWriter.String())
|
t.Fatalf("Should have failed: %d\n\n%s", code, ui.ErrorWriter.String())
|
||||||
|
|
|
@ -143,6 +143,10 @@ func outputsStr(os []*Output) string {
|
||||||
result += fmt.Sprintf(" %s: %s\n", kind, str)
|
result += fmt.Sprintf(" %s: %s\n", kind, str)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if o.Description != "" {
|
||||||
|
result += fmt.Sprintf(" description\n %s\n", o.Description)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return strings.TrimSpace(result)
|
return strings.TrimSpace(result)
|
||||||
|
|
|
@ -499,6 +499,7 @@ func loadOutputsHcl(list *ast.ObjectList) ([]*Output, error) {
|
||||||
|
|
||||||
// Delete special keys
|
// Delete special keys
|
||||||
delete(config, "depends_on")
|
delete(config, "depends_on")
|
||||||
|
delete(config, "description")
|
||||||
|
|
||||||
rawConfig, err := NewRawConfig(config)
|
rawConfig, err := NewRawConfig(config)
|
||||||
if err != nil {
|
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{
|
result = append(result, &Output{
|
||||||
Name: n,
|
Name: n,
|
||||||
RawConfig: rawConfig,
|
RawConfig: rawConfig,
|
||||||
DependsOn: dependsOn,
|
DependsOn: dependsOn,
|
||||||
|
Description: description,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1058,6 +1058,11 @@ aws_instance.test (x1)
|
||||||
`
|
`
|
||||||
|
|
||||||
const basicOutputsStr = `
|
const basicOutputsStr = `
|
||||||
|
web_id
|
||||||
|
vars
|
||||||
|
resource: aws_instance.web.id
|
||||||
|
description
|
||||||
|
The ID
|
||||||
web_ip
|
web_ip
|
||||||
vars
|
vars
|
||||||
resource: aws_instance.web.private_ip
|
resource: aws_instance.web.private_ip
|
||||||
|
|
|
@ -85,6 +85,11 @@ output "web_ip" {
|
||||||
value = "${aws_instance.web.private_ip}"
|
value = "${aws_instance.web.private_ip}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
output "web_id" {
|
||||||
|
description = "The ID"
|
||||||
|
value = "${aws_instance.web.id}"
|
||||||
|
}
|
||||||
|
|
||||||
atlas {
|
atlas {
|
||||||
name = "mitchellh/foo"
|
name = "mitchellh/foo"
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,6 +88,10 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
"output": {
|
"output": {
|
||||||
|
"web_id": {
|
||||||
|
"description": "The ID",
|
||||||
|
"value": "${aws_instance.web.id}"
|
||||||
|
},
|
||||||
"web_ip": {
|
"web_ip": {
|
||||||
"value": "${aws_instance.web.private_ip}"
|
"value": "${aws_instance.web.private_ip}"
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,8 @@ import (
|
||||||
|
|
||||||
// TestRawConfig is used to create a RawConfig for testing.
|
// TestRawConfig is used to create a RawConfig for testing.
|
||||||
func TestRawConfig(t *testing.T, c map[string]interface{}) *RawConfig {
|
func TestRawConfig(t *testing.T, c map[string]interface{}) *RawConfig {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
cfg, err := NewRawConfig(c)
|
cfg, err := NewRawConfig(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
|
|
|
@ -10,6 +10,8 @@ import (
|
||||||
// TestResourceDataRaw creates a ResourceData from a raw configuration map.
|
// TestResourceDataRaw creates a ResourceData from a raw configuration map.
|
||||||
func TestResourceDataRaw(
|
func TestResourceDataRaw(
|
||||||
t *testing.T, schema map[string]*Schema, raw map[string]interface{}) *ResourceData {
|
t *testing.T, schema map[string]*Schema, raw map[string]interface{}) *ResourceData {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
c, err := config.NewRawConfig(raw)
|
c, err := config.NewRawConfig(raw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
|
|
|
@ -3,6 +3,7 @@ package validation
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/helper/schema"
|
"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) {
|
func ValidateJsonString(v interface{}, k string) (ws []string, errors []error) {
|
||||||
if _, err := structure.NormalizeJsonString(v); err != nil {
|
if _, err := structure.NormalizeJsonString(v); err != nil {
|
||||||
errors = append(errors, fmt.Errorf("%q contains an invalid JSON: %s", k, err))
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
return
|
||||||
}
|
}
|
|
@ -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) {
|
func TestValidateJsonString(t *testing.T) {
|
||||||
type testCases struct {
|
type testCases struct {
|
||||||
Value string
|
Value string
|
||||||
|
|
|
@ -11,6 +11,8 @@ import (
|
||||||
// that the given implementation is pre-loaded with the TestStateInitial
|
// that the given implementation is pre-loaded with the TestStateInitial
|
||||||
// state.
|
// state.
|
||||||
func TestState(t *testing.T, s State) {
|
func TestState(t *testing.T, s State) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
if err := s.RefreshState(); err != nil {
|
if err := s.RefreshState(); err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,6 @@ import (
|
||||||
"github.com/hashicorp/hcl"
|
"github.com/hashicorp/hcl"
|
||||||
"github.com/hashicorp/terraform/config"
|
"github.com/hashicorp/terraform/config"
|
||||||
"github.com/hashicorp/terraform/config/module"
|
"github.com/hashicorp/terraform/config/module"
|
||||||
"github.com/hashicorp/terraform/helper/experiment"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// InputMode defines what sort of input will be asked for when Input
|
// 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) {
|
func NewContext(opts *ContextOpts) (*Context, error) {
|
||||||
// Validate the version requirement if it is given
|
// Validate the version requirement if it is given
|
||||||
if opts.Module != nil {
|
if opts.Module != nil {
|
||||||
if err := checkRequiredVersion(opts.Module); err != nil {
|
if err := CheckRequiredVersion(opts.Module); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -465,7 +464,7 @@ func (c *Context) Input(mode InputMode) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do the walk
|
// Do the walk
|
||||||
if _, err := c.walk(graph, nil, walkInput); err != nil {
|
if _, err := c.walk(graph, walkInput); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -506,7 +505,7 @@ func (c *Context) Apply() (*State, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Walk the graph
|
// Walk the graph
|
||||||
walker, err := c.walk(graph, graph, operation)
|
walker, err := c.walk(graph, operation)
|
||||||
if len(walker.ValidationErrors) > 0 {
|
if len(walker.ValidationErrors) > 0 {
|
||||||
err = multierror.Append(err, walker.ValidationErrors...)
|
err = multierror.Append(err, walker.ValidationErrors...)
|
||||||
}
|
}
|
||||||
|
@ -575,7 +574,7 @@ func (c *Context) Plan() (*Plan, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do the walk
|
// Do the walk
|
||||||
walker, err := c.walk(graph, graph, operation)
|
walker, err := c.walk(graph, operation)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -630,7 +629,7 @@ func (c *Context) Refresh() (*State, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do the walk
|
// Do the walk
|
||||||
if _, err := c.walk(graph, graph, walkRefresh); err != nil {
|
if _, err := c.walk(graph, walkRefresh); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -705,7 +704,7 @@ func (c *Context) Validate() ([]string, []error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Walk
|
// Walk
|
||||||
walker, err := c.walk(graph, graph, walkValidate)
|
walker, err := c.walk(graph, walkValidate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, multierror.Append(errs, err).Errors
|
return nil, multierror.Append(errs, err).Errors
|
||||||
}
|
}
|
||||||
|
@ -792,33 +791,11 @@ func (c *Context) releaseRun() {
|
||||||
c.runContext = nil
|
c.runContext = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) walk(
|
func (c *Context) walk(graph *Graph, operation walkOperation) (*ContextGraphWalker, error) {
|
||||||
graph, shadow *Graph, operation walkOperation) (*ContextGraphWalker, error) {
|
|
||||||
// Keep track of the "real" context which is the context that does
|
// Keep track of the "real" context which is the context that does
|
||||||
// the real work: talking to real providers, modifying real state, etc.
|
// the real work: talking to real providers, modifying real state, etc.
|
||||||
realCtx := c
|
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())
|
log.Printf("[DEBUG] Starting graph walk: %s", operation.String())
|
||||||
|
|
||||||
walker := &ContextGraphWalker{
|
walker := &ContextGraphWalker{
|
||||||
|
@ -837,90 +814,6 @@ func (c *Context) walk(
|
||||||
close(watchStop)
|
close(watchStop)
|
||||||
<-watchWait
|
<-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
|
return walker, realErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -66,7 +66,7 @@ func (c *Context) Import(opts *ImportOpts) (*State, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Walk it
|
// Walk it
|
||||||
if _, err := c.walk(graph, nil, walkImport); err != nil {
|
if _, err := c.walk(graph, walkImport); err != nil {
|
||||||
return c.state, err
|
return c.state, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1005,7 +1005,7 @@ func TestContext2Validate_PlanGraphBuilder(t *testing.T) {
|
||||||
t.Fatalf("error attmepting to Build PlanGraphBuilder: %s", err)
|
t.Fatalf("error attmepting to Build PlanGraphBuilder: %s", err)
|
||||||
}
|
}
|
||||||
defer c.acquireRun("validate-test")()
|
defer c.acquireRun("validate-test")()
|
||||||
walker, err := c.walk(graph, graph, walkValidate)
|
walker, err := c.walk(graph, walkValidate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -48,6 +48,8 @@ func TestMain(m *testing.M) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func tempDir(t *testing.T) string {
|
func tempDir(t *testing.T) string {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
dir, err := ioutil.TempDir("", "tf")
|
dir, err := ioutil.TempDir("", "tf")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
|
@ -63,6 +65,8 @@ func tempDir(t *testing.T) string {
|
||||||
// a function to defer to reset the old value.
|
// a function to defer to reset the old value.
|
||||||
// the old value that should be set via a defer.
|
// the old value that should be set via a defer.
|
||||||
func tempEnv(t *testing.T, k string, v string) func() {
|
func tempEnv(t *testing.T, k string, v string) func() {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
old, oldOk := os.LookupEnv(k)
|
old, oldOk := os.LookupEnv(k)
|
||||||
os.Setenv(k, v)
|
os.Setenv(k, v)
|
||||||
return func() {
|
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 {
|
func testConfig(t *testing.T, name string) *config.Config {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
c, err := config.LoadFile(filepath.Join(fixtureDir, name, "main.tf"))
|
c, err := config.LoadFile(filepath.Join(fixtureDir, name, "main.tf"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
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 {
|
func testModule(t *testing.T, name string) *module.Tree {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
mod, err := module.NewTreeModule("", filepath.Join(fixtureDir, name))
|
mod, err := module.NewTreeModule("", filepath.Join(fixtureDir, name))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
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
|
// testModuleInline takes a map of path -> config strings and yields a config
|
||||||
// structure with those files loaded from disk
|
// structure with those files loaded from disk
|
||||||
func testModuleInline(t *testing.T, config map[string]string) *module.Tree {
|
func testModuleInline(t *testing.T, config map[string]string) *module.Tree {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
cfgPath, err := ioutil.TempDir("", "tf-test")
|
cfgPath, err := ioutil.TempDir("", "tf-test")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Error creating temporary directory for config: %s", err)
|
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) {
|
func testStringMatch(t *testing.T, s fmt.Stringer, expected string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
actual := strings.TrimSpace(s.String())
|
actual := strings.TrimSpace(s.String())
|
||||||
expected = strings.TrimSpace(expected)
|
expected = strings.TrimSpace(expected)
|
||||||
if actual != expected {
|
if actual != expected {
|
||||||
|
|
|
@ -8,17 +8,17 @@ import (
|
||||||
"github.com/hashicorp/terraform/config/module"
|
"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.
|
// the configuration are met.
|
||||||
//
|
//
|
||||||
// This checks the root module as well as any additional version requirements
|
// This checks the root module as well as any additional version requirements
|
||||||
// from child modules.
|
// from child modules.
|
||||||
//
|
//
|
||||||
// This is tested in context_test.go.
|
// This is tested in context_test.go.
|
||||||
func checkRequiredVersion(m *module.Tree) error {
|
func CheckRequiredVersion(m *module.Tree) error {
|
||||||
// Check any children
|
// Check any children
|
||||||
for _, c := range m.Children() {
|
for _, c := range m.Children() {
|
||||||
if err := checkRequiredVersion(c); err != nil {
|
if err := CheckRequiredVersion(c); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -110,6 +110,12 @@ This special argument applies to _all_ providers.
|
||||||
view the specified version constraints for all providers used in the
|
view the specified version constraints for all providers used in the
|
||||||
current configuration.
|
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
|
## Multiple Provider Instances
|
||||||
|
|
||||||
You can define multiple instances of the same provider in order to support
|
You can define multiple instances of the same provider in order to support
|
||||||
|
|
|
@ -84,7 +84,7 @@ Additionally, because these map directly to variables, module configuration can
|
||||||
|
|
||||||
## Outputs
|
## 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
|
```hcl
|
||||||
resource "aws_instance" "client" {
|
resource "aws_instance" "client" {
|
||||||
|
|
Loading…
Reference in New Issue